# K2 客户端第二批:分类 + 观看记录 + 收藏列表 + 下载授权管理 > 依据:`docs/plans/INBOX.md` 2026-10-17 `[待处理] K2` 条目。 > 客户端基线:`ReelShort` `develop` / `00280ca`。 > 接口唯一权威:`FruitServer/docs/api-phase1.md` §0、§1.3、§1.5、§1.6。 > 本批只做 K2,完成后停下写回执,不连做 K3/K4。 ## 一、范围与完成口径 ### K2-A 分类 - 接 `GET /app/categories`,分类 id 全程按字符串处理。 - 发现页分类条改为服务端分类;保留现有分类条样式。 - 分类内容只从 `GET /app/discover/feed` 的 `sections[]` 中读取 `code == "category-{id}"` 的分区,不在客户端拼装分类请求。 - 分类在发现 feed 中没有对应分区时显示空态。 - 保留 `hot` / `new` 入口与既有样式;其内容同样只按发现 feed 中对应 `code` 分区读取,缺失时显示空态。 ### K2-B 观看记录页 - 接 `GET /app/watch/recent?limit=20`、`DELETE /app/watch/progress/{dramaId}`、 `DELETE /app/watch/progress`。 - 最近记录先去重 `dramaId`,再按 id 补 `GET /app/dramas/{id}` 卡片数据。 - 详情补取并发上限 4,同页按 `dramaId` 只请求一次。 - 单条详情失败只影响该条:显示“加载失败”占位与单项重试,其余记录正常渲染。 - 独立“观看记录”页:封面、剧名、集号、进度、更新时间;点击详情并续看; 单条删除、清空均有二次确认;删除采用乐观更新,失败回滚并提示。 - 加载 / 空 / 失败可重试三态;未登录使用现有登录引导。 ### K2-C 收藏列表页 - 复用 `GET /app/favorites`、`DELETE /app/favorites/{dramaId}`、 `POST /app/favorites/toggle`,不增加分页参数。 - 独立“我的收藏”页,复用卡片数据模型与列表卡片视觉;点击进详情。 - 取消收藏采用乐观更新,失败回滚并提示。 - 加载 / 空 / 失败可重试三态;未登录使用现有登录引导。 ### K2-D 下载授权管理 - 接契约 §1.6 全部 8 个接口: `GET /app/downloads/policy`、`PUT /app/downloads/settings`、 `POST /app/downloads`、`GET /app/downloads`、 `GET /app/downloads/{episodeId}/url`、`POST /app/downloads/{episodeId}/progress`、 `DELETE /app/downloads/{episodeId}`、`DELETE /app/downloads`。 - 详情页与播放页增加下载入口;后端拒绝时直接展示后端 `message`。 - 独立“我的下载”页:策略卡、授权列表、单条删除、清空、仅 Wi-Fi、最高清晰度。 - 授权状态与任务状态完全使用后端字符串,客户端不新增状态枚举、不自行判断付费/配额。 - 加载 / 空 / 失败可重试三态;未登录使用现有登录引导。 - 明确不做:文件分片落盘、断点续传、离线播放、`licenseKey` 本地校验。 `url` 与 `progress` 本批只提供并测试 API 方法,不驱动实际下载任务。 ## 二、实施顺序与 TDD 1. 基线:从 `ReelShort/develop` 建 `feature/flutter-k2-library`。 2. K2-A:先写分类解析、`category-{id}` 映射、无分区空态和筛选状态测试, 确认红灯后实现;再跑该块门禁。 3. K2-B:先写控制器测试覆盖详情去重、并发上限 4、单条失败降级、 删除/清空乐观更新与回滚;确认红灯后实现 API、repository、controller、页面; 再跑门禁。 4. K2-C:先写独立收藏页取消收藏失败回滚、三态与登录引导测试, 确认红灯后实现页面与导航;再跑门禁。 5. K2-D:先写 DTO 字符串 ID/时间解析、策略/列表渲染、授权失败透传、 设置更新、删除/清空和未登录测试;确认红灯后实现; `ApiClient` 仅补契约所需的 `PUT` 能力;再跑门禁。 6. 三份 ARB 一次补齐并生成 l10n;全量跑 `dart analyze lib test`、`flutter test`、`flutter build apk --debug`。 7. 门禁全绿后合并回 `develop` 并 push;不动 `main`。 8. 在 `PROGRESS.md` 与 INBOX K2 条目追加回执;真机验证交用户执行。 ## 三、预计影响文件 ### 数据层 - 修改:`lib/core/data/remote/api_client.dart`(补 `put`) - 修改:`lib/core/data/models/watch_progress.dart`(补 `updatedAt`) - 修改:`lib/core/data/remote/watch_api.dart`(补清空) - 修改:`lib/core/data/repositories/watch_history_repository.dart`(补清空) - 新增:`lib/core/data/models/content_category.dart` - 新增:`lib/core/data/remote/category_api.dart` - 新增:`lib/core/data/repositories/category_repository.dart` - 新增:`lib/core/data/services/category_controller.dart` - 新增:`lib/core/data/models/watch_record.dart` - 新增:`lib/core/data/services/watch_records_controller.dart` - 新增:`lib/core/data/models/download_policy.dart` - 新增:`lib/core/data/models/download_license.dart` - 新增:`lib/core/data/remote/download_api.dart` - 新增:`lib/core/data/repositories/download_repository.dart` - 新增:`lib/core/data/services/download_controller.dart` ### 页面与共享 UI - 修改:`lib/app.dart`(新仓库/控制器注入) - 修改:`lib/core/widgets/auth_gate.dart`(页面级登录引导复用) - 修改:`lib/pages/discover/discover_logic.dart` - 修改:`lib/pages/discover/discover_view.dart` - 修改:`lib/pages/home/home_view.dart`(我的页入口接线) - 修改:`lib/pages/mine/mine_view.dart`(观看记录、收藏、下载入口) - 修改:`lib/pages/drama_detail/drama_detail_view.dart`(下载入口) - 修改:`lib/pages/player/player_view.dart`(当前集下载入口) - 新增:`lib/pages/watch_records/watch_records_binding.dart` - 新增:`lib/pages/watch_records/watch_records_view.dart` - 新增:`lib/pages/favorites/favorites_binding.dart` - 新增:`lib/pages/favorites/favorites_view.dart` - 新增:`lib/pages/downloads/downloads_binding.dart` - 新增:`lib/pages/downloads/downloads_view.dart` - 视复用需要新增列表卡片/状态组件,预计位于 `lib/core/widgets/` 或各页面 `widgets/`。 ### 文案 - 修改:`lib/l10n/app_en.arb` - 修改:`lib/l10n/app_zh.arb` - 修改:`lib/l10n/app_id.arb` - 重新生成:`lib/l10n/app_localizations*.dart` ### 测试 - 修改:`test/catalog/catalog_controller_test.dart` 及分类筛选相关测试 - 修改:`test/discover/discover_logic_test.dart` - 修改:`test/mine/mine_logic_test.dart` - 修改:`test/library/favorite_controller_test.dart` - 新增:分类 API/控制器与 section 映射测试 - 新增:观看记录 API、并发加载、单项降级、删除/清空回滚和页面三态测试 - 新增:收藏独立页、取消收藏回滚、三态和登录引导测试 - 新增:下载 DTO/API/controller/page 测试 - 新增:未登录 `AuthGate` 页面测试 ## 四、契约缺口(本批登记,不改契约/后端) 1. `GET /app/watch/recent` 未返回剧名与封面,客户端只能先取最近记录, 再对去重后的 `dramaId` 调详情补齐卡片;代价是 N+1 请求。 建议后续在 `ProgressRespVO` 补卡片字段(需 specs 负责人改契约)。 2. `GET /app/favorites` 没有分页参数并返回全量数组,与 `TC-RECORD-04` 的“分页正确”不一致。本批按全量列表实现, 不自造 `page/size`。 3. `api-phase1.md` 没有搜索接口,本批不做搜索,也不以本地模糊匹配冒充搜索接口。 ## 五、风险与控制 1. 记录页 N+1 详情请求可能放大流量和延迟:严格去重、并发上限 4、页内缓存, 单项失败降级,不在失败后自动无限重试。 2. 页面销毁时异步详情仍可能返回:controller 的 `notify` 做 disposed 保护, 不把迟到结果写回已退出页面。 3. `updatedAt` 为后端 `LocalDateTime` 字符串,解析失败只能显示占位, 不能让整条记录解析失败。 4. 下载契约里授权与任务状态均为字符串,客户端只原样展示/映射现有文案, 不根据未知值猜测业务状态。 5. 下载地址字段 `playUrl` 后端当前未填充,本批不做实际下载;不能据此宣称 “可离线播放”。 6. 本机可能缺少 Android SDK,`flutter build apk --debug` 若失败将保留原始输出, 回执标记“未验证”,不静默跳过。 7. 真机/模拟器可达性由用户验证;开发机 `curl` 不作为设备证据。 ## 六、待确认(不阻塞本批) 1. 发现页 `hot` / `new` 入口保留,但当前后端 `discoverFeed()` 只生成 `category-{id}` 分区。若后续要展示热门/最新内容,需要后端在发现 feed 下发 对应分区;在此之前按契约显示空态。 2. 收藏分页与观看记录卡片字段待契约负责人后续补足;本批按 INBOX 降级方案执行。 3. 下载真实落盘、断点续传与离线播放需要存储、权限、加密播放方案,另立批次。 ## 七、完成定义 - 四块均有“红 → 绿”测试证据。 - `dart analyze lib test` 零告警,`flutter test` 全过。 - `flutter build apk --debug` 通过,或保留缺 SDK 的原始失败输出并标记未验证。 - 三语言 ARB 齐全且生成物已更新。 - 独立记录、收藏、下载页和分类条均接契约接口;未登录入口可完成登录引导。 - 不改 `main`、不改 specs、不改 FruitServer 后端、不做搜索。 - `PROGRESS.md` 与 INBOX K2 回执包含验证步骤、预期结果、提交号与原始门禁输出。