Co-Authored-By: Claude Code <noreply@anthropic.com>
3.9 KiB
选图时已上传照片「点选拦截」实施计划(方案 A)
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: 相册选图器里点选「已上传到目标相册」的照片时当场拦下并提示;再次点击同一张可强制选择(保留活口)。判定口径与现有查重一致(本机历史 ∪ 服务器预检,当前相册,bytes 为主)。
Architecture: 用 wechat_assets_picker 10.1.3 的官方钩子 AssetPickerConfig.selectPredicate(源码确认:每次点选/取消时调用,FutureOr<bool>,返回 false 静默不选)。拦截器持有 per-asset 结果缓存 + 二次点击放行集;服务器/本地查询失败一律放行(enqueue 弹窗 + init 409 兜底不变)。不做视觉置灰(那是方案 B,另行评估)。
已确认决策(2026-09-29): 方案 A;保留强制口子(提示「已上传过——再点一次可强制选择」,二次点击放行);判定范围=当前目标相册。
设计要点:
MultiImagePickertypedef 增参:(BuildContext context, {required String accountId, required String albumId})(拦截判定需要账号+相册;两处调用点都已持有)。albumId/accountId 缺省场景不存在——两个调用点必传。- 拦截器
_PickerDuplicateGate(enqueue.dart 私有类):decide(ctx, asset, selected):取消选择(selected=true)恒放行;已缓存判定直接用;- 判定 = 本机 TaskStore(同账号+相册+completed,filename+bytes)∪ 服务器
checkUploadDuplicates(单项,filename=asset.title,bytes=asset 文件大小);两路任一命中=重复; - 查询异常(网络/存储)→ 放行(不能因预检故障挡住正常选图);
- 重复 → 首次 false + SnackBar 提示(用谓词收到的 ctx,ScaffoldMessenger);该 asset.id 记入放行集 → 二次点击 true;
- per-asset 判定缓存(Map<assetId, bool>):避免重复查询同一张。
AssetEntity文件大小 API 以 photo_manager 3.12 源码为准(实现前先查清是length还是异步方法);asset.title可能为 null → 本机跳过(服务器按 bytes 仍可判)。- l10n:
pickerDuplicateBlocked(zh「这张已上传到该相册——再点一次可强制选择」/ en 对应)。 - 测试替身同步改签名:album_detail_page_test / capture_page_test / integration_test helpers/app_boot.dart。
Task 1: 点选拦截器 + 双页接线(TDD,单任务单提交)
Files:
-
Modify:
apps/mobile/lib/features/uploads/enqueue.dart(typedef + gate + provider) -
Modify:
apps/mobile/lib/pages/album_detail_page.dart、capture_page.dart(传 accountId/albumId) -
Modify:
apps/mobile/lib/l10n/app_zh.arb、app_en.arb(+pickerDuplicateBlocked) -
Test:
apps/mobile/test/features/uploads/enqueue_test.dart(gate 用例)、test/pages/{album_detail_page,capture_page}_test.dart、integration_test/helpers/app_boot.dart(替身签名) -
Step 1: 失败测试(gate 五态:本机命中首次拒/二次放行;服务器单项命中拒;两路空放行;服务器异常放行;取消选择恒放行——用 ProviderScope override 注入 fake taskStore/apiClient)
-
Step 2: 确认失败 → 实现(先查 photo_manager AssetEntity 文件大小 API 与 title 语义)
-
Step 3: 全绿(flutter analyze + 全量 test)
-
Step 4: Commit
feat(app): block selecting already-uploaded photos at picker tap (double-tap to force)
风险与回退: 谓词异步查询在点选瞬间有 ~100-300ms 延迟(有缓存后二次即瞬回);查询失败放行=最坏回到现状(弹窗+409 兜底);revert 单 commit 即回退。置灰视觉(方案 B)不在本次。