feat(mobile): upload queue ui and continuous capture page

Task 9 (P3 plan): queue tab replaces the stub, continuous capture page
enqueues per shot, album detail gains an upload FAB, and the upload
engine lifecycle is now bound to the login session.

- Engine scope: session account drives UploadEngine start/stop; wired
  from app boot, login success, logout, and 401 eviction. Tasks stay in
  the store across logout (accountId hard isolation).
- Queue tab: album x state x time filter bar, task rows with thumbnail,
  state chip, localized error copy, attempt count, per-row progress via
  a broadcast progress hub, state-dependent actions (re-pick retry,
  retryNow, cancel, delete), batch retry bar, 200ms-debounced live
  refresh from drift table updates.
- Capture page: camera preview via injected CaptureSource, one enqueue
  per shutter press, persistent album/category/badges overlay with a
  switch-only-affects-new-shots hint, gallery multi-pick, exit confirm
  while uploads are pending, permission-denied and init-failure views.
- POST_NOTIFICATIONS runtime permission via a small MethodChannel
  (photolive/permissions) + PermissionPlugin.kt: single runtime
  permission need, avoids permission_handler dependency; requested once
  on first enqueue, silent degradation when denied or unavailable.
- Album detail FAB: capture-and-upload / pick-from-gallery sheet.
- l10n: ~50 new keys in zh/en arbs; tests: +45 (filter matrix, engine
  scope lifecycle, permission gate, queue tab, capture page, FAB entry);
  existing page tests updated to inject session/task-store fakes.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-19 04:47:31 +08:00
parent 42e1c2e34b
commit 955f9b13c1
30 changed files with 4206 additions and 51 deletions
+56 -1
View File
@@ -117,5 +117,60 @@
"uploadStateConfirmed": "已确认",
"uploadStateCancelled": "已取消",
"uploadStateExpired": "已过期",
"thisDevice": "本设备"
"thisDevice": "本设备",
"queueAlbumFilterAll": "全部相册",
"queueStateAll": "全部",
"queueStatePreparing": "准备中",
"queueStateQueued": "待传",
"queueStateUploading": "上传中",
"queueStateWaitingNetwork": "等待网络",
"queueStateFailed": "失败",
"queueStateCompleted": "已完成",
"queueTimeAll": "全部时间",
"queueTimeToday": "今天",
"queueTimeLast7Days": "近7天",
"queueTimeLast30Days": "近30天",
"queueEmpty": "暂无上传任务",
"queueRetryAllFailed": "一键重试全部失败",
"queueAttemptCount": "已尝试 {count} 次",
"@queueAttemptCount": { "placeholders": { "count": { "type": "int" } } },
"taskStatePreparing": "准备中",
"taskStatePreparationFailed": "准备失败",
"taskStateQueued": "待上传",
"taskStateUploading": "上传中",
"taskStateUploadedConfirming": "确认中",
"taskStateCompleted": "已完成",
"taskStateWaitingNetwork": "等待网络",
"taskStateRetryableFailed": "上传失败",
"taskStateCancelled": "已取消",
"errorPrepSourceUnreadable": "无法读取所选图片,请重新选择",
"errorPrepUnsupportedType": "仅支持 JPG/PNG/HEIC 图片",
"errorPrepTooLarge": "图片超过 50MB 上限",
"errorPrepCopyFailed": "保存图片副本失败,请重试",
"errorUploadSourceMissing": "源文件已缺失,请重新选择",
"errorAlbumUploadDisabled": "相册已关闭上传",
"errorUploadSessionConflict": "上传会话冲突,请重试",
"errorUploadVerifyFailed": "上传核验失败,将自动重试",
"captureTitle": "连拍上传",
"captureCategoryDefault": "默认分类",
"captureSwitchHint": "切换分类仅影响之后拍摄的照片",
"capturePendingBadge": "待传 {count}",
"@capturePendingBadge": { "placeholders": { "count": { "type": "int" } } },
"captureFailedBadge": "失败 {count}",
"@captureFailedBadge": { "placeholders": { "count": { "type": "int" } } },
"capturePickFromGallery": "从相册选择",
"captureShutter": "拍摄",
"captureExitTitle": "退出拍摄?",
"captureExitBody": "还有 {count} 张照片未上传,退出后任务将在后台继续。",
"@captureExitBody": { "placeholders": { "count": { "type": "int" } } },
"captureKeepShooting": "继续拍摄",
"captureExitConfirm": "退出",
"cameraPermissionDenied": "无法访问相机,请在系统设置中允许相机权限",
"cameraInitFailed": "相机初始化失败",
"uploadActionCapture": "拍照上传",
"uploadActionPick": "从相册选择",
"uploadAction": "上传",
"uploadEnqueuedCount": "已加入 {count} 张上传队列",
"@uploadEnqueuedCount": { "placeholders": { "count": { "type": "int" } } },
"uploadPickNone": "未选择照片"
}