diff --git a/docs/plans/2026-10-21-K94-home-type-feed.md b/docs/plans/2026-10-21-K94-home-type-feed.md new file mode 100644 index 0000000..d63aa97 --- /dev/null +++ b/docs/plans/2026-10-21-K94-home-type-feed.md @@ -0,0 +1,126 @@ +# K94 首页内容类型 Feed 与无感切换 + +## 目标 + +- 首页顶栏「漫剧 / 真人剧 / 推荐」接入既有 `/app/feed`。 +- 每频道独立缓存与分页,切换时先同步展示缓存,再后台静默刷新。 +- 推荐首屏复用 `/app/home/feed` 打平数据,首次进入不额外转圈。 +- 无缓存频道只在黑色内容区显示小转圈,顶栏和底栏保持。 +- 滑动接近末尾自动加载下一页,并在播放器队列中原地追加。 + +## 分支 + +- ReelShort:`feat/k94-home-type-feed`,从 `origin/develop` 创建。 +- FruitServer 仅文档留痕,不改功能代码。 + +## A. 数据层 + +### A1 FeedPage + +新增 `lib/core/data/models/feed_page.dart`: + +- `dramas` 使用 `Drama.fromCardJson`。 +- `nextCursor` 兼容数字/字符串,空值或字符串 `"null"` 转为 null。 +- `hasMore` 读取布尔值。 + +### A2 CatalogApi / Repository + +- `CatalogApi.feed({types, cursor, size})` 请求 `/app/feed`。 +- types 为空不传;非空用逗号拼接。 +- cursor 为空不传。 +- 返回结构异常抛 `ApiException(-1, '返回数据格式异常')`。 +- `CatalogRepository` 与 HTTP 实现增加 `fetchFeed`。 +- 所有测试 CatalogRepository 实现同步补齐方法。 + +## B. 频道控制器 + +新增: + +- `lib/pages/home/home_feed_channel.dart` +- `lib/core/data/services/home_feed_controller.dart` + +`HomeFeedController` 使用每个频道独立状态: + +- dramas / cursor / hasMore / loaded / loadingMore / requestSeq +- 当前 channel,默认 recommend +- `seedRecommend`:同步写入推荐首屏,不发请求 +- `selectChannel`:同步切频道并 notify;无缓存加载第一页,有缓存静默刷新 +- `refreshCurrent`:失败保留旧列表 +- `loadMore`:cursor 追加,按 drama.id 去重 +- 所有响应必须同时满足“仍为当前频道 + requestSeq 最新”才应用 +- dispose 后停止 notify + +## C. 首页接入 + +### C1 顶栏受控 + +`HomeFeedHeader`: + +- 删除内部 tab 状态、comingSoon 和 SnackBar。 +- 新增 `channel` 与 `onChannelSelected`。 +- 三个 Key 保持不变。 +- refresh 内部 loading 状态保留。 + +### C2 HomePage + +- `_HomePageState` 用 `CatalogController.repository` 创建 HomeFeedController。 +- Catalog 首次有数据时同步 seed recommend,随后在 post-frame 静默刷新 recommend。 +- `_HomeTabs` 使用稳定 Key,删除 `ValueKey(catalog.dramas.first.id)`。 +- 当前频道无数据时,保留 `_HomeTabs` 顶栏/底栏,body 用黑色背景 + 居中 + `home-feed-channel-loading`。 +- 当前频道有数据时把频道列表传给 PlayerPage。 +- 刷新回调改 `refreshCurrent`;分页回调改 `loadMore`。 + +### C3 PlayerPage / PlayerLogic + +- PlayerPage 增加 `feedChannel` 和 `onLoadMore`。 +- 同频道 feed 列表变化:`updateFeed(..., keepDramaId: currentDramaId)`。 +- channel 变化:`updateFeed(..., keepDramaId: null)`,回到新频道第 1 个。 +- PlayerLogic 保存 factory/coordinator,支持需要换内容时替换 PlaybackSession。 +- 空列表清空 queue/visibleIndices 并停止旧播放。 +- 当前剧仍在新列表且索引不变时,只扩展队列与 episode 列表,不重新 select。 +- PageView 在 `position >= visibleIndices.length - 3` 时调用 onLoadMore。 + +## D. 测试 + +新增 `test/core/data/services/home_feed_controller_test.dart`: + +- recommendation seed 不发请求 +- 三频道请求 types 分别为 `[] / [ANIME] / [LIVE]` +- 缓存频道切换不清空并后台刷新 +- loadMore 追加去重、无更多后不再请求 +- 串包只接受最新频道请求 +- refresh 失败保留旧数据 + +新增顶栏 widget 测试: + +- 三个 Key 存在 +- 点击分别回调 anime/live/recommend +- 当前 channel 决定选中样式 + +PlayerLogic 测试补充: + +- updateFeed 空列表安全 +- 追加列表保持当前剧 +- 切频道列表回到第 1 个 + +门禁: + +- `dart analyze lib test` +- `flutter test --no-pub` +- `flutter build apk --debug` + +## 风险与控制 + +1. PlaybackSession 的 episodes 是构造时绑定的列表;分页原地追加时会同步扩展该列表, + 避免仅换 queue 而 session 仍指向旧列表。 +2. 换频道可能释放并重建播放器 session,这是内容源变化;静默刷新/分页在顺序不变时 + 不重新 select 当前剧,避免视频重启。 +3. 推荐 seed 只保证首帧立即有内容;后台 `/app/feed` 刷新失败时保留 seed。 +4. 首页仍在 `/app/home/feed` 首次加载期间使用既有启动页;频道切换不再触发整页 splash。 + +## Git + +- 提交:`feat(home): 首页按内容类型分频道 Feed 并支持分页` +- `--no-ff` 合回 ReelShort `develop` 并 push。 +- 作者:`Zhou Weidong <502333560@qq.com>`;禁止 `git add -A`。 diff --git a/docs/plans/PROGRESS.md b/docs/plans/PROGRESS.md index 77ba9d4..6ce421a 100644 --- a/docs/plans/PROGRESS.md +++ b/docs/plans/PROGRESS.md @@ -24064,3 +24064,53 @@ Build successful. Please see dist-dev directory - ReelShortWeb Husky 与当前 npm/pnpm 环境不兼容,功能与合并提交使用 `--no-verify`, 门禁已独立完成。 - 未改 FruitServer 代码,未安装或操作真机,等待用户验收。 + +## 2026-10-21 K94 首页内容类型 Feed 与分页 + +- 已完整读取 K94 `docs/plans/INBOX.md`;本批次只改 ReelShort Flutter 客户端。 +- 已创建 ReelShort `feat/k94-home-type-feed` worktree,基于最新 `origin/develop`。 +- 已先写计划 `docs/plans/2026-10-21-K94-home-type-feed.md`,记录数据层、频道缓存、 + 首页无感切换、播放器队列更新、分页触发和测试矩阵。 +- 不改后端、ReelShortWeb、specs;不安装或操作真机。 +- 已完成数据层:新增 `FeedPage`,`CatalogApi.feed` 与 `CatalogRepository.fetchFeed` + 支持 types/cursor/size,并补齐所有测试仓库实现。 +- 已完成 `HomeFeedChannel` 与 `HomeFeedController`:推荐 seed、三频道独立缓存、 + 静默刷新、游标追加去重、requestSeq 防串包、失败保留旧数据。 +- 首页顶栏改为受控频道,删除 comingSoon;无缓存频道在黑色内容区显示小转圈, + 推荐首屏立即使用 `/app/home/feed` 打平数据。 +- `PlayerLogic.updateFeed` 支持原地扩队列与当前剧索引重映射;同频道刷新不重启当前视频, + 频道变化回到第 1 个;PageView 倒数第 3 个触发 loadMore。 +- 已补 Feed API、控制器、顶栏和 PlayerLogic 测试,并更新既有 CatalogRepository 测试实现。 + +门禁原始输出: + +```text +$ dart analyze lib test +Analyzing lib, test... +No issues found! + +$ flutter test --no-pub +00:53 +532: All tests passed! + +$ flutter build apk --debug +Running Gradle task 'assembleDebug'... 27.9s +✓ Built build/app/outputs/flutter-apk/app-debug.apk +``` + +解读:静态分析零告警,532 项测试全过,debug APK 构建成功;未安装或操作真机。 + +### K94 交付结果 + +- ReelShort 功能提交:`ae61d9e feat(home): 首页按内容类型分频道 Feed 并支持分页`。 +- ReelShort develop 合并提交:`c8392b2 merge: 合并 K94 首页类型 Feed`, + 已推送 `4d0be47..c8392b2`。 +- 推荐首屏使用 home feed seed,并在后台静默刷新 `/app/feed`;漫剧/真人剧首次请求分别带 + `ANIME/LIVE`,推荐不带 types,均使用游标分页。 +- 三频道独立缓存、同步切换、requestSeq 防串包、分页去重和失败保留旧数据已实现。 +- 顶栏删除 comingSoon 并改为受控频道;无缓存频道保留顶栏/底栏,在黑色内容区显示 + `home-feed-channel-loading` 小转圈。 +- PlayerLogic 原地更新 queue/visibleIndices;当前剧仍存在时重映射 active slot, + 不重启当前 player;频道变化回到第 1 个。 +- 门禁:`dart analyze lib test` 零告警;`flutter test --no-pub` 532 项全过; + `flutter build apk --debug` 成功生成 `build/app/outputs/flutter-apk/app-debug.apk`。 +- 未改后端、ReelShortWeb、specs;未安装或操作真机,等待用户验收。