940 lines
37 KiB
Markdown
940 lines
37 KiB
Markdown
# 开发进度
|
||
|
||
## 2026-10-21 K96 剧场页服务端分类分页
|
||
|
||
- 已完整读取 K96 `docs/plans/INBOX.md`。
|
||
- 已从最新 `origin/develop` 创建 ReelShort `feat/k96-theater-feed`。
|
||
- 计划见 FruitServer `docs/plans/2026-10-21-K96-theater-feed.md`。
|
||
- 客户端将新增 TheaterFeedController,并让剧场「全部 / 分类」改用
|
||
`/app/feed` 服务端游标分页;不改首页 K94/K95 已交付逻辑。
|
||
- 已完成 `CatalogApi.feed` / `CatalogRepository.fetchFeed` 可选 `categoryId`,
|
||
非空才拼请求参数。
|
||
- 已新增 `TheaterFeedController`:按分类独立缓存,缓存先渲染后静默刷新,
|
||
10 秒超时、游标追加去重、失败保留旧数据、失败分类切回自动重试。
|
||
- 剧场页「全部」和分类 chips 已改用 theater feed;滚动剩余不足 400px 触发
|
||
`loadMore`,下拉刷新同时执行 categories 与 theater 刷新,搜索模式保持独立。
|
||
- 已补分类参数、缓存切换、分页去重、hasMore 停止、失败保留、失败重试、
|
||
分类点击请求和滚动加载 widget 测试。
|
||
|
||
门禁原始输出:
|
||
|
||
```text
|
||
$ dart analyze lib test
|
||
Analyzing lib, test...
|
||
No issues found!
|
||
|
||
$ flutter test --no-pub
|
||
00:52 +553: All tests passed!
|
||
|
||
$ flutter build apk --debug
|
||
Running Gradle task 'assembleDebug'... 24.8s
|
||
✓ Built build/app/outputs/flutter-apk/app-debug.apk
|
||
```
|
||
|
||
解读:静态分析零告警,553 项全量测试通过,debug APK 构建成功;
|
||
`pubspec.lock` 已还原,无依赖变更,未安装或操作真机。
|
||
|
||
## 2026-10-21 K97 修复 iOS 评论输入弹窗键盘避让
|
||
|
||
- 已完整读取 K97 `docs/plans/INBOX.md`。
|
||
- 已从最新 `origin/develop` 创建 `fix/k97-ios-comment-keyboard`。
|
||
- 计划见 FruitServer `docs/plans/2026-10-21-K97-ios-comment-keyboard.md`。
|
||
- Android 保留既有 pinned/channel 策略;iOS 与其他平台改用实时
|
||
`MediaQuery.viewInsets.bottom`,并补 iOS 键盘避让测试。
|
||
- 已完成 `_isAndroid` 平台分流;Android 的 channel、80ms 防抖和 pinned 逻辑保持不变,
|
||
iOS/其他平台直接读取实时 viewInsets,超过屏幕 60% 的异常值按 0 处理。
|
||
- 面板已使用 160ms `easeOutCubic` AnimatedPadding;Android 不包裹 SafeArea,
|
||
iOS/其他平台使用 `SafeArea(top: false)` 处理 home indicator。
|
||
- 已补 iOS insets=300/0 测试,发表按钮在键盘上方;Android pinned 既有用例全部通过。
|
||
|
||
门禁原始输出:
|
||
|
||
```text
|
||
$ dart analyze lib test
|
||
Analyzing lib, test...
|
||
No issues found!
|
||
|
||
$ flutter test --no-pub
|
||
00:54 +555: All tests passed!
|
||
|
||
$ flutter build apk --debug
|
||
Running Gradle task 'assembleDebug'... 26.2s
|
||
✓ Built build/app/outputs/flutter-apk/app-debug.apk
|
||
|
||
$ flutter build ios --simulator --no-codesign
|
||
Xcode build done. 44.0s
|
||
✓ Built build/ios/iphonesimulator/Runner.app
|
||
```
|
||
|
||
解读:静态分析零告警,555 项全量测试通过,Android 与 iOS simulator 构建成功;
|
||
`pubspec.lock` 已还原,无依赖变更,未安装或操作真机。
|
||
|
||
## 2026-10-21 K97.1 剧场搜索框圆角改小
|
||
|
||
- 已从最新 `origin/develop` 创建 `fix/k97.1-theater-search-radius`。
|
||
- 仅将剧场搜索框圆角从 22 调整为 12,高度 44、颜色、聚焦态及
|
||
清空/识剧逻辑保持不变,并同步更新对应 widget 断言。
|
||
|
||
门禁原始输出:
|
||
|
||
```text
|
||
$ dart analyze lib test
|
||
Analyzing lib, test...
|
||
No issues found!
|
||
|
||
$ flutter test --no-pub
|
||
00:55 +555: All tests passed!
|
||
|
||
$ flutter build apk --debug
|
||
Running Gradle task 'assembleDebug'... 25.8s
|
||
✓ Built build/app/outputs/flutter-apk/app-debug.apk
|
||
```
|
||
|
||
解读:静态分析零告警,555 项全量测试通过,debug APK 构建成功;
|
||
`pubspec.lock` 已还原,无依赖变更,未安装或操作真机。
|
||
|
||
## 2026-10-21 K98 修复登录与手机号登录键盘遮挡
|
||
|
||
- 已完整读取 K98 `docs/plans/INBOX.md`。
|
||
- 已从最新 `origin/develop` 创建 `fix/k98-auth-keyboard`。
|
||
- 计划见 FruitServer `docs/plans/2026-10-21-K98-auth-keyboard.md`。
|
||
- 将统一 viewInsets/channel 有效键盘高度,并给登录页与手机号弹窗增加
|
||
焦点/键盘变化后的字段滚动校正。
|
||
- 已新增 `effectiveKeyboardInset`,兼容 iOS 实时 viewInsets 与 Android
|
||
`adjustNothing` 下的 MethodChannel 键盘高度。
|
||
- 登录/注册页已增加 ScrollController、notifier 监听和邮箱/密码焦点校正;
|
||
手机号弹窗已同样接入手机号/验证码框滚动校正。
|
||
- 已补 iOS 登录/注册、Android channel、手机号验证码框键盘避让测试。
|
||
|
||
门禁原始输出:
|
||
|
||
```text
|
||
$ dart analyze lib test
|
||
Analyzing lib, test...
|
||
No issues found!
|
||
|
||
$ flutter test --no-pub
|
||
00:52 +559: All tests passed!
|
||
|
||
$ flutter build apk --debug
|
||
Running Gradle task 'assembleDebug'... 26.2s
|
||
✓ Built build/app/outputs/flutter-apk/app-debug.apk
|
||
```
|
||
|
||
解读:静态分析零告警,559 项全量测试通过,debug APK 构建成功;
|
||
`pubspec.lock` 已还原,无依赖变更,未安装或操作真机。
|
||
|
||
## 2026-10-21 K98.1 收藏与观看记录直接进入播放页
|
||
|
||
- 已从最新 `origin/develop` 创建 `fix/k98.1-play-from-lists`。
|
||
- 收藏卡片已改为调用 `playDramaFromCatalog`,不再进入详情页。
|
||
- 观看记录卡片已按 `progress.episodeId` 直达播放页对应集;episodeId 为空时从第 1 集起播。
|
||
- 详情未加载完成的观看记录仍保持原禁用、加载和重试行为。
|
||
- 已补收藏及观看记录直达 PlayerPage 的路由断言。
|
||
|
||
门禁原始输出:
|
||
|
||
```text
|
||
$ dart analyze lib test
|
||
Analyzing lib, test...
|
||
No issues found!
|
||
|
||
$ flutter test --no-pub
|
||
01:09 +561: All tests passed!
|
||
|
||
$ flutter build apk --debug
|
||
Running Gradle task 'assembleDebug'... 28.7s
|
||
✓ Built build/app/outputs/flutter-apk/app-debug.apk
|
||
```
|
||
|
||
解读:静态分析零告警,561 项全量测试通过,debug APK 构建成功;
|
||
`pubspec.lock` 已还原,无依赖变更,未安装或操作真机。
|
||
|
||
## 2026-10-21 K98.2 修复 feed 更新触发 build 期通知
|
||
|
||
- 已从最新 `origin/develop` 创建 `fix/k98.2-feed-build-notify`。
|
||
- PlayerPage 的 feed 更新改为帧后调度,同一帧多次变化继续由 PlayerLogic 合并。
|
||
- PlayerLogic 在非 idle 调度阶段延后 feed 通知与 PageController 跳页,并给延迟通知去重。
|
||
- HomeLogic.onDramaChanged 在 build/layout/paint 阶段改为帧后通知。
|
||
- 已补 build 阶段 updateFeed、最终 PageController 位置及 HomeLogic 延后通知回归测试。
|
||
|
||
门禁原始输出:
|
||
|
||
```text
|
||
$ dart analyze lib test
|
||
Analyzing lib, test...
|
||
No issues found!
|
||
|
||
$ flutter test --no-pub
|
||
00:55 +563: All tests passed!
|
||
|
||
$ flutter build apk --debug
|
||
Running Gradle task 'assembleDebug'... 26.4s
|
||
✓ Built build/app/outputs/flutter-apk/app-debug.apk
|
||
```
|
||
|
||
解读:静态分析零告警,563 项全量测试通过,debug APK 构建成功;
|
||
`pubspec.lock` 已还原,无依赖变更,未安装或操作真机。
|
||
|
||
## 2026-10-21 K98.3 修复频道切换复用已释放视频控制器
|
||
|
||
- 已从最新 `origin/develop` 创建 `fix/k98.3-disposed-video-controller`。
|
||
- `PlaybackSession.active` 与 `preparedPlayerAt` 不再暴露 closed/retiring slot;
|
||
`_retire` 先清空活动引用并通知,再等待界面帧边界后暂停和释放播放器。
|
||
- `PlayerLogic._replaceSession` 切换 session 后等待 `endOfFrame`,并再次校验
|
||
revision 后才关闭旧 session;其它 session 回收路径同样使用该帧边界。
|
||
- 已补 retiring player 脱钩单测,以及真实 `PlayerVideoSurface` 经 feed 切换、
|
||
连续 pump 后不出现 disposed controller 异常的回归测试。
|
||
|
||
门禁原始输出:
|
||
|
||
```text
|
||
$ dart analyze lib test
|
||
Analyzing lib, test...
|
||
No issues found!
|
||
|
||
$ flutter test --no-pub
|
||
00:58 +565: All tests passed!
|
||
|
||
$ flutter build apk --debug
|
||
Running Gradle task 'assembleDebug'... 35.7s
|
||
✓ Built build/app/outputs/flutter-apk/app-debug.apk
|
||
```
|
||
|
||
解读:静态分析零告警,565 项全量测试通过,debug APK 构建成功;
|
||
`pubspec.lock` 已还原,无依赖变更,未安装或操作真机。
|
||
|
||
## 2026-10-22 登录异常改为屏幕居中弹窗
|
||
|
||
- 已从 `origin/develop`(`9b78e43`)创建 `feature/login-error-center-dialog`。
|
||
- 新增 `lib/core/widgets/centered_error_dialog.dart`,统一居中样式、遮罩关闭、确认按钮。
|
||
- 邮箱登录/注册与手机号 OTP/验证码异常改为居中弹窗;字段校验保持内联,成功提示不变。
|
||
- 新增 `authLoginFailed` / `authRegisterFailed` / `authErrorConfirm` 三语言文案并重新生成 l10n。
|
||
- 异常弹窗前提交 busy 已复位,避免 loading 卡在弹窗打开期间。
|
||
|
||
执行命令与原始输出:
|
||
|
||
```text
|
||
$ flutter gen-l10n
|
||
Because l10n.yaml exists, the options defined there will be used instead.
|
||
To use the command line arguments, delete the l10n.yaml file in the Flutter project.
|
||
|
||
$ flutter test --no-pub test/auth/auth_page_test.dart test/auth/phone_auth_sheet_test.dart
|
||
00:02 +20: All tests passed!
|
||
|
||
$ dart analyze lib test
|
||
Analyzing lib, test...
|
||
No issues found!
|
||
|
||
$ flutter test --no-pub
|
||
00:49 +570: All tests passed!
|
||
```
|
||
|
||
解读:登录异常、注册标题、未知异常回退、本地校验不弹窗、遮罩/按钮关闭和手机号两条
|
||
异常路径均通过;全量静态分析零告警,570 项测试全过。`pubspec.lock` 已还原,
|
||
未启停服务,未操作真机。
|
||
|
||
## 2026-10-22 登录异常调整为中心吐司
|
||
|
||
- 已从 `develop`(`11e37a9`)创建 `feature/login-error-center-toast`。
|
||
- 删除 AlertDialog 组件,新增 OverlayEntry 居中吐司:淡入淡出、约 2 秒消失、不拦截手势。
|
||
- 吐司保持全局单例,新消息替换旧消息;邮箱与手机号异常路径均已切换。
|
||
- 本地表单校验继续内联,手机号验证码发送成功 SnackBar 保持不变。
|
||
- 清理 `authLoginFailed` / `authRegisterFailed` / `authErrorConfirm` 并重新生成 l10n。
|
||
|
||
执行命令与原始输出:
|
||
|
||
```text
|
||
$ flutter gen-l10n
|
||
Because l10n.yaml exists, the options defined there will be used instead.
|
||
To use the command line arguments, delete the l10n.yaml file in the Flutter project.
|
||
|
||
$ flutter test --no-pub test/core/centered_toast_test.dart
|
||
00:00 +3: All tests passed!
|
||
|
||
$ flutter test --no-pub test/auth/auth_page_test.dart test/auth/phone_auth_sheet_test.dart
|
||
00:02 +20: All tests passed!
|
||
|
||
$ dart analyze lib test
|
||
Analyzing lib, test...
|
||
No issues found!
|
||
|
||
$ flutter test --no-pub
|
||
01:00 +573: All tests passed!
|
||
```
|
||
|
||
解读:吐司单例替换、点击穿透和自动消失通过;认证异常与本地校验路径通过;
|
||
全量静态分析零告警,573 项测试全过。`pubspec.lock` 已还原,未启停服务,
|
||
未操作真机。
|
||
|
||
## 2026-10-22 播放器 loading 与锁定进度条调整
|
||
|
||
- 已从 `develop`(`7b068e8`)创建 `feature/player-loading-polish`。
|
||
- 播放器票据与 session loading 圈统一为 24×24、线宽 2。
|
||
- loading 高光默认改为白色,普通轨道色与首页 white24 轨道保持不变。
|
||
- 首页和全屏播放器进度条增加 locked 状态;锁定态显示静态 inactive 线,不进入 loading,
|
||
禁用拖动与 seek。
|
||
- 已补 loading 指示器尺寸、缓冲高光颜色、首页/全屏锁定态和锁定轨道高度测试。
|
||
|
||
执行命令与原始输出:
|
||
|
||
```text
|
||
$ flutter test --no-pub test/player/loading_progress_track_test.dart test/playback/seek_progress_bar_test.dart
|
||
00:00 +8: All tests passed!
|
||
|
||
$ flutter test --no-pub test/home/home_preview_layout_test.dart
|
||
00:01 +7: All tests passed!
|
||
|
||
$ flutter test --no-pub test/player/player_fullscreen_test.dart
|
||
00:09 +37: All tests passed!
|
||
|
||
$ dart analyze lib test
|
||
Analyzing lib, test...
|
||
No issues found!
|
||
|
||
$ flutter test --no-pub
|
||
00:50 +576: All tests passed!
|
||
```
|
||
|
||
解读:加载高光、锁定静态轨道、拖动禁用和两个 loading 指示器尺寸均通过;
|
||
全量静态分析零告警,576 项测试全过。`pubspec.lock` 已还原,未启停服务,
|
||
未操作真机。
|
||
|
||
## 2026-10-22 屏幕常亮与前台自动续播
|
||
|
||
- 已从 `develop`(`5bf1022`)创建 `feature/keep-awake-auto-resume`。
|
||
- Android 主 Activity 添加 `FLAG_KEEP_SCREEN_ON`,保留已有渠道、PiP 和键盘逻辑。
|
||
- iOS AppDelegate 开启 `isIdleTimerDisabled`,前台保持常亮。
|
||
- 用户主动暂停后回前台安全恢复;过期票据先完成重取再设置播放意图。
|
||
- locked、blocked、票据错误、会话错误、解锁/浮层遮挡和 PiP 场景不会强制续播。
|
||
- 后台仍立即保存进度并暂停,不做后台播放。
|
||
|
||
执行命令与原始输出:
|
||
|
||
```text
|
||
$ flutter test --no-pub test/player/player_logic_test.dart
|
||
00:00 +35: All tests passed!
|
||
|
||
$ dart analyze lib test
|
||
Analyzing lib, test...
|
||
No issues found!
|
||
|
||
$ flutter test --no-pub
|
||
00:50 +580: All tests passed!
|
||
```
|
||
|
||
解读:用户暂停恢复、过期票据重取、locked/ticketError/浮层阻断及后台保存暂停
|
||
均通过;全量静态分析零告警,580 项测试全过。`pubspec.lock` 已还原,
|
||
未启停服务,未操作真机。
|
||
|
||
## 2026-10-22 播放位置连续性
|
||
|
||
- 已从 `develop`(`fe49c6d`)创建 `feature/playback-position-continuity`。
|
||
- 新增 `PlaybackPositionCache`,按 episodeId 保存 dramaId、position、duration、updatedAt。
|
||
- App Provider 注入单一缓存实例,PlayerLogic 与详情页共享。
|
||
- 进度监听、保存进度与进入详情前更新缓存;loadResumePoint 优先缓存,未命中再回源。
|
||
- 详情页继续观看与 CTA 优先使用最近缓存集,返回首页保持当前播放器位置。
|
||
- 跨剧缓存不命中;片尾保护继续复用 `clampResumePosition`。
|
||
|
||
执行命令与原始输出:
|
||
|
||
```text
|
||
$ flutter test --no-pub test/playback/playback_position_cache_test.dart
|
||
00:00 +4: All tests passed!
|
||
|
||
$ flutter test --no-pub test/player/player_logic_test.dart
|
||
00:00 +38: All tests passed!
|
||
|
||
$ flutter test --no-pub test/player/player_open_detail_test.dart
|
||
00:00 +1: All tests passed!
|
||
|
||
$ dart analyze lib test
|
||
Analyzing lib, test...
|
||
No issues found!
|
||
|
||
$ flutter test --no-pub
|
||
00:53 +589: All tests passed!
|
||
```
|
||
|
||
解读:缓存读写/过期、跨剧隔离、缓存命中去网络、服务端回落、详情 CTA 缓存集
|
||
和详情往返同秒位置均通过;全量静态分析零告警,589 项测试全过。
|
||
`pubspec.lock` 已还原,未启停服务,未操作真机。
|
||
|
||
## 2026-10-22 Seek 后播放位置保持
|
||
|
||
- 已从 `develop`(`47ca214`)创建 `feature/playback-seek-position`。
|
||
- 新增播放位置 clamp;缓存、上报与详情返回统一使用 seek 完成后的 controller 位置。
|
||
- PlayerLogic 增加 pending seek 屏障,saveCurrentProgress 与离开路径会等待 seek 和写入完成。
|
||
- 监听器统一读取一次当前播放快照,避免旧位置覆盖新位置。
|
||
- 已补 seek 目标缓存、越界 clamp、seek 后立即进详情并返回的位置测试。
|
||
|
||
执行命令与原始输出:
|
||
|
||
```text
|
||
$ flutter test --no-pub test/playback/playback_position_cache_test.dart
|
||
00:00 +5: All tests passed!
|
||
|
||
$ flutter test --no-pub test/player/player_logic_test.dart --plain-name should_cache_actual_position_after_seek_completes
|
||
00:00 +1: All tests passed!
|
||
|
||
$ flutter test --no-pub test/player/player_open_detail_test.dart
|
||
00:00 +1: All tests passed!
|
||
|
||
$ dart analyze lib test
|
||
Analyzing lib, test...
|
||
No issues found!
|
||
|
||
$ flutter test --no-pub
|
||
00:49 +591: All tests passed!
|
||
```
|
||
|
||
解读:seek 完成后缓存与上报使用实际位置,越界目标被 clamp;seek 后立即
|
||
进详情并返回仍保持目标位置。全量静态分析零告警,591 项测试全过。
|
||
`pubspec.lock` 已还原,未启停服务,未操作真机。
|
||
|
||
## 2026-10-22 播放入口无缓冲提示
|
||
|
||
- 已从 Seek 修复合并后的 `develop`(`f302b39`)创建 `feature/playback-entry-no-buffer`。
|
||
- 未初始化进度条改为静态 inactive 轨道,不显示流动高光且不可拖动。
|
||
- 仅 initialized 且 isBuffering 持续超过 350ms 时显示白色缓冲高光。
|
||
- PlayerView 删除 session 居中 loading,只保留 ticket loading。
|
||
- preparePlayer 改为初始化完成后先 seek 缓存位置再 play;详情返回复用原 controller。
|
||
|
||
执行命令与原始输出:
|
||
|
||
```text
|
||
$ flutter test --no-pub test/playback/seek_progress_bar_test.dart
|
||
00:00 +7: All tests passed!
|
||
|
||
$ flutter test --no-pub test/home/home_preview_layout_test.dart
|
||
00:01 +8: All tests passed!
|
||
|
||
$ flutter test --no-pub test/player/player_logic_test.dart
|
||
00:00 +40: All tests passed!
|
||
|
||
$ flutter test --no-pub test/player/player_fullscreen_test.dart
|
||
00:09 +37: All tests passed!
|
||
|
||
$ dart analyze lib test
|
||
Analyzing lib, test...
|
||
No issues found!
|
||
|
||
$ flutter test --no-pub
|
||
00:49 +593: All tests passed!
|
||
```
|
||
|
||
解读:短缓冲不闪、持续缓冲仍显示白色高光;未初始化/入口/返回/重建路径
|
||
均无多余 loading,重建顺序为 initialize→seek→play。全量静态分析零告警,
|
||
593 项测试全过。`pubspec.lock` 已还原,未启停服务,未操作真机。
|
||
|
||
## 2026-10-22 播放器进度拖动画面预览
|
||
|
||
- 已从最新 `develop`(`d06dce9`)创建 `feature/scrub-frame-preview`。
|
||
- 计划文档:`docs/plans/2026-10-22-scrub-frame-preview.md`。
|
||
- 性能红线:拖动期间主线程无卡顿;原生并发取帧不超过 1;预览图最大宽
|
||
240px;Dart LRU 缓存不超过 24 帧;不新增 pub 依赖;不改后端契约。
|
||
- 实施顺序:缩略图服务与缓存 -> 预览卡片及两处进度条 -> Android/iOS
|
||
原生渠道 -> 单测/widget 测试与全量门禁。
|
||
|
||
```text
|
||
$ git status --short --branch
|
||
## feature/scrub-frame-preview
|
||
|
||
$ git log -5 --oneline --decorate
|
||
d06dce9 (HEAD -> feature/scrub-frame-preview, origin/develop, develop) merge: 合并播放入口无缓冲提示
|
||
```
|
||
|
||
解读:工作树从最新 develop 创建且开工前无未提交改动。当前仅新增计划与
|
||
PROGRESS 留痕,后续每完成一个阶段会补充命令原始输出与解读。
|
||
|
||
### 实现结果
|
||
|
||
- 新增 `PlaybackThumbnailService`:1 秒 bucket、24 帧 LRU、单飞合流、
|
||
最新 bucket 优先、停顿 120ms 后最多预取相邻 1 帧、1.5 秒统一回落。
|
||
- 新增 `ScrubPreviewCard`:120dp 宽、圆角 8、白色 1px 描边,显示
|
||
`当前时间 / 总时长`,安全边距内跟随拖动点;帧到达前始终显示封面。
|
||
- 首页 feed 与全屏播放器进度条均接入;松手立即隐藏并取消尚未开始的请求。
|
||
- Android 使用单线程 `MediaMetadataRetriever`,iOS 使用串行
|
||
`AVAssetImageGenerator`;两端优先本地路径、最大宽 240px、JPEG 约 70、
|
||
失败或 1.5 秒超时回调 null。
|
||
- 未新增 pub 依赖,未改后端契约;`pubspec.lock` 已还原,未启停服务,
|
||
未操作真机。
|
||
|
||
执行命令与原始输出:
|
||
|
||
```text
|
||
$ flutter test --no-pub test/playback/playback_thumbnail_service_test.dart test/playback/seek_progress_bar_test.dart test/home/home_preview_layout_test.dart
|
||
00:01 +28: All tests passed!
|
||
|
||
$ dart analyze lib test
|
||
Analyzing lib, test...
|
||
No issues found!
|
||
|
||
$ flutter test --no-pub
|
||
00:56 +606: All tests passed!
|
||
```
|
||
|
||
解读:bucket、24 帧 LRU、同 bucket 合流、过期请求丢弃、松手取消、
|
||
120ms 预取防覆盖、异常/超时回落、首页与全屏预览卡片、封面占位和松手立即
|
||
消失均通过;全量静态分析零告警,606 项测试全过。性能红线保持:主线程不做
|
||
图片解码/取帧、原生并发 1、预览最大宽 240px、缓存最多 24 帧、不增依赖、
|
||
不改后端。原生取帧真实画面与设备性能由 PM 真机验收。
|
||
|
||
## 2026-10-22 雪碧图 VTT 拖动预览
|
||
|
||
- 已从 `develop`(`89a94f8`)创建 `feature/sprite-scrub-preview`。
|
||
- 计划文档:`docs/plans/2026-10-22-sprite-scrub-preview.md`。
|
||
- 性能红线:拖动帧不发起网络/解码,只做 cue 二分与 sourceRect 绘制;
|
||
VTT 缓存 10 分钟且只解析一次;sprite ui.Image 最多 2 张;超过 2048px
|
||
按比例缩放;签名地址不进入持久化缓存。
|
||
- 回退链:雪碧图 -> 现有本地原生取帧 -> 集封面。
|
||
|
||
```text
|
||
$ git status --short --branch
|
||
## feature/sprite-scrub-preview
|
||
|
||
$ git rev-parse --short HEAD
|
||
89a94f8
|
||
```
|
||
|
||
解读:工作树从指定最新 develop 创建,开工前无未提交改动;主工作区现有
|
||
`pubspec.lock` 未复制、未修改。
|
||
|
||
### 实现结果
|
||
|
||
- `Episode` / `PlayTicket` 增加 nullable `thumbnailsUrl`;票据写回和详情来源都保留,
|
||
本地下载只覆盖视频地址,不丢 VTT;签名地址未进入持久化缓存。
|
||
- 新增 `SpriteThumbnailAxisService`:Dio VTT 拉取、10 分钟缓存、403 清缓存重试一次、
|
||
cue 解析与二分、sprite `ui.Image` LRU2、2048px 解码上限。
|
||
- `PlayerPage` 在选中集就绪时预取当前及相邻 sprite;dispose 释放 VTT 与图片。
|
||
- `ScrubPreviewCard` 优先用 `CustomPaint.drawImageRect` 绘制 `sourceRect`;
|
||
VTT 未就绪显示封面,轴为空/加载失败回退本地原生取帧,再失败保留封面。
|
||
- 拖动帧只做缓存查询、二分、矩形裁剪与 drawImageRect,不发起网络或图片解码。
|
||
|
||
执行命令与原始输出:
|
||
|
||
```text
|
||
$ flutter test --no-pub test/playback/sprite_thumbnail_axis_test.dart test/player/scrub_preview_card_test.dart
|
||
00:00 +11: All tests passed!
|
||
|
||
$ flutter test --no-pub test/playback/seek_progress_bar_test.dart test/home/home_preview_layout_test.dart test/player/player_fullscreen_test.dart
|
||
00:10 +55: All tests passed!
|
||
|
||
$ dart analyze lib test
|
||
Analyzing lib, test...
|
||
No issues found!
|
||
|
||
$ flutter test --no-pub
|
||
00:54 +617: All tests passed!
|
||
```
|
||
|
||
解读:VTT 时间/xywh/多 sprite、越界最近 cue、LRU2、403 重试、10 分钟 TTL、
|
||
回退链、sprite 零本地取帧、无 URL 本地回退和最终封面兜底均通过;全量静态分析
|
||
零告警,617 项测试全过。`pubspec.lock` 已还原,未启停服务,未操作真机。
|
||
|
||
## 2026-10-22 Android Studio 调试 HTTP 日志
|
||
|
||
- 分支:`feature/debug-http-logging`
|
||
- 基线:`develop` / `origin/develop` = `dc4b602`
|
||
- 计划:`docs/plans/2026-10-22-debug-http-logging.md`
|
||
|
||
```text
|
||
$ git status --short --branch
|
||
## develop...origin/develop
|
||
M pubspec.lock
|
||
|
||
$ git rev-parse HEAD
|
||
dc4b60270dd38d594510b45bd7a3d50b7dcd2602
|
||
```
|
||
|
||
解读:基于最新 develop 创建功能分支;开工时 `pubspec.lock` 已有未提交改动,
|
||
本次未修改、未暂存该文件。
|
||
|
||
### 实现结果
|
||
|
||
- 新增 `NetworkLogInterceptor`,默认 `debugPrint`,可注入
|
||
`void Function(String)` 作为单测日志收集器。
|
||
- `enabled` 常量与 `kDebugMode` 同时为真才输出,release/profile 零 HTTP 日志。
|
||
- 请求行包含完整 URL 与 query,同时输出耗时起点、脱敏请求头和格式化请求体。
|
||
- Authorization 仅保留前 8 字符后加 `...`;X-Device-Id 原样输出;
|
||
其余请求头值统一 `***`。
|
||
- ResponseType.bytes 响应统一 `utf8.decode(..., allowMalformed: true)`,
|
||
JSON 自动缩进;图片/音视频/字体/PDF/ZIP 等只输出 MIME 与字节数。
|
||
- 请求体和响应体超过 3000 字符时截断并追加 `…(truncated)`。
|
||
- 拦截链为 `AuthHeaderInterceptor → EnvelopeInterceptor → NetworkLogInterceptor`。
|
||
Envelope 在解包前暂存原始 bytes,末尾日志读取暂存值,因此仍能打印 Envelope
|
||
处理前的原始响应。
|
||
- 异常路径输出 `[HTTP] ✗ DioException类型 耗时ms URL message`,并继续
|
||
`handler.next(err)`,不吞异常。
|
||
|
||
代表性原始日志:
|
||
|
||
```text
|
||
[HTTP] → POST http://logging.test/app/logging?keyword=%E4%B8%AD%E6%96%87
|
||
[HTTP] → 耗时起点:2026-10-22T12:12:25.377357
|
||
[HTTP] → headers:{"Accept":"***","Authorization":"Bearer e...","X-Device-Id":"device-raw-001"}
|
||
[HTTP] → body:{
|
||
"filter": "中文"
|
||
}
|
||
[HTTP] ← 200 12ms POST http://logging.test/app/logging?keyword=%E4%B8%AD%E6%96%87
|
||
[HTTP] ← body:{
|
||
"code": 0,
|
||
"message": "ok",
|
||
"data": {
|
||
"title": "中文标题"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 门禁
|
||
|
||
```text
|
||
$ flutter test --no-pub test/core/data/remote/network_log_interceptor_test.dart
|
||
00:00 +0: should_log_request_and_response_lines_with_elapsed_when_requested
|
||
00:00 +1: should_truncate_long_body_when_log_exceeds_limit
|
||
00:00 +2: should_log_plain_text_without_throwing_when_response_is_not_json
|
||
00:00 +3: should_log_byte_count_only_when_response_is_image
|
||
00:00 +4: should_log_dio_exception_type_and_message_when_request_fails
|
||
00:00 +5: All tests passed!
|
||
|
||
$ dart analyze lib test
|
||
Analyzing lib, test...
|
||
No issues found!
|
||
|
||
$ flutter test --no-pub
|
||
00:58 +622: All tests passed!
|
||
```
|
||
|
||
解读:请求/响应行与耗时、Authorization 脱敏、X-Device-Id 原样、bytes 中文
|
||
UTF-8 解码、3000 字符截断、非 JSON、二进制字节数、DioException 类型与 message
|
||
均通过;全量静态分析零告警,622 项测试全过。
|
||
|
||
未新增依赖、未启停服务、未操作真机;`pubspec.lock` 既有改动未提交。
|
||
|
||
## 2026-10-22 收藏与观看历史分页 UI
|
||
|
||
- 分支:`feature/library-paging-ui`
|
||
- 基线:`develop` / `origin/develop` = `63d3b0f`
|
||
- 后端依赖:FruitServer `7b69493`(收藏/观看历史 PageResult 与展示字段)
|
||
- 计划:`docs/plans/2026-10-22-library-paging-ui.md`
|
||
|
||
### 实现
|
||
|
||
- 新增通用 `PageResult<T>`;`LibraryApi.favorites` 与 `WatchApi.recent`
|
||
统一请求 `pageIndex/pageSize` 并返回 `PageResult`。
|
||
- 收藏与观看仓储、Controller 增加首屏、刷新、loadMore、total、hasMore、
|
||
loadingMore 和 loadMoreError;请求代次避免旧刷新/旧分页覆盖新列表。
|
||
- 收藏按 dramaId 去重,观看按 dramaId 去重;空页或 `loaded >= total` 停止请求。
|
||
- 收藏页、观看历史页接入滚动触底加载、下拉刷新、通用分页 footer、总数展示。
|
||
- 观看卡片优先展示分页接口的 `dramaTitle/dramaCover/episodeNumber`;
|
||
标题为空时显示本地化“第 N 集”,封面缺失时显示占位图标。
|
||
- API 严格解析服务端 PageResult;旧 List 不做客户端全量包装,由后端部署新契约解决。
|
||
- MineLogic 改用分页首屏 `list`,不再调用旧 limit 接口。
|
||
|
||
### 门禁
|
||
|
||
定向测试:
|
||
|
||
```text
|
||
$ flutter test --no-pub test/library/library_api_test.dart test/library/favorite_controller_test.dart test/library/favorites_page_test.dart test/watch/watch_api_test.dart test/watch/watch_records_controller_test.dart test/watch/watch_records_page_test.dart test/mine/mine_logic_test.dart
|
||
00:03 +40: All tests passed!
|
||
```
|
||
|
||
完整门禁:
|
||
|
||
```text
|
||
$ dart analyze lib test
|
||
Analyzing lib, test...
|
||
No issues found!
|
||
|
||
$ flutter test --no-pub
|
||
00:56 +632: All tests passed!
|
||
```
|
||
|
||
解读:分页第一页、追加去重、无更多不重复请求、loadMore 失败重试、
|
||
观看标题/集号/封面展示及空标题兜底均通过;全量静态分析零告警,632 项测试全过。
|
||
|
||
未新增依赖、未启停服务、未操作真机;现有 `pubspec.lock` 改动未提交。
|
||
|
||
### 范围修订:观看标题优先与按需详情
|
||
|
||
- 分页协议、Repository 与 Controller 分页状态保持不变。
|
||
- `_hydrate` 只对 `dramaTitle` 为空的记录补拉详情;后端已有标题时不再请求详情。
|
||
- 有标题的记录点击播放时才调用 `detailForOpen` 按需加载详情,避免列表阶段 N+1。
|
||
- 标题仍为空时使用本地化“第 N 集”,封面缺失继续显示占位。
|
||
- 移除了旧 List 自动包装为 PageResult 的前端全量兜底,避免掩盖后端契约问题。
|
||
|
||
真实后端分页取证(仅测试账号写入,不启停服务):
|
||
|
||
```text
|
||
GET /app/watch/recent?pageIndex=1&pageSize=1
|
||
{"shape":"list","length":2,"dramaIds":["1","5"]}
|
||
GET /app/watch/recent?pageIndex=2&pageSize=1
|
||
{"shape":"list","length":2,"dramaIds":["1","5"]}
|
||
GET /app/favorites?pageIndex=1&pageSize=1
|
||
{"shape":"list","length":2,"dramaIds":["1","5"]}
|
||
GET /app/favorites?pageIndex=2&pageSize=1
|
||
{"shape":"list","length":2,"dramaIds":["1","5"]}
|
||
```
|
||
|
||
解读:当前 8081 运行实例仍是旧包,返回 List 且忽略 pageIndex/pageSize;第 1/2 页
|
||
均返回相同两条数据。客户端不做全量兜底;live smoke 检测到旧 List 后明确标记 skipped,
|
||
等待后端新包部署后再执行分页真实联调。
|
||
|
||
门禁:
|
||
|
||
```text
|
||
$ flutter test --no-pub test/integration/live_watch_flow_test.dart test/library/library_api_test.dart test/watch/watch_api_test.dart test/watch/watch_records_controller_test.dart test/watch/watch_records_page_test.dart
|
||
00:02 +22 ~1: All tests passed!
|
||
|
||
$ dart analyze lib test
|
||
Analyzing lib, test...
|
||
No issues found!
|
||
|
||
$ flutter test --no-pub
|
||
00:52 +630 ~1: All tests passed!
|
||
```
|
||
|
||
解读:有 dramaTitle 时不触发详情请求、点击时才加载详情、空标题兜底、
|
||
第 2 页追加去重和 hasMore 行为均通过;全量分析零告警,630 项通过、1 项旧服务
|
||
能力探测跳过。`pubspec.lock` 既有改动未提交。
|
||
|
||
## 2026-10-22 收藏卡片简介
|
||
|
||
- 分支:`feature/favorite-card-synopsis`
|
||
- 基线:`develop` / `origin/develop` = `9093700`
|
||
- 计划:`docs/plans/2026-10-22-favorite-card-synopsis.md`
|
||
|
||
### 实现
|
||
|
||
- `_FavoriteCard` 在标题/标签下方条件渲染 `drama.synopsis.trim()`。
|
||
- 简介最多 2 行、`TextOverflow.ellipsis`,使用 `AppColors.muted`、12 号字和
|
||
`1.15` 行高,由 `Flexible` 占据剩余空间,保持封面与卡片原高度。
|
||
- 简介为空或纯空白时不创建文本节点,继续使用原 Spacer,不占高度。
|
||
- 未改接口、后端和收藏分页逻辑。
|
||
|
||
### 门禁
|
||
|
||
```text
|
||
$ flutter test --no-pub test/library/favorites_page_test.dart
|
||
00:01 +7: All tests passed!
|
||
|
||
$ dart analyze lib test
|
||
Analyzing lib, test...
|
||
No issues found!
|
||
|
||
$ flutter test --no-pub
|
||
00:51 +631 ~1: All tests passed!
|
||
```
|
||
|
||
解读:有简介时文本最多 2 行并省略,空简介不渲染且卡片高度与正常卡片一致;
|
||
全量静态分析零告警,631 项通过、1 项运行中旧后端能力探测跳过。
|
||
`pubspec.lock` 既有改动未提交。
|
||
|
||
## 2026-10-22 ReelShort 客户端深色视觉改造
|
||
|
||
- 分支:`feature/dark-theme-client`
|
||
- 基线:开发前已有约 43 个未提交视觉文件,本次在原改动上继续收敛,未推倒重来。
|
||
- 计划:`docs/plans/2026-10-22-dark-theme-client.md`
|
||
|
||
### 颜色与主题
|
||
|
||
- `AppTheme.dark()` 成为默认且唯一主题,系统状态栏/导航栏图标统一浅色。
|
||
- 色板统一为背景 `#0B1020`、卡片 `#151B2E`、浮层 `#1C2436`、
|
||
divider `#0FFFFFFF`、主色 `#FF6B2C`、辅助链接色 `#22D3EE`。
|
||
- 主/次/禁用文字分别使用白色 92% / 64% / 38%。
|
||
- 卡片/按钮/输入框/标签圆角统一为 12/10/10/6,卡片阴影关闭。
|
||
- 页面和组件内的 `Colors.*`、`Color(0x...)` 已全部映射到 `AppColors`
|
||
语义 token;剩余原始色值只在 `app_colors.dart` 中定义。
|
||
- 导航、玻璃面板、播放器遮罩、加载轨道、渐变、边框和阴影均改为深色语义。
|
||
- 修正全局输入装饰 padding 导致账户性别下拉溢出,保持原布局宽度。
|
||
|
||
### 验证命令与原始结果
|
||
|
||
```text
|
||
$ git checkout -- pubspec.lock
|
||
$ git status --short --branch
|
||
## feature/dark-theme-client
|
||
|
||
$ rg -n -P "Color\(0x|(?<!App)Colors\." lib | rg -v '^lib/core/theme/'
|
||
(no output)
|
||
|
||
$ rg -n "SystemUiOverlayStyle\.dark" lib
|
||
(no output)
|
||
```
|
||
|
||
解读:pubspec.lock 漂移已还原;页面/组件层不再直接写颜色,所有状态栏均使用浅色图标。
|
||
|
||
```text
|
||
$ dart analyze lib test
|
||
Analyzing lib, test...
|
||
No issues found!
|
||
```
|
||
|
||
```text
|
||
$ flutter test --no-pub
|
||
00:51 +644: All tests passed!
|
||
```
|
||
|
||
```text
|
||
$ flutter build apk --debug
|
||
Running Gradle task 'assembleDebug'...
|
||
2,058ms
|
||
✓ Built build/app/outputs/flutter-apk/app-debug.apk
|
||
```
|
||
|
||
解读:静态分析零告警,644 项测试全部通过,debug APK 构建成功;测试与构建后再次确认
|
||
`pubspec.lock` 无改动,未启停服务、未操作真机。
|
||
|
||
## 2026-10-22 客户端底部导航改为首页/推荐/追剧/我的
|
||
|
||
- 分支:`feature/client-bottom-nav-4tabs`
|
||
- 基线:`develop` / `origin/develop` = `ec3cb8f`
|
||
- 计划:`docs/plans/2026-10-22-client-bottom-nav-4tabs.md`
|
||
- 实现提交:`91e869d feat(nav): 底部导航改为首页推荐追剧我的`
|
||
|
||
### 问题
|
||
|
||
根导航仍为首页/剧场/创作/我的,底栏选中态使用品牌橙。追剧若通过新路由打开,
|
||
会叠加第二层底栏并产生返回栈,不符合参考图和单根导航交互。
|
||
|
||
### 改动
|
||
|
||
- 新增 `AppRootTab` 统一四个 tab 的顺序、key、图标和 l10n 标签。
|
||
- `AppBottomNav` 改为纯黑底、白色选中、`#8A8A8E` 未选中,图标 24、文字 11,
|
||
保留 SafeArea 与轻震动。
|
||
- 首页 `IndexedStack` 第三槽直接使用 `FavoritesContent`,不再 push 收藏路由。
|
||
追剧保留顶部标题区、隐藏返回按钮,只有一个根底栏,不创建嵌套 Scaffold。
|
||
- `FavoritesPage` 保留独立 Scaffold 和返回行为,继续供“我的”入口使用。
|
||
- 推荐、追剧、我的三个根页面统一调用 `HomeLogic.select` 切换,不进入返回栈。
|
||
- 创作页保留,`mine-creator-center` 入口保留;创作不再出现在根底栏。
|
||
- 中文使用“追剧”,英文使用 `Home / Recommend / Following / My`,
|
||
印尼语使用 `Beranda / Rekomendasi / Daftar Tonton / Saya`。
|
||
- `FavoritesContent` 内置顶部 SafeArea,避免嵌入首页后被状态栏遮挡。
|
||
|
||
### 门禁命令与原始输出
|
||
|
||
```text
|
||
$ dart analyze lib test
|
||
Analyzing lib, test...
|
||
No issues found!
|
||
```
|
||
|
||
```text
|
||
$ flutter test --no-pub
|
||
04:38 +644: All tests passed!
|
||
```
|
||
|
||
导航测试覆盖:四个 tab 数量与顺序、纯白/灰色状态、点击追剧进入
|
||
`FavoritesContent`、仅一个 `AppBottomNav`、追剧内部无嵌套 Scaffold、
|
||
`Navigator.canPop == false`、创作不再出现在底栏且我的入口保留。
|
||
|
||
解读:静态分析零告警,644 项测试全部通过;追剧与首页/推荐/我的均在同一根页面
|
||
内切换,无叠加导航或返回栈。`pubspec.lock` 未提交,未启动或停止服务,未操作真机。
|
||
|
||
## 2026-10-22 根导航布局规格对齐
|
||
|
||
- 分支:`feature/client-bottom-nav-layout`
|
||
- 基线:`develop` / `origin/develop` = `667fdc5`
|
||
- 实现提交:`81787dc style(nav): 对齐四 Tab 底栏布局规格`
|
||
|
||
### 改动
|
||
|
||
- 新增 `AppRootBottomNav` 作为首页、推荐、追剧、我的唯一根导航入口,
|
||
三个 Shell 页面不再各自拼装 item。
|
||
- 四列使用 `Expanded` 等分,图标在上、文字在下,间距 4。
|
||
- 内容高度 56,另由 `SafeArea` 增加底部安全区;图标 26、文字 11、
|
||
`letterSpacing = 0`,选中 `w600`、未选中 `w400`。
|
||
- 背景纯黑,取消顶部边框;选中 `#FFFFFF`,未选中 `#8E8E93`。
|
||
- 首页、推荐、书签、人像继续使用各自的选中实心/未选中描边图标。
|
||
|
||
### 门禁命令与原始输出
|
||
|
||
```text
|
||
$ flutter analyze
|
||
Analyzing reelshort-bottom-nav...
|
||
No issues found! (ran in 5.2s)
|
||
```
|
||
|
||
```text
|
||
$ flutter test
|
||
04:43 +645: All tests passed!
|
||
```
|
||
|
||
解读:四等分、56 内容高度、安全区、26 图标、4 间距、精确色值和无分割线
|
||
均有测试覆盖;645 项测试全部通过。`pubspec.lock` 未提交,未操作真机。
|
||
|
||
## 2026-10-22 首页改为三列探索网格
|
||
|
||
- 分支:`feature/home-grid-layout`
|
||
- 基线:`develop` / `origin/develop` = `6dea9d4`
|
||
- 实现提交:`dd96792 feat(home): 首页改为三列探索网格`
|
||
|
||
### 布局与数据
|
||
|
||
- 首页按参考图改为纯黑探索页:搜索框、皇冠、礼物入口、一级分类、
|
||
二级标签与三列封面网格。
|
||
- `HomeFeedController` 继续使用 10 条游标分页;支持下拉刷新、触底加载、
|
||
空态与失败重试,没有全量拉取。
|
||
- 一级固定项为精选/新剧/分类/榜单;古装/社群仅在 `/app/categories`
|
||
名称匹配时显示。新剧/分类/榜单复用现有页面。
|
||
- 二级标签按分类与题材可用性显示,筛选按钮复用 `FilterPage`。
|
||
- 卡片封面 5:6、圆角 8、间距 8;包含真实热剧/新剧/漫剧角标、
|
||
播放量、最多两行标题与最多两个题材标签。
|
||
- `Drama` 增加 `dramaType` 和可选 `createdAt`;卡片未下发上架时间时
|
||
从 Snowflake id 解析,不使用假数据。
|
||
- 原 `PlayerPage(homeMode: true)` 保留并迁至推荐 tab,播放器文件与测试未删除。
|
||
- 首页顶部中性色、卡片与描边 token 已沉淀到 `AppColors/AppDimens`,
|
||
后续 P14 复用。
|
||
|
||
### 门禁命令与原始输出
|
||
|
||
```text
|
||
$ flutter analyze
|
||
Analyzing reelshort-bottom-nav...
|
||
No issues found! (ran in 2.7s)
|
||
```
|
||
|
||
```text
|
||
$ flutter test
|
||
02:32 +651: All tests passed!
|
||
```
|
||
|
||
解读:三列网格、分类顺序、动态入口隐藏、角标、卡片信息、分页大小、
|
||
下一页追加和卡片进入播放器均有测试覆盖。`home_view.dart` 为 478 行,
|
||
其余新增组件均低于 500 行。`pubspec.lock` 未提交,未操作真机。
|
||
|
||
## 2026-10-22 「我的」页视觉风格统一
|
||
|
||
- 分支:`feature/mine-visual-style`
|
||
- 基线:`develop` / `origin/develop` = `47bf645`
|
||
- 实现提交:`83129ac feat(mine): 统一我的页深色视觉风格`
|
||
|
||
### Token 与改动
|
||
|
||
- 复用 P11 的 `appBackground/contentCard/contentSurface/contentBorder`,
|
||
新增 `textPrimary/mineCardRadius/mineRowHeight/minePageTitle/mineSectionTitle`。
|
||
- 页面底色改为纯黑,移除「我的」页的旧深蓝渐变;页头白色标题 21/w700。
|
||
- 头像、三宫格、金币卡、会员卡、菜单统一为深灰 16 圆角、1px 极淡描边,
|
||
无阴影、无渐变、无发光。
|
||
- 主文字/昵称/金额使用 `#FFFFFF`,次级说明和 chevron 使用 `#8E8E93`。
|
||
- 三宫格等宽,入口图标 24 线性;菜单行高 52、标题 15,分割线只存在行间。
|
||
- 橙色仅保留在充值、加入 VIP 等主操作;头像与菜单图标不再使用粉色/橙色装饰。
|
||
- 区块顺序、全部入口、跳转与交互逻辑保持不变。
|
||
|
||
### 门禁命令与原始输出
|
||
|
||
```text
|
||
$ flutter analyze
|
||
Analyzing reelshort-bottom-nav...
|
||
No issues found!
|
||
```
|
||
|
||
```text
|
||
$ flutter test
|
||
00:56 +651: All tests passed!
|
||
```
|
||
|
||
解读:背景、卡片 token、圆角/描边、无渐变阴影、三宫格等宽、菜单 52 行高、
|
||
入口保留和收藏页跳转均有测试覆盖。`pubspec.lock` 未提交,未操作真机。
|