docs(plan): picker tap-time duplicate gate (option A) — incremental plan
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
# 选图时已上传照片「点选拦截」实施计划(方案 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)不在本次。
|
||||
Reference in New Issue
Block a user