# 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`。