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
+300
View File
@@ -805,6 +805,306 @@ abstract class AppLocalizations {
/// In zh, this message translates to:
/// **'本设备'**
String get thisDevice;
/// No description provided for @queueAlbumFilterAll.
///
/// In zh, this message translates to:
/// **'全部相册'**
String get queueAlbumFilterAll;
/// No description provided for @queueStateAll.
///
/// In zh, this message translates to:
/// **'全部'**
String get queueStateAll;
/// No description provided for @queueStatePreparing.
///
/// In zh, this message translates to:
/// **'准备中'**
String get queueStatePreparing;
/// No description provided for @queueStateQueued.
///
/// In zh, this message translates to:
/// **'待传'**
String get queueStateQueued;
/// No description provided for @queueStateUploading.
///
/// In zh, this message translates to:
/// **'上传中'**
String get queueStateUploading;
/// No description provided for @queueStateWaitingNetwork.
///
/// In zh, this message translates to:
/// **'等待网络'**
String get queueStateWaitingNetwork;
/// No description provided for @queueStateFailed.
///
/// In zh, this message translates to:
/// **'失败'**
String get queueStateFailed;
/// No description provided for @queueStateCompleted.
///
/// In zh, this message translates to:
/// **'已完成'**
String get queueStateCompleted;
/// No description provided for @queueTimeAll.
///
/// In zh, this message translates to:
/// **'全部时间'**
String get queueTimeAll;
/// No description provided for @queueTimeToday.
///
/// In zh, this message translates to:
/// **'今天'**
String get queueTimeToday;
/// No description provided for @queueTimeLast7Days.
///
/// In zh, this message translates to:
/// **'近7天'**
String get queueTimeLast7Days;
/// No description provided for @queueTimeLast30Days.
///
/// In zh, this message translates to:
/// **'近30天'**
String get queueTimeLast30Days;
/// No description provided for @queueEmpty.
///
/// In zh, this message translates to:
/// **'暂无上传任务'**
String get queueEmpty;
/// No description provided for @queueRetryAllFailed.
///
/// In zh, this message translates to:
/// **'一键重试全部失败'**
String get queueRetryAllFailed;
/// No description provided for @queueAttemptCount.
///
/// In zh, this message translates to:
/// **'已尝试 {count} 次'**
String queueAttemptCount(int count);
/// No description provided for @taskStatePreparing.
///
/// In zh, this message translates to:
/// **'准备中'**
String get taskStatePreparing;
/// No description provided for @taskStatePreparationFailed.
///
/// In zh, this message translates to:
/// **'准备失败'**
String get taskStatePreparationFailed;
/// No description provided for @taskStateQueued.
///
/// In zh, this message translates to:
/// **'待上传'**
String get taskStateQueued;
/// No description provided for @taskStateUploading.
///
/// In zh, this message translates to:
/// **'上传中'**
String get taskStateUploading;
/// No description provided for @taskStateUploadedConfirming.
///
/// In zh, this message translates to:
/// **'确认中'**
String get taskStateUploadedConfirming;
/// No description provided for @taskStateCompleted.
///
/// In zh, this message translates to:
/// **'已完成'**
String get taskStateCompleted;
/// No description provided for @taskStateWaitingNetwork.
///
/// In zh, this message translates to:
/// **'等待网络'**
String get taskStateWaitingNetwork;
/// No description provided for @taskStateRetryableFailed.
///
/// In zh, this message translates to:
/// **'上传失败'**
String get taskStateRetryableFailed;
/// No description provided for @taskStateCancelled.
///
/// In zh, this message translates to:
/// **'已取消'**
String get taskStateCancelled;
/// No description provided for @errorPrepSourceUnreadable.
///
/// In zh, this message translates to:
/// **'无法读取所选图片,请重新选择'**
String get errorPrepSourceUnreadable;
/// No description provided for @errorPrepUnsupportedType.
///
/// In zh, this message translates to:
/// **'仅支持 JPG/PNG/HEIC 图片'**
String get errorPrepUnsupportedType;
/// No description provided for @errorPrepTooLarge.
///
/// In zh, this message translates to:
/// **'图片超过 50MB 上限'**
String get errorPrepTooLarge;
/// No description provided for @errorPrepCopyFailed.
///
/// In zh, this message translates to:
/// **'保存图片副本失败,请重试'**
String get errorPrepCopyFailed;
/// No description provided for @errorUploadSourceMissing.
///
/// In zh, this message translates to:
/// **'源文件已缺失,请重新选择'**
String get errorUploadSourceMissing;
/// No description provided for @errorAlbumUploadDisabled.
///
/// In zh, this message translates to:
/// **'相册已关闭上传'**
String get errorAlbumUploadDisabled;
/// No description provided for @errorUploadSessionConflict.
///
/// In zh, this message translates to:
/// **'上传会话冲突,请重试'**
String get errorUploadSessionConflict;
/// No description provided for @errorUploadVerifyFailed.
///
/// In zh, this message translates to:
/// **'上传核验失败,将自动重试'**
String get errorUploadVerifyFailed;
/// No description provided for @captureTitle.
///
/// In zh, this message translates to:
/// **'连拍上传'**
String get captureTitle;
/// No description provided for @captureCategoryDefault.
///
/// In zh, this message translates to:
/// **'默认分类'**
String get captureCategoryDefault;
/// No description provided for @captureSwitchHint.
///
/// In zh, this message translates to:
/// **'切换分类仅影响之后拍摄的照片'**
String get captureSwitchHint;
/// No description provided for @capturePendingBadge.
///
/// In zh, this message translates to:
/// **'待传 {count}'**
String capturePendingBadge(int count);
/// No description provided for @captureFailedBadge.
///
/// In zh, this message translates to:
/// **'失败 {count}'**
String captureFailedBadge(int count);
/// No description provided for @capturePickFromGallery.
///
/// In zh, this message translates to:
/// **'从相册选择'**
String get capturePickFromGallery;
/// No description provided for @captureShutter.
///
/// In zh, this message translates to:
/// **'拍摄'**
String get captureShutter;
/// No description provided for @captureExitTitle.
///
/// In zh, this message translates to:
/// **'退出拍摄?'**
String get captureExitTitle;
/// No description provided for @captureExitBody.
///
/// In zh, this message translates to:
/// **'还有 {count} 张照片未上传,退出后任务将在后台继续。'**
String captureExitBody(int count);
/// No description provided for @captureKeepShooting.
///
/// In zh, this message translates to:
/// **'继续拍摄'**
String get captureKeepShooting;
/// No description provided for @captureExitConfirm.
///
/// In zh, this message translates to:
/// **'退出'**
String get captureExitConfirm;
/// No description provided for @cameraPermissionDenied.
///
/// In zh, this message translates to:
/// **'无法访问相机,请在系统设置中允许相机权限'**
String get cameraPermissionDenied;
/// No description provided for @cameraInitFailed.
///
/// In zh, this message translates to:
/// **'相机初始化失败'**
String get cameraInitFailed;
/// No description provided for @uploadActionCapture.
///
/// In zh, this message translates to:
/// **'拍照上传'**
String get uploadActionCapture;
/// No description provided for @uploadActionPick.
///
/// In zh, this message translates to:
/// **'从相册选择'**
String get uploadActionPick;
/// No description provided for @uploadAction.
///
/// In zh, this message translates to:
/// **'上传'**
String get uploadAction;
/// No description provided for @uploadEnqueuedCount.
///
/// In zh, this message translates to:
/// **'已加入 {count} 张上传队列'**
String uploadEnqueuedCount(int count);
/// No description provided for @uploadPickNone.
///
/// In zh, this message translates to:
/// **'未选择照片'**
String get uploadPickNone;
}
class _AppLocalizationsDelegate