/// 上传域 Provider 装配(P3 计划 Task 6)。 /// /// 生产装配:TaskStore.app()(documents/upload_tasks.sqlite)+ ApiClient /// (Bearer 自动注入)+ DeviceId(shared_preferences)。引擎生命周期 /// (start(accountId)/stop,随登录态切换)在 engine_scope.dart 绑定 /// (Task 9 接线)。 library; import 'dart:async'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/api_client.dart'; import '../../core/device_id.dart'; import 'preparer.dart'; import 'task_store.dart'; import 'upload_engine.dart'; /// 任务库(生产:app documents dir 持久库;测试 override 为 memory)。 final taskStoreProvider = Provider((ref) { final store = TaskStore.app(); ref.onDispose(store.close); return store; }); /// 单任务传输进度事件(队列 UI 行级进度条消费)。 class UploadTaskProgress { const UploadTaskProgress({ required this.taskId, required this.sent, required this.total, }); final String taskId; final int sent; final int total; /// 0~1 进度分数;total 未知(0)返回 null(UI 转不确定态进度条)。 double? get fraction => total > 0 ? (sent / total).clamp(0.0, 1.0).toDouble() : null; } /// 进度事件总线:引擎 onProgress 回调 → 广播流(行级 StreamBuilder 按 /// taskId 过滤消费,避免整表重建风暴)。 class UploadProgressHub { final StreamController _controller = StreamController.broadcast(); /// 进度事件流(广播;行级按 taskId 过滤)。 Stream get stream => _controller.stream; /// 引擎回调挂接点(uploadEngineProvider 内接线)。 void emit(String taskId, int sent, int total) => _controller.add(UploadTaskProgress(taskId: taskId, sent: sent, total: total)); void dispose() => _controller.close(); } /// 进度总线 Provider。 final uploadProgressHubProvider = Provider((ref) { final hub = UploadProgressHub(); ref.onDispose(hub.dispose); return hub; }); /// 传输引擎(裸 Dio 直传 presign;并行/保留期配置见 upload_engine.dart /// 的 dart-define 常量;进度经 [UploadProgressHub] 广播)。 final uploadEngineProvider = Provider((ref) { final hub = ref.watch(uploadProgressHubProvider); final engine = UploadEngine( ref.watch(taskStoreProvider), ref.watch(apiClientProvider), ref.watch(deviceIdProvider), onProgress: (taskId, sent, total) => hub.emit(taskId, sent, total), ); ref.onDispose(engine.dispose); return engine; }); /// 准备器(入队回调唤醒引擎立即出队)。 final uploadPreparerProvider = Provider((ref) { final engine = ref.watch(uploadEngineProvider); return UploadPreparer( ref.watch(taskStoreProvider), onEnqueued: engine.notifyWorkAvailable, ); });