Commit Graph

3 Commits

Author SHA1 Message Date
浅念 0673f394d5 fix(app): picker dup gate — 2s server-check timeout, single snackbar, fileSize-throw allow
评审跟进三项(拦截面加固,判定口径不变):

- 服务器半场时间帽:checkUploadDuplicates 竞速 serverCheckTimeout
  (构造参数,默认 2s)——弱网下点选不再静默挂到 dio 连接超时
  (10s);超时=无命中,同各半场失败降级放行(底层请求不取消,
  dio 自身超时照常兜底)。测试 ⑥:永不回包适配器 + 注入 50ms 帽
  → 放行且请求确已发出;
- SnackBar 不堆积:呈现前 clearSnackBars——快速点多个重复资产
  只留当前一条(原会排队 N 条 4s 提示)。测试 ① 扩展:第二条
  隐藏后无排队等待的提示条;
- fileSize 失败路径钉住(测试 ⑦):资产事实 fileSize 抛错 → 放行
  且两半场零查询。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 10:35:04 +08:00
浅念 7e5c550fb3 feat(app): block selecting already-uploaded photos at picker tap (double-tap to force)
方案 A(选图点选拦截):multiImagePickerProvider 经 wechat_assets_picker
10.1.3 官方钩子 AssetPickerConfig.selectPredicate 挂 PickerDuplicateGate——
点选「已上传到目标相册」的照片当场拦下(SnackBar 提示),再次点击同一张
强制选择(escape hatch)。

- MultiImagePicker typedef 增参 accountId/albumId(拦截判定范围=当前
  目标相册;相册详情/连拍页两处调用点传入);
- 判定口径同 checkDuplicatesUnified:本机 TaskStore(同账号+相册+
  completed,filename+bytes)∪ 服务器单项预检(bytes;本机命中短路
  服务器,点选延迟考量);任一半场异常按该半场无命中,fileSize 失败等
  整体异常直接放行——enqueue 弹窗 + init 409 兜底不变;
- per-asset 判定缓存(二次点击瞬回)+ 二次点击放行集;取消选择恒放行
  且不预热放行集;
- AssetEntity 事实适配(PickerAssetFact 测试接缝):fileSize 为
  photo_manager 3.12 的 Future<int> 平台通道 getter(entity.dart:778),
  惰性求值;title 为 String?(Android 恒有值;iOS 需 needTitle——null
  时本机半场跳过,服务器按 bytes 仍可判);
- l10n pickerDuplicateBlocked(zh/en);测试替身签名同步
  (album_detail/capture_page 页测 + integration_test app_boot),
  页测补 (accountId, albumId) 接线断言;gate 五态用例入 enqueue_test。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 10:10:49 +08:00
浅念 ac37794d43 fix(app): path-precise duplicate matching; shared file stat; union test
Review follow-ups on 0fa23e0 (Task 2), applied as one commit:

- Path-precise dupPaths (#2, user-visible): DuplicateMatch gains
  sourcePath (local match fills it from the matched pick; unified
  server branch from picks[hit.index].path). Both pages' duplicate
  dialogs derive dupPaths from duplicates' sourcePath instead of
  basename matching, so same-basename siblings (different dir/bytes)
  are no longer mis-flagged — "跳过重复" no longer drops the
  non-duplicate sibling. Stale basename-derivation comment removed.
- Single shared stat (#3): files are stat'd once in
  checkDuplicatesUnified (_statPicks) and the picks feed both the
  local half (extracted _matchLocalHistory; public checkDuplicates
  folded — it had no remaining callers) and the server request.
  Halves stat IO for large batches (was ~2x).
- Union test (#1): new test/features/uploads/enqueue_test.dart pins
  that a file hit by BOTH local and server history yields ONE
  DuplicateMatch with the local uploadedAt (server's echoed time does
  not override). Merge dedup is now keyed by sourcePath
  (putIfAbsent, local first).

Tests: +1 enqueue union test (real-async chain inside one runAsync
window; sync file/dir setup — fake-async body must not await real
IO), +1 album_detail same-basename sibling test (skip-duplicates
enqueues exactly the non-hit sibling, distinguished by bytes since
sourceFilePath becomes the preparer's staging copy).

Gates: flutter analyze clean; full flutter test 429/429 green.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 20:22:37 +08:00