/// 选图注入点 + 批量入队动作(P3 计划 Task 9)。 /// /// image_picker 的平台调用经 Provider 注入([singleImagePickerProvider]/ /// [multiImagePickerProvider]),widget 测试 override 为替身——不经平台 /// 通道即可驱动入队流;[enqueueForUpload] 为连拍页与相册详情 FAB 的共用 /// 入队路径:首次动作触发 POST_NOTIFICATIONS 请求(静默降级)+ 逐张经 /// 准备器入队(准备器内部串行复制,防连拍 IO 风暴)。 library; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:image_picker/image_picker.dart'; import '../../core/notification_permission.dart'; import 'providers.dart'; import 'task_state.dart'; /// 单图选择(准备失败重试的重新选图路径);返回源文件路径或 null(取消)。 typedef SingleImagePicker = Future Function(); /// 多图选择(相册多选入队);返回源文件路径列表(空=取消)。 typedef MultiImagePicker = Future> Function(); /// 单图选择 Provider(生产=image_picker;测试 override)。 final singleImagePickerProvider = Provider( (ref) => () async { final file = await ImagePicker().pickImage(source: ImageSource.gallery); return file?.path; }, ); /// 多图选择 Provider(生产=image_picker;测试 override)。 final multiImagePickerProvider = Provider( (ref) => () async { final files = await ImagePicker().pickMultiImage(); return files.map((file) => file.path).toList(); }, ); /// 批量入队:通知权限单次请求 + 逐张准备入队。 /// /// 返回成功入队(state=queued)的张数;准备失败的任务已落库为 /// preparationFailed(连拍页失败徽章/队列页可见,不在此抛出)。 Future enqueueForUpload( WidgetRef ref, { required String accountId, required String albumId, String? categoryId, required List sourcePaths, }) async { // 首次入队动作触发(幂等;拒绝/非 Android 静默降级,见实现注释)。 await ref.read(notificationPermissionProvider).ensureRequested(); final preparer = ref.read(uploadPreparerProvider); var enqueued = 0; for (final path in sourcePaths) { final task = await preparer.prepare( accountId: accountId, albumId: albumId, categoryId: categoryId, sourcePath: path, ); if (task.state == UploadTaskState.queued) enqueued++; } return enqueued; }