Files
photo-live/docs/plans/2026-09-29-选图时已传照片点选拦截-实施计划.md
T
2026-09-29 09:51:41 +08:00

37 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 选图时已上传照片「点选拦截」实施计划(方案 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;保留强制口子(提示「已上传过——再点一次可强制选择」,二次点击放行);判定范围=当前目标相册。
**设计要点:**
- `MultiImagePicker` typedef 增参:`(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)不在本次。