Files
photo-live/docs/plans/2026-09-18-P3-Flutter-App.md
T
2026-09-20 15:23:45 +08:00

12 KiB
Raw Blame History

P3 Flutter App 实施计划

✅ 阶段已完成(2026-09-20,tag p3,基线 79eb975..e484aa5)。 完成状态的权威记录是 docs/plans/progress/P3-验收记录.md;Task 1-4/6/7/9/10 的部分步骤勾选框未逐项回填(12/12 任务全部交付),以验收记录为准。G2 真机关口与 D03 分发决策待用户执行/确认。

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 交付工作人员 Flutter App(Android 优先落地):账号密码登录、相册配置与协作(含分享二维码)、持久化上传队列(准备阶段状态机、账号隔离、连拍入队、跨设备服务端历史)、原生后台上传桥接(Android UIDT/前台服务;iOS 代码就绪待 G3 编译)、App 内语言切换——全部经真实 cn-api 验证(MuMu 模拟器)。

Architecture: apps/mobile 单一 Flutter 工程(Android/iOS 双平台配置);Dart 侧 = Riverpod 状态 + drift 本地任务库 + dio 客户端(复用 @photo/contracts?——contracts 是 TS,不共享运行时;App 内以手工镜像的 Dart 模型 + openapi 生成的对照文档开发,契约以 openapi.yaml 为准人工对齐,登记偏差);原生侧 = Kotlin WorkManager(UIDT) / Swift URLSession background 经 MethodChannel 桥接。后端零改动(P1 契约已齐)。

Tech Stack: Flutter 3.44.7 stable(本机 D:\dev\flutter)· Dart 3.13 · Riverpod · drift(SQLite) · dio · flutter_secure_storage · image_picker · camera · qr_flutter · integration_test + MuMu 模拟器(adb connect 127.0.0.1:16384 或运行时探测)

对应验收(代码+模拟器级): A04 A05(模拟器级——锁屏/厂商后台留 G2)A15 A19;A02/A01 联动回归。

依赖基线: tag p2(79eb975)。环境前置(已验证 2026-09-18):D:\dev\flutter(3.44.7)、D:\dev\android-sdk(API 34-36/bt36/licenses 已受)、D:\dev\jdk-21、pub-cache 在 D:\dev\pub-cache、MuMu 模拟器已安装(D:\application\MuMuPlayer)。env 约定:所有 Flutter 命令前置 source scripts/flutter-env.sh(仓库根新增:PATH/JAVA_HOME/ANDROID_HOME/PUB_HOSTED_URL=https://pub.flutter-io.cn/FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn/PUB_CACHE=D:\dev\pub-cache)。

iOS 边界(G3 关口):Swift 桥接代码与 Info.plist/backgroundModes 完整编写并提交,但编译/签名/真机验证依赖 macOS——出口时列 G2/G3 清单交用户。


Task 1: 工程脚手架 + 环境脚本

  • scripts/flutter-env.sh(上述 env);apps/mobile flutter create(org com.photolive,platforms android,ios——ios 目录生成但本机不编译);Android minSdk 24/targetSdk 36(UIDT 可用需 34+,文档注明降级路径=WorkManager+前台服务通知)
  • 依赖接入(Riverpod/drift/dio/secure_storage/image_picker/camera/qr_flutter);l10n(flutter gen-l10n,zh/en arb);基础主题与路由(login→home(albums)→album detail→queue);flutter analyze 干净;Android debug 构建 + MuMu 安装冒烟(adb connect → flutter run -d 或 build apk + adb install)
  • Commit feat(mobile): flutter scaffold with env script and baseline nav

Task 2: API 客户端与会话

  • dio 封装(base url 从 --dart-define/env dev=http://10.0.2.2:8080(模拟器宿主回环);Bearer 注入 flutter_secure_storage;401→清会话跳登录;错误码→l10n 文案映射,对照 errors.ts 14 码全量)
  • Dart 模型层(Login/Album/Member/Category/UploadSession/PhotoRecord/AdminPhoto 等,字段对齐 openapi.yaml;lint 规则:模型注释标注契约 operationId)
  • 单测(dio 拦截器/错误映射/模型解析 json fixture——真实 openapi examples 手工转 fixture)
  • Commit feat(mobile): api client, session storage and contract-aligned models

Task 3: 登录与修改密码

  • 登录页(账号/密码、错误本地化含 AUTH_RATE_LIMITED、429 提示重试等待);会话持久(secure storage);退出登录(调 logout 尽力而为);修改密码页(后端缺口:契约无修改密码端点——P1 范围内"支持修改密码"是需求 4.1!本任务顺带后端补:契约+cn 实现 POST /v1/auth/password-change(Bearer+旧密码+新密码,旧密码错→AUTH_INVALID_CREDENTIALS;成功后撤销其他会话、保留当前+ e2e);同一契约批次顺带补 sourceDeviceId(§6.1:POST /uploads 请求可选 +sourceDeviceId(设备随机标识,App 首次生成持久),upload_sessions 落列,GET /uploads 条目返回——A19 设备来源语义);admin 无 UI(P4a)——契约变更走 gen 流程,登记为 P3 增补)
  • widget 测试 + 模拟器手动冒烟
  • Commit feat(mobile): login, logout and password change (+backend endpoint)

Task 4: 相册列表/创建/编辑 + 二维码

  • 相册列表(游标加载更多、开关状态标签);创建/编辑表单(zh-CN/en 双语 tab、时间选择、时区默认、开关、defaultLocale 校验);详情页(信息+分类管理+成员管理:账号前缀搜索邀请/移除)
  • 分享:观众链接展示 + qr_flutter 二维码(viewer URL 构造:{VIEWER_BASE}/a/{token}——VIEWER_BASE dart-define,dev=http://10.0.2.2:5175)
  • widget 测试;MuMu 手动冒烟(对着本地 cn-api + viewer 真链路)
  • Commit feat(mobile): album management pages with qr share

Task 5: 上传域模型与本地任务库(drift)

  • drift schema:upload_tasks(id uuid、accountId 隔离键、albumId、categoryId、clientTaskId、源文件路径(受管目录 copy)、filename/bytes/contentType、state 状态机 preparing/preparation_failed/queued/uploading/uploaded-confirming/completed/waiting_network/retryable_failed/cancelled、attempt/nextRetryAt、serverSessionId/photoId、createdAt/updatedAt、账号切换过滤=accountId 索引)
  • 状态机单测(合法迁移表驱动;账号隔离查询;任务保留策略=completed 仅可清理标记)
  • Commit feat(mobile): drift upload task store with state machine

Task 6: 上传引擎(Dart 侧)

  • 准备阶段:image_picker/camera 选图/拍摄 → 复制到受管目录(app 支持目录/uploads/{albumId}/)→ 空间检查(磁盘阈值)→ 入队 queued;准备失败独立状态(preparation_failed,可重试);连拍模式:camera 连续拍摄每张自动 preparing→queued,目标相册+分类固定显示于拍摄页
  • 传输:出队 → POST /uploads(幂等 clientTaskId,含 sourceDeviceId)→ presign PUT(dio,进度回调)→ complete → completed;失败分类(网络→waiting_network 指数退避;409 冲突→terminal+提示;凭证过期→renew 重试);取消(未开始直接删;进行中标记取消尽力);源文件清理执行器(评审缺口 1:completed 任务源文件按 KEEP_COMPLETED_DAYS(默认 14)自动清理,执行器随队列引擎运行;硬约束=仅清理 completed/cancelled,未完成源文件永不清理——单测覆盖该守护)
  • 服务端历史页(GET /uploads:跨设备记录,显示 state/photo.processingState,明确标注"本机队列不在此列"——A19 语义)
  • 引擎单测(fake api:幂等重试/断网恢复/取消/账号隔离/连拍归属不变 A15)
  • Commit feat(mobile): upload engine with preparation, batching states and retries

Task 7: Android 原生后台上传桥接

  • Kotlin 侧:MethodChannel photolive/uploads——入队(registerTask: taskId, presignUrl, headers, file, metadata)、查询/取消;实现=WorkManager OneTimeWorkRequest(targetSdk>=34 走 setExpedited+数据传输 foregroundServiceType=dataSync;通知渠道+进度通知);任务映射表(taskId→workId 持久 WorkManager 自持);完成/失败回调 EventChannel→Dart 校准队列状态
  • Dart 侧适配:engine 的传输步骤在 Android 上委托原生(可选开关 dart-define FORCE_DART_TRANSPORT 用于调试);锁屏/切后台行为=系统 WorkManager 语义(模拟器可验:切后台继续)
  • Android 单测(WorkManager test)+ 模拟器验证:上传中切后台/息屏(模拟器电源按钮)→ 队列继续完成
  • Commit feat(mobile): android background upload bridge via workmanager

Task 8: iOS 桥接代码(编译留 G3)

  • Swift:URLSession background(withIdentifier:) + AppDelegate wiring + MethodChannel 同协议;Info.plist backgroundModes(fetch/processing 按文档)、上传任务映射持久化(plist/sqlite 简版);README-ios.md(Xcode 步骤、签名、验证清单=技术方案 6.2 要点)——实现偏差:UIBackgroundModes 有意不声明(background URLSession 无需,见 README-ios.md 与 Info.plist 注释)
  • 静态保证:dart 侧协议层与平台无关(同一 interface),iOS 分支代码完整、flutter analyze 通过(iOS 不编译不阻塞)
  • Commit feat(mobile): ios background upload bridge (compile-gated at G3)

Task 9: 队列 UI 与连拍页

  • 队列页:按相册/状态/时间(需求 4.3 三维筛选)筛选、缩略图、进度条、错误原因(本地化)、重试/批量重试/取消;拍摄页:常驻相册名+分类+待传数+失败提示(R17/O01)
  • widget 测试
  • Commit feat(mobile): upload queue ui and continuous capture page

Task 10: App 内语言切换

  • 设置项 zh/en/跟随系统;持久;立即生效(Riverpod locale provider);全部页面文案走 l10n(漏译检查脚本——arb 完整性单测)
  • Commit feat(mobile): in-app locale switching

Task 11: 集成测试(MuMu + 真实后端)

  • integration_test 套件(对本地 cn-api + MinIO):登录→建相册(zh+en)→邀成员→传 3 图(含连拍 2 张)→队列全 completed→服务端历史可见→(flushSync+ov)viewer 网页侧可见 3 张→隐藏一张→App 侧管理列表状态同步→修改密码→重登录。断网场景:dio mock 拦截(模拟器断真实网不便)驱动 waiting_network→恢复重试
  • 后台延续场景:Android 上传中切后台 30s+ → 完成(原生桥接路径);上传中杀进程重启→队列状态完整恢复续传(A05 模拟器级;需持久化 in-flight 状态随任务库)
  • Commit test(mobile): integration suite against real backend on mumu

Task 12: 回归门 + P3 验收记录

  • 全仓回归绿(web 侧不降级);flutter analyze/test 零警告;debug apk 构建产物留存路径登记
  • docs/plans/progress/P3-验收记录.md(A04/A05 模拟器级、A15/A19、契约增补 password-change + sourceDeviceId 登记、G2 真机清单(锁屏15min/厂商后台/断网/凭证过期/强制退出/源文件缺失/存储不足/批量100张真实样本/HEIC 真机样本——技术方案 6.4 全量)、G3 iOS 编译清单、偏差表(Dart 模型手工对齐契约、模拟器网络回环 10.0.2.2、断网用 mock 拦截、Android 桥接采用 expedited+dataSync 前台服务实现(§6.3 UIDT 主路径待 G2 真机评估后定)、源文件保留策略默认 14 天))
  • 总纲 P3 行进度注记(代码+模拟器级完成;G2 强制关口待用户真机执行);git tag p3;Commit docs: p3 acceptance record

执行注意

  • Flutter 命令一律先 source scripts/flutter-env.sh(子代理 dispatch 时在 prompt 里显式给出 export 串)。
  • 模拟器:MuMu 启动后 adb connect 127.0.0.1:16384(失败则探测 5555/7555/21503,MuMu 12 常用 16384);adb devices 确认后再 flutter run/install。
  • 后端联动默认全本地(compose 套件 + dev:api + flushSync);不得触碰生产资源。
  • 契约唯一增补=password-change(Task 3),其余一律复用既有端点;模型与 openapi.yaml 字段分歧=偏差登记。
  • 每任务 TDD;提交带 Co-Authored-By trailer;apk 产物不入库(gitignore apps/mobile/build)。