Author SHA1 Message Date
Zhou Weidong 12b1a11bd3 feat(ui): 更新底栏图标与播放加载视觉 2026-10-31 14:35:26 +08:00
Zhou Weidong 8ec6df2be5 merge: 修复 iOS 系统分享面板
flutter-ci / test (push) Canceled after 0s
2026-10-31 13:40:14 +08:00
Zhou Weidong 5ec4db151a fix(ios): 修复分享面板并同步互动栏布局断言 2026-10-31 13:33:13 +08:00
Vicky a47af8c3ad style(player): 视频信息区文案统一为 65% 半透明白
flutter-ci / test (push) Canceled after 0s
2026-10-31 09:41:19 +08:00
Vicky 2ddf9ee1c6 test(config): 服务地址断言跟随环境开关
flutter-ci / test (push) Canceled after 0s
2026-10-31 00:02:53 +08:00
Vicky 3bc79ad8e2 fix(i18n): 去掉印尼语内容完整性过滤,缺失时回退中文 2026-10-31 00:02:53 +08:00
Vicky c307903c42 docs: 修正 README 中的分析基线文案
flutter-ci / test (push) Canceled after 0s
2026-10-30 22:53:18 +08:00
Vicky c5b94633f1 feat(following): 收藏页改为紧凑布局并去掉重复续播横幅 2026-10-30 22:53:18 +08:00
Vicky fd2ba0bf43 feat(player): 重做播放页信息层级与互动栏点击动效 2026-10-30 22:53:18 +08:00
Vicky aed9ee2b01 feat(image): 图片加载占位改为微光骨架 2026-10-30 22:53:18 +08:00
Vicky 335e5dc286 feat(home): 顶部导航随翻页联动并把推荐更名为精选 2026-10-30 22:53:15 +08:00
Vicky 2f4593a46e fix(a11y): 限制底栏文字缩放并统一播放页触控尺寸
flutter-ci / test (push) Canceled after 0s
2026-10-30 17:14:40 +08:00
Vicky 7509cf2ecd fix(following): 减淡继续观看封面遮罩亮度 2026-10-30 17:14:40 +08:00
Vicky 501bc66282 fix(wallet): 丢弃切换账号后返回的旧余额响应 2026-10-30 17:14:40 +08:00
Vicky 19196ceb8e fix(i18n): 印尼语内容缺失时不再回退中文 2026-10-30 17:14:37 +08:00
Vicky 9ef8eb15b0 feat(recommend): 轮播只保留标题简介并重做顶部导航 2026-10-30 17:14:34 +08:00
Vicky 53e8c924a6 feat(mine): 重做我的页会员卡与功能宫格布局 2026-10-30 17:14:32 +08:00
Vicky e7399fa3fd feat(auth): 登录页改为 ymb 品牌并支持后台配置背景图 2026-10-30 17:14:30 +08:00
Vicky 22cc669bbe feat(brand): 应用图标、开屏资源与品牌字体切换为 ymb 2026-10-30 17:14:27 +08:00
Vicky 26321e3348 feat(cover): 支持横版封面并收紧底栏图标间距
flutter-ci / test (push) Canceled after 0s
- Drama 新增 coverLandscape,WatchProgress 新增 dramaCoverLandscape
- 继续观看横幅优先使用横版封面,缺省回退竖版
- 底部导航栏图标与文字间距由 5dp 收紧至 1dp
2026-10-29 17:32:41 +08:00
Vicky 3590cd0c55 feat(recommend): 上新板块改按后端语义码判断,顶部轮播改为无缝循环
flutter-ci / test (push) Failing after 30m39s
Drama 新增 tagCode,推荐页只看 NEW_ARRIVAL 这个码决定上新横滑,
不再依赖可变的标签文案;分类网格高度改为复用卡片的统一常量。
头部轮播改用虚拟无限页码,首尾相接且能反向滑,自动播放不再倒滑回第一张。
2026-10-29 15:44:33 +08:00
Vicky c311985601 fix(ui): 列表剧名统一两行省略,收藏卡去掉观看进度副标题
筛选、追剧收藏、新片、榜单、缓存与推荐上新等列表卡都按两行剧名预留高度,
超长标题显示省略号;收藏卡不再显示「看到第 N 集」,文字区高度同步收窄。
2026-10-29 15:44:30 +08:00
Vicky 4dd37b2588 fix(i18n): 缩短我的页收藏、缓存与历史的英文和印尼语文案
My Favorites / Offline downloads / Watch History 在菜单里过长,
统一收成 Favorites / Downloads / History 与 Favorit / Unduhan / Riwayat。
2026-10-29 15:44:26 +08:00
Vicky 543d0aadd5 style(player): 播放页与首页浮层的控件样式统一
播放页顶部按钮与选集条去掉底色、进度条不再画滑块圆点并压到最底部;
剧名与简介收在互动栏左侧,清屏后只留右下角退出按钮。
首页观看条与进度条共用 20pt 页边距,进度槽底色与播放页一致。
2026-10-29 15:44:22 +08:00
Vicky d5bcda54d3 fix(player): 雪碧图缓存补齐槽位预取量,清屏按钮贴近进度条
三个渲染槽位各预取 2 张雪碧图,容量 3 装不下会导致「重建→重新下载解码→
notify→重建」的死循环,真机持续发烫掉帧;容量恢复为 8 并只在未命中时预取。
首页清屏态的退出按钮由距底 56 收到 24,贴着进度条上方。
2026-10-29 15:44:17 +08:00
Vicky 79463e65c2 feat(player): 剧名与选集合并为同一个弹窗
点剧名默认停在详情页签,选集入口直接停在选集页签,不再跳详情页;
详情页签补上简介、标签、收藏与相似推荐,原选集弹窗合并进来后删除。
2026-10-29 15:44:10 +08:00
Vicky ad7aba3daf chore(gitignore): 忽略 IDE 的 Widget Preview 生成目录
flutter-ci / test (push) Failing after 19m31s
.widget_preview/ 是 IDE 里 Flutter Widget Preview 生成的临时工程(含 .dart_tool、build),不入库。
2026-10-29 11:56:23 +08:00
Vicky 47fdd4a29d build(ios): 移除 CocoaPods 集成,插件统一走 Swift Package Manager
iOS 插件已全部支持 SPM,但工程仍保留 Podfile,每次构建都会提示移除 CocoaPods 集成;
按提示 pod deintegrate、删除 Podfile,并去掉两个 Flutter xcconfig 里对 Pods-Runner 配置的 include。
2026-10-29 11:55:35 +08:00
Vicky fe49527dd8 perf(discover): 剧场与新片页网格改为按行懒加载
flutter-ci / test (push) Failing after 19m39s
双列网格原来是两个 Column 一次性建出所有卡片,翻几页后上百张卡连同封面都留在树上;
现在改成按行的 SliverList,只构建可见行,剧场页/新片页/搜索结果的滚动容器一并换成 sliver。
搜索结果、加载失败等状态同步改成 sliver,滚动位置与下拉刷新行为保持不变。
2026-10-29 11:38:21 +08:00
Vicky 4aaa5bbd60 build(deps): 显式声明 intl 依赖,修掉 CI 零告警门禁的存量告警
flutter-ci / test (push) Failing after 19m29s
播放量格式化直接用了 NumberFormat,但 intl 只是 flutter_localizations 的传递依赖;
新增的 dart analyze --fatal-infos 流水线会因此失败,这里按 flutter_localizations 钉住的版本显式声明。
2026-10-29 11:27:53 +08:00
Vicky 5649e4c3d4 perf(home): 四个根 tab 改为首次可见才构建
原先 IndexedStack 冷启动就把推荐/发现/追剧/我的四棵子树全建出来,各自还会发请求;
现在只建当前 tab,切过去再建并常驻,冷启动少三页构建和一次观看历史请求。
2026-10-29 11:25:47 +08:00
Vicky 9f7301305e perf(following): 追剧页不可见时不再提前拉观看历史
首页用 IndexedStack 常驻四个 tab,追剧页一启动就会发一次观看历史请求,和首页 feed 抢网络;
改为切到该 tab 时再加载,收藏列表也只在首次可见且为空时补拉。
2026-10-29 11:25:47 +08:00
Vicky 63191b4692 perf(player): 续看点加载与进度写盘改为并行且复用同一请求
进播放页与首次准备播放会各要一次续看点,同一次请求跑两遍还串在首帧前;
现在并发请求复用同一 Future,并与上一条进度写盘并行等待,少一个网络往返的起播延迟。
剧场网格海报按布局宽度限制解码尺寸,半屏卡片不再按原图解码。
2026-10-29 11:25:47 +08:00
Vicky 0f777b19f2 perf(player): 限制雪碧图与海报预缓存内存并给播放位置缓存加容量上限
雪碧图解码从 2048 降到 1024、缓存从 8 张降到 3 张,避免擦洗预览驻留十几 MB 位图;
相邻集海报预缓存改用 ResizeImage 按 360/720 宽解码,不再缓存整张原图;
播放位置缓存按写入时间淘汰,最多保留 100 条,长会话不再只增不减;
观看记录详情失败日志加 kDebugMode 保护,release 下不再逐条打堆栈。
2026-10-29 11:25:47 +08:00
Zhou Weidong fb9001a02d ci: 新增 Gitea Actions 流水线(测试/分析/构建门禁)
flutter-ci / test (push) Failing after 23m25s
2026-10-29 10:47:36 +08:00
Zhou Weidong 1b80f841be chore(ios): 补齐 CocoaPods 集成配置文件
xcconfig 增加 Pods 引用(PiP 等插件依赖),Podfile 纳入版本管理,
保证任何机器拉取代码后可直接构建 iOS。
2026-10-29 10:46:42 +08:00
Zhou Weidong 3e89c71f50 docs(readme): 更新服务地址配置与双渠道打包说明
补上 dev/prod 配置文件的切换方式,并把生产打包拆成 Google Play 的 AAB
与国内市场的通用 APK,标注实测体积、符号表归档与签名待办。
2026-10-29 10:44:58 +08:00
Zhou Weidong 2ce92aa7dd build(android): 压缩生产包体积至 13MB 并统一双渠道产物
生产包九成体积是两套 ABI 的引擎与快照。release 固定 split-debug-info 与
dart-obfuscation,快照从 8MB 降到 1.1MB/ABI;原生库改为压缩存储;开启
R8 裁剪并补保号规则;只打包实际引用的登录背景,移除零引用的图标字体。
2026-10-29 10:44:54 +08:00
Zhou Weidong a6d6b66d31 refactor(config): 服务地址改为 dev/prod 配置文件与单个环境开关
启动时按环回、局域网、公网顺序探测地址,换网络后会静默切到别处,排查
困难。改为代码内一个布尔开关选择环境,地址分别放在 dev_config.dart 与
prod_config.dart,不再探测;调试条改地址后直接重载,不再重新探测。
release 构建若仍是开发开关会启动即报错,避免线上包连开发机。
2026-10-29 10:44:50 +08:00
Zhou Weidong 57f84c45bd fix(player): 解锁状态登录期内共享,重进播放页不再丢解锁
解锁记录原本是播放页实例的私有集合,退出再进入会先闪一次锁定态,还会
被 selectLocked 提前释放播放器。改为进程内共享的 UnlockedEpisodeRegistry,
由全局注入并在登录态变化时清空;播放器三处渲染分支统一走同一判定,
会话锁态只约束当前页,避免相邻预加载页被误判为锁定。
2026-10-29 10:44:44 +08:00
Zhou Weidong a4d08e7c1e refactor(paging): 抽取分页控制器基类收敛四个列表页
收藏、观看记录、剧单、公告各自手写 loading 互斥、请求序号、跨页去重与
错误映射,修一处要同步改四份。统一到 PagedListController 后子类只提供
取页与条目标识;空页按已加载数收口,避免 total 虚大时反复请求。
2026-10-29 10:44:41 +08:00
Zhou Weidong 9c9a277217 refactor(feed): 合并首页与剧场频道控制器的重复分桶逻辑
两个控制器的缓存优先、请求序号防串台、分页去重、预取与 seed 完全同构,
此前各写一份。抽出 ChannelFeedController 后,子类只保留分桶 key 与取页
实现;顺带修掉刷新被翻页抢占时 loading 永久为 true 的问题。
2026-10-29 10:44:36 +08:00
Zhou Weidong 1be8ebf1f9 refactor(api): 统一响应解析并修复下载错误文案空白
各 *Api 类此前各自实现 data 结构判断,download_api 在结构异常时抛出空
message,界面会显示空白提示。抽出 ResponseDecoders 提供对象、可空对象、
对象数组三种语义,七个文件改为复用;下载侧异常文案与其他接口对齐。
2026-10-29 10:44:32 +08:00
Zhou Weidong 70be941755 chore(build): 忽略 android 构建产物目录 2026-10-29 09:18:47 +08:00
Zhou Weidong 351d0afd09 chore(deps): 补齐 pubspec.lock 中画中画相关依赖 2026-10-29 09:18:43 +08:00
Zhou Weidong 7d32324a2d docs(readme): 补画中画、下载落盘与 iOS 打包说明,更新接口清单
接口从 26 条路径更新到 47 条(补账号设备/注销、搜索、剧单、标签、剧集统计
与解锁、钱包商品订单、评论数);画中画结论由"iOS 未接入"改为双端已接入;
下载由"不落盘、不支持离线播放"改为已落盘可离线、但不支持断点续传;
门禁更新为 dart analyze + flutter test(775 例)。
2026-10-28 19:05:12 +08:00
Zhou Weidong 1d4ab5e0e6 test: 按新 UI 基线重写过期用例
覆盖 155 条失败与 4 处编译错误:入口改走推荐流 tab、弹框换 AppGlowDialog、
rail 尺寸与底部锚定、子页标题字重 w400、底栏选中品牌红、收藏文案改收藏短剧。
全量 flutter test 775 通过。
2026-10-28 18:48:03 +08:00
Zhou Weidong 799f3d1b7d feat(player): 选集面板挑集后记住上次选集 2026-10-28 18:47:51 +08:00
Zhou Weidong 08aa6903d2 fix(player): 推荐流播放器补钱包依赖并统一点赞选中色 2026-10-28 18:47:51 +08:00
Zhou Weidong 4de9417e27 fix(auth): 放大登录注册分段的可点击区域 2026-10-28 18:47:51 +08:00
Zhou Weidong e269d5dce5 fix(ui): 修正卡片正文预留高度,消除底部溢出 2026-10-28 18:47:51 +08:00
Zhou Weidong 78f464f2a4 fix(ui): 弹框富内容加高度约束,长文案恢复滚动 2026-10-28 18:47:51 +08:00
Zhou Weidong 9983d42015 fix(core): 非移动端跳过设备区域读取,避免注册流程卡住 2026-10-28 18:47:51 +08:00
Vicky bfe0df62dc Merge branch 'develop' 2026-10-28 17:14:52 +08:00
Vicky df2654c9d6 fix(player): iOS自动小窗仅在播放页活跃时拉起 2026-10-28 17:14:50 +08:00
Vicky 496b1cb797 Merge branch 'develop' 2026-10-28 16:39:11 +08:00
Vicky 85dedf20fa fix(player): 修复清晰度切换闪屏与还原黑屏 2026-10-28 16:36:55 +08:00
Vicky 43e60e29ec feat(player): 接入双端画中画播放 2026-10-28 16:36:55 +08:00
Vicky 5346be40a9 fix(ui): 打磨推荐追剧我的设置页间距与图标 2026-10-28 16:36:51 +08:00
Vicky de7da64886 feat(ui): 登录页重设计并统一弹框主题渐变外壳 2026-10-28 16:36:43 +08:00
Vicky 709972245f fix(config): release 包固定公网接口地址,不再探测开发机
- release 默认地址固定为 publicBaseUrl(39.162.50.99:8081)
- release 启动跳过环回/局域网探测,避免合并 develop 后请求到开发机 192.168 地址
- debug 行为不变,仍按环回/开发机探测并支持 dart-define 覆盖
2026-10-27 20:10:06 +08:00
Vicky d5d13549f5 chore(merge): 合并推荐频道页与沉浸式首页到 main
- 新增推荐(tab1)接口分类频道页:分类轮播切换、选中频道自动定位、下拉刷新与上拉分页
- 精选频道 banner 轮播 + 接口 tag 上新横滑 + 双列短剧网格,卡片标题统一白色
- 恢复首页(tab0)沉浸式全屏信息流
2026-10-27 20:02:53 +08:00
Vicky c027acc91f feat(recommend): 新增推荐频道页并恢复首页沉浸式信息流
- tab1 推荐页改为接口分类频道:频道导航可横滑,选中频道自动滚动到可视区,内容区左右滑动切换分类
- 精选频道:顶部固定高度 banner 轮播图 + 接口 tag 筛选的上新短剧横滑 + 其余短剧双列网格;其他分类三列网格
- 分类内容支持懒加载、下拉刷新与上拉分页加载
- 卡片角标文案与背景色取自接口 tag/tagColor,短剧标题统一白色
- 恢复 tab0 为沉浸式全屏视频首页及其三 inner-tab 顶导(搜索/礼物入口)
- l10n 新增原创出品、立即观看、上新、编辑精选、全N集等多语言文案
2026-10-27 19:57:42 +08:00
Vicky a3f3481f4f feat: phase1 UI prep — 设备ID系统传递 + 国家本地化展示 + 继续观看直进播放页
设备标识: Android SSAID/ iOS IDFV 原生通道 → Dart DeviceIdProvider
国家解析: 注册/登录从后端 IP → 国家/语言/时区/币种 自动落库
账号资料: 全部本地化展示 (account_display.dart)
我的页: 继续观看卡片点击直接进播放页
2026-10-27 15:45:20 +08:00
Vicky 1b19513efb feat(core): 设备号改用系统标识,重装不再变成新游客
- Android 读 SSAID、iOS 读 IDFV,均免权限,由原生通道提供,不引插件
- 取不到时回退本地 UUIDv4,替代原来 32 位随机 + 时间戳的拼接
2026-10-27 14:15:50 +08:00
Vicky 935de13343 refactor(core): 平台上报收敛为单个请求头
去掉 device_info_plus 依赖与机型/系统版本,只保留 X-Client-Platform。
2026-10-27 14:00:19 +08:00
Vicky 01a3b0101e chore(merge): 合并客户端设备画像上报 2026-10-27 13:54:29 +08:00
Vicky d6df5fbe5f feat(core): 请求头统一上报设备系统与机型
- 新增 DeviceProfileReader,首次读取后缓存,插件不可用时安全降级
- 拦截器为所有请求补 X-Client-Platform / Os / Model,匿名请求同样携带
2026-10-27 13:54:28 +08:00
Vicky 929b7f72f8 chore(merge): 合并右侧操作栏顺序统一 2026-10-27 13:35:41 +08:00
Vicky e028ca40b1 fix(ui): 统一播放页右侧操作栏顺序为收藏-点赞-评论-分享
与推荐页操作栏保持一致,同步更新布局断言。
2026-10-27 13:35:41 +08:00
Zhou Weidong d206465ee1 fix(recommend): 首屏不再兜出重试空态 2026-10-27 11:24:21 +08:00
Zhou Weidong 9117bf58b8 fix(ui): 拆分媒体骨架底色,恢复可见度 2026-10-27 11:16:24 +08:00
Zhou Weidong bd826a9236 fix(ui): 骨架屏扫光收窄压淡 2026-10-27 11:12:31 +08:00
Zhou Weidong a5dbae7fff fix(ui): 骨架屏改为逐卡片扫光 2026-10-27 11:08:43 +08:00
Zhou Weidong 00405d2e3b fix(ui): 收敛骨架屏亮度 2026-10-27 10:49:30 +08:00
Zhou Weidong 72dcb92aca fix(following): 收紧取消追剧确认弹窗 2026-10-27 10:34:07 +08:00
Zhou Weidong 8d511933df fix(mine): 支持未登录展示互动计数 2026-10-27 10:26:11 +08:00
Zhou Weidong 06a2f6d7c0 fix(following): 支持未登录追剧与历史 2026-10-27 10:13:27 +08:00
Zhou Weidong 0d785bf1f8 feat(library): 支持设备追剧与观看历史 2026-10-27 09:54:51 +08:00
Zhou Weidong 4c3acb047c fix(player): 收紧推荐页互动栏间距 2026-10-27 09:37:00 +08:00
Zhou Weidong d3dbc9f8dc fix(favorite): 统一收藏状态与互动图标 2026-10-27 09:31:48 +08:00
Zhou Weidong 4b97f89c0b fix(recommend): 延后推荐数据刷新 2026-10-27 09:19:18 +08:00
Zhou Weidong 6be024f86f test(flutter): 收敛异步配置与分页测试 2026-10-26 22:49:27 +08:00
Zhou Weidong 6512808506 test(ui): 更新主题导航与布局断言 2026-10-26 22:49:23 +08:00
Zhou Weidong 2d65cd3006 test(auth): 更新登录失败提示断言 2026-10-26 22:49:21 +08:00
Zhou Weidong 391b01e9d0 fix(theater): 修复新剧卡片与榜单溢出 2026-10-26 22:49:13 +08:00
Zhou Weidong d4ce3a5d8b fix(home): 修复目录错误态被占位层遮挡 2026-10-26 22:49:10 +08:00
Zhou Weidong bf47c065c9 fix(ui): 修复继续观看卡片纵向溢出 2026-10-26 22:49:07 +08:00
Zhou Weidong c6b3ca9311 fix(auth): 禁止刷新接口递归重试 2026-10-26 22:49:04 +08:00
Zhou Weidong a3d50eb935 feat(interaction): 支持游客点赞收藏评论 2026-10-26 22:33:14 +08:00
Zhou Weidong 188811fde6 feat(player): 切集刷新互动计数统计 2026-10-26 21:40:39 +08:00
Zhou Weidong 37353dac15 fix(player): 观看全集进入后自动播放 2026-10-26 21:25:38 +08:00
Zhou Weidong 350d16db75 fix(player): 修正缓冲态与首帧时间闪现 2026-10-26 21:13:55 +08:00
Zhou Weidong 76429628ec chore(merge): 合并播放器切集与双击点赞优化到 main 2026-10-26 19:31:47 +08:00
Zhou Weidong 0f27daf664 chore(merge): 合并播放器切集与双击点赞优化 2026-10-26 19:24:49 +08:00
Zhou Weidong afdc4af2f2 chore(player): 移除飘心毫秒调试文字 2026-10-26 19:21:24 +08:00
Zhou Weidong 1b50a73bfc fix(player): 修复首次双击点赞状态刷新 2026-10-26 19:17:14 +08:00
Zhou Weidong 978c309cd7 fix(player): 改用墙钟驱动飘心动画 2026-10-26 19:07:56 +08:00
Zhou Weidong c0f65042e6 fix(player): 修正飘心Overlay生命周期 2026-10-26 19:00:37 +08:00
Zhou Weidong a7d01e0306 test(player): 增加飘心像素定位回归 2026-10-26 18:57:04 +08:00
Zhou Weidong ed2cf4c18c fix(player): 使用根Overlay承载飘心 2026-10-26 18:54:05 +08:00
Zhou Weidong db3e153409 fix(player): 使用根Overlay承载飘心 2026-10-26 18:49:36 +08:00
Zhou Weidong 810ca5bf9d fix(player): 延长飘心并显示毫秒计时 2026-10-26 18:48:12 +08:00
Zhou Weidong 84b24775d6 fix(player): 恢复develop飘心实现 2026-10-26 18:38:59 +08:00
Zhou Weidong 9039b27c7f fix(player): 使用根Overlay渲染飘心 2026-10-26 18:35:30 +08:00
Zhou Weidong 30b43878ab fix(player): 使用根Overlay渲染飘心 2026-10-26 18:29:48 +08:00
Zhou Weidong e7afd316f8 fix(player): 放慢随机飘心并验证完整时长 2026-10-26 18:24:19 +08:00
Zhou Weidong f3dd4a9b5c fix(player): 恢复随机飘心粒子模型 2026-10-26 18:19:39 +08:00
Zhou Weidong 94b37ef00f fix(player): 优化双击飘心轨迹 2026-10-26 18:16:06 +08:00
Zhou Weidong bea50ab32f fix(player): 调整双击飘心动画 2026-10-26 18:11:01 +08:00
Zhou Weidong 63296d2a92 fix(player): 恢复独立播放页双击点赞 2026-10-26 18:06:55 +08:00
Zhou Weidong b291044acc fix(player): 收敛播放页入口加载态 2026-10-26 17:56:40 +08:00
Zhou Weidong ee4e573f4f fix(player): 播放页改为非阻塞加载 2026-10-26 17:43:57 +08:00
Zhou Weidong e8f192ccf8 fix(player): 顶层抓帧淡出揭幕 2026-10-26 17:31:59 +08:00
Zhou Weidong c8ec481fc9 fix(player): 预解码观看全集首帧 2026-10-26 17:27:43 +08:00
Zhou Weidong 1fc8c4924e fix(player): 调整观看全集淡入时长 2026-10-26 17:21:52 +08:00
Zhou Weidong 55fc2796a9 fix(player): 观看全集复用抓帧首屏 2026-10-26 17:20:24 +08:00
Zhou Weidong 4d3d067f77 fix(player): 规范预热回收日志并抓帧过渡 2026-10-26 17:13:56 +08:00
Zhou Weidong bb2c729a7e fix(player): 隔离播放资源所有权并修复黑屏 2026-10-26 17:03:19 +08:00
Zhou Weidong 54eb5a60a8 fix(player): 修复返回首页黑屏并恢复预热 2026-10-26 16:45:00 +08:00
Zhou Weidong 6667330ce0 fix(player): 分离首页与播放页播放上下文 2026-10-26 16:37:37 +08:00
Zhou Weidong 440188f261 fix(player): 修复切集返回推荐页黑屏 2026-10-26 16:20:31 +08:00
Zhou Weidong 9d2d3b97a1 fix(player): 独立全屏路由复用播放会话 2026-10-26 16:08:15 +08:00
Zhou Weidong 23e80dd637 fix(player): 复用播放会话进入全屏 2026-10-26 15:55:46 +08:00
Zhou Weidong ab4c246675 fix(player): 保持推荐页返回时播放状态 2026-10-26 15:44:28 +08:00
Zhou Weidong 2a32dec183 fix(player): 持久化竖滑播放画面层 2026-10-26 15:34:24 +08:00
Zhou Weidong 5dafc68775 test(player): 覆盖 feed 高清首帧解析 2026-10-26 15:20:45 +08:00
Zhou Weidong 80c962a95c fix(player): 收紧图片与快速切集资源占用 2026-10-26 15:10:02 +08:00
Zhou Weidong 2fef7470e0 fix(player): 使用高清首帧并稳定切集占位 2026-10-26 14:58:00 +08:00
Zhou Weidong 0cb0ab0613 fix(player): 重构单层过渡并阻止锁定集串播 2026-10-26 14:41:18 +08:00
Zhou Weidong 70d8e0f982 fix(player): 使用雪碧图预热首帧兜底 2026-10-26 14:21:52 +08:00
Zhou Weidong 48b4e67d34 fix(player): 预热直出并与视频统一封面布局 2026-10-26 14:12:42 +08:00
Zhou Weidong a3a79f7068 fix(player): 修正封面比例与首帧切换闪烁 2026-10-26 14:07:36 +08:00
Zhou Weidong 04f034817c fix(player): 优化封面过渡与换集起播 2026-10-26 14:01:42 +08:00
Zhou Weidong 516593499c fix(player): 修复竖滑上一集不自动播放 2026-10-26 13:43:43 +08:00
Zhou Weidong df36890b48 chore(analyze): 清理静态检查告警 2026-10-26 13:43:38 +08:00
Zhou Weidong 222bacdeeb fix(player): 优化上下滑切集黑屏 2026-10-26 13:15:56 +08:00
Zhou Weidong 43f3593218 chore(merge): 合并 iOS 无线数据选择框触发到 main 2026-10-26 09:35:29 +08:00
Zhou Weidong b83b5799e3 chore(merge): 合并 iOS 无线数据选择框触发 2026-10-26 09:35:29 +08:00
Zhou Weidong d4ae8f427f fix(ios): 用系统网络请求唤起无线数据选择
Flutter 的接口请求不会触发系统选择框,进入前台后补一次 URLSession 请求。
2026-10-26 09:35:29 +08:00
Zhou Weidong 0eca7ecc31 chore(merge): 合并 iOS 无线数据询问到 main 2026-10-25 21:23:26 +08:00
Zhou Weidong 98d7debfb5 chore(merge): 合并 iOS 无线数据询问 2026-10-25 21:23:26 +08:00
Zhou Weidong b5e9deb4bd feat(ios): 首次启动询问无线数据权限
iOS 在首个接口请求前等待系统无线数据选择,避免未授权时请求被静默拒绝。
2026-10-25 21:23:25 +08:00
Zhou Weidong af5d8f9d80 chore(merge): 合并客户端多语言筛选到 main 2026-10-25 19:52:30 +08:00
Zhou Weidong 47df002d8e chore(merge): 合并客户端多语言筛选与注销原因修正 2026-10-24 17:22:57 +08:00
Zhou Weidong 4f1feeadbd fix(app): 注销原因对齐到输入框顶部 2026-10-24 17:20:24 +08:00
Zhou Weidong 8b50b5bd06 feat(app): 客户端展示多语言并支持筛选分页 2026-10-24 17:20:24 +08:00
Zhou Weidong b84a5368d6 chore(merge): 将 release/v1.0.0 同步到 main 2026-10-23 13:07:33 +08:00
Zhou Weidong 67e178c89d chore(merge): 将 develop 同步到 release/v1.0.0 2026-10-23 13:07:33 +08:00
Zhou Weidong 8481611372 chore(merge): 合并错误码三语文案 2026-10-23 13:05:29 +08:00
Zhou Weidong b1e95b370c feat(app): 按错误码展示中英印尼文案 2026-10-23 13:05:29 +08:00
Zhou Weidong 0ef0d46919 chore(merge): 合并未使用注销原因清理 2026-10-23 12:45:30 +08:00
Zhou Weidong b4d38f8e10 refactor(app): 删除未使用的注销原因组件 2026-10-23 12:45:30 +08:00
Zhou Weidong c034627fdb chore(merge): 合并播放协议搜索与空状态 2026-10-23 12:38:47 +08:00
Zhou Weidong ef29fc5f15 feat(app): 完善播放协议搜索与空状态 2026-10-23 12:38:47 +08:00
Zhou Weidong 671e0ce143 merge: 将远程 main 同步到 develop 2026-10-23 08:09:24 +08:00
Zhou Weidong 0f1f64a09f merge: 将 release/v1.0.0 同步到 main 2026-10-23 03:22:26 +08:00
Zhou Weidong d29ae20491 merge: 将客户端调整同步到 release/v1.0.0 2026-10-23 03:21:53 +08:00
Zhou Weidong 12e08a89a4 fix(player): 提高解锁金币标签对比度 2026-10-23 03:18:24 +08:00
Zhou Weidong e0ba54f23b fix(cover): 封面占位改为品牌标识 2026-10-23 03:18:24 +08:00
Zhou Weidong 5043d6c25b fix(l10n): 缩短印尼语全部短剧文案 2026-10-23 03:18:24 +08:00
Zhou Weidong ab6800aba2 fix(search): 避免键盘顶起搜索页 2026-10-23 03:18:24 +08:00
Zhou Weidong f8500bbaca fix(player): 完善进度预览清屏与下拉刷新 2026-10-23 03:18:24 +08:00
Zhou Weidong 9b1afbdb36 feat(recommend): 剧单更多进入两列详情页 2026-10-23 03:18:24 +08:00
Zhou Weidong f4cf7bc2dd fix(home): 调整榜单版式并去掉二级分类 2026-10-23 03:18:24 +08:00
Zhou Weidong 60489d6dca fix(ios): 修复逐帧请求头编译失败 2026-10-23 03:18:23 +08:00
Zhou Weidong 44d3bcba18 style(empty): 放大空状态图标并降低亮度 2026-10-23 01:10:59 +08:00
Zhou Weidong f206977ad8 fix(config): 开发服务地址改为当前局域网 2026-10-23 01:10:59 +08:00
Zhou Weidong f3d64f6fb5 fix(auth): 登录页靠上排列并支持外部失焦 2026-10-23 01:10:59 +08:00
Zhou Weidong 6318ca24ec feat(splash): 缩短开屏停留并统一开屏图 2026-10-23 01:06:07 +08:00
Zhou Weidong b513ac0a9c merge: 调整品牌开屏与纯黑原生启动 2026-10-22 23:15:36 +08:00
Zhou Weidong 25d90e1383 feat: 调整品牌开屏与纯黑原生启动 2026-10-22 23:15:27 +08:00
Zhou Weidong 7804809d83 merge: 开屏后直接展示首页骨架 2026-10-22 23:11:13 +08:00
Zhou Weidong 5f278a3510 feat: 开屏后直接展示首页骨架 2026-10-22 23:11:03 +08:00
Zhou Weidong 56d7abc6b1 merge: 保留系统开屏并更新品牌素材 2026-10-22 23:04:31 +08:00
Zhou Weidong 345cc2ad28 feat: 保留系统开屏并更新品牌素材 2026-10-22 23:04:23 +08:00
Zhou Weidong 41bd4683c3 merge: 更新 App 图标与开屏素材 2026-10-22 22:59:14 +08:00
Zhou Weidong d868ce9336 feat: 更新 App 图标与开屏素材 2026-10-22 22:59:07 +08:00
Zhou Weidong 928ac1de19 merge: 增加搜索结果下拉刷新 2026-10-22 22:53:01 +08:00
Zhou Weidong 6b3d870c93 feat: 增加搜索结果下拉刷新 2026-10-22 22:52:50 +08:00
Zhou Weidong 281f13a810 merge: 调整搜索结果两列布局 2026-10-22 22:50:55 +08:00
Zhou Weidong 8637f5da21 style: 调整搜索结果两列布局 2026-10-22 22:50:47 +08:00
Zhou Weidong 0319f8a6a8 merge: 统一首页与推荐搜索入口 2026-10-22 22:48:29 +08:00
Zhou Weidong 9aa797504b feat: 统一首页与推荐搜索入口 2026-10-22 22:48:21 +08:00
Zhou Weidong 933333b0fe merge: 使用点击序号防止双击误暂停 2026-10-22 22:39:30 +08:00
Zhou Weidong bbc759473c fix: 使用点击序号防止双击误暂停 2026-10-22 22:39:21 +08:00
Zhou Weidong 9ddd7fc833 merge: 修复播放器重复暂停手势 2026-10-22 22:34:26 +08:00
Zhou Weidong 5794dd4e4d fix: 修复播放器重复暂停手势 2026-10-22 22:34:18 +08:00
Zhou Weidong 76c4b528d3 merge: 修正双击点赞与心形联动 2026-10-22 22:30:11 +08:00
Zhou Weidong 68ccfa21c0 fix: 修正双击点赞与心形联动 2026-10-22 22:30:01 +08:00
Zhou Weidong 78d0166eb7 merge: 增加播放器双击点赞与飘心 2026-10-22 22:25:22 +08:00
Zhou Weidong 4140c4eb50 feat: 增加播放器双击点赞与飘心 2026-10-22 22:25:14 +08:00
Zhou Weidong e2cc409e5a merge: 重做首页热播榜布局 2026-10-22 22:20:01 +08:00
Zhou Weidong 9b83283689 feat: 重做首页热播榜布局 2026-10-22 22:19:55 +08:00
Zhou Weidong db8433ace3 merge: 统一下拉与加载指示器 2026-10-22 22:07:21 +08:00
Zhou Weidong 6f1da5aa39 style: 统一下拉与加载指示器 2026-10-22 22:07:13 +08:00
Zhou Weidong 397483d339 merge: 统一全站深色视觉 2026-10-22 22:01:45 +08:00
Zhou Weidong 37e35f4b47 style: 统一全站深色视觉 2026-10-22 22:01:36 +08:00
Zhou Weidong 86aa7e0f4f merge: 增加推荐剧单渐变背景 2026-10-22 21:52:20 +08:00
Zhou Weidong 604ab60b63 style: 增加推荐剧单渐变背景 2026-10-22 21:52:13 +08:00
Zhou Weidong 0b3f362093 merge: 修正我的 UID 行高度 2026-10-22 21:49:54 +08:00
Zhou Weidong 19acbb2278 fix: 修正我的 UID 行高度 2026-10-22 21:49:47 +08:00
Zhou Weidong ce3ecc4d21 merge: 收紧我的用户区信息间距 2026-10-22 21:46:57 +08:00
Zhou Weidong c604cc1333 style: 收紧我的用户区信息间距 2026-10-22 21:46:49 +08:00
Zhou Weidong e7367e99be merge: 补充我的页内容区块 2026-10-22 21:44:36 +08:00
Zhou Weidong 9953c955e3 feat: 补充我的页内容区块 2026-10-22 21:44:03 +08:00
Zhou Weidong 7971ac18fc merge: 增强我的页渐变与图标配色 2026-10-22 21:38:55 +08:00
Zhou Weidong a81bec4d47 style: 增强我的页渐变与图标配色 2026-10-22 21:38:49 +08:00
Zhou Weidong 647f6afd77 merge: 改造我的页渐变与图标网格 2026-10-22 21:35:20 +08:00
Zhou Weidong 9785a1baf3 feat: 改造我的页渐变与图标网格 2026-10-22 21:35:14 +08:00
Zhou Weidong e70ca03c03 merge: 调整继续观看按钮样式 2026-10-22 21:28:41 +08:00
Zhou Weidong 95fb2ff882 style: 调整继续观看按钮样式 2026-10-22 21:28:36 +08:00
Zhou Weidong b6e5f808d6 merge: 优化追剧页切换与编辑按钮 2026-10-22 21:26:01 +08:00
Zhou Weidong 3f3dc9263c fix: 优化追剧页切换与编辑按钮 2026-10-22 21:25:55 +08:00
Zhou Weidong f67b8bd514 merge: 调整追剧加号颜色 2026-10-22 21:21:58 +08:00
Zhou Weidong 4d304cb549 style: 调整追剧加号颜色 2026-10-22 21:21:52 +08:00
Zhou Weidong 201d58e8f4 merge: 调整追剧加号卡片顺序 2026-10-22 21:20:12 +08:00
Zhou Weidong 091ea358bd fix: 调整追剧加号卡片顺序 2026-10-22 21:20:06 +08:00
Zhou Weidong af828d2c46 merge: 播放历史单列列表 2026-10-22 21:18:08 +08:00
Zhou Weidong c14d89ffce feat: 调整播放历史为单列列表 2026-10-22 21:18:02 +08:00
Zhou Weidong b185200e8c merge: 追剧页双列表与三列卡片 2026-10-22 21:14:31 +08:00
Zhou Weidong 2fac46f248 feat: 改造追剧页双列表与三列卡片 2026-10-22 21:14:23 +08:00
Zhou Weidong cffb68809c merge: 更正筛选按钮所属页面 2026-10-22 21:06:00 +08:00
Zhou Weidong 13d70fcef2 fix(recommend): 更正筛选按钮所属页面 2026-10-22 21:05:51 +08:00
Zhou Weidong bf20e44b85 merge: 移除二级标签筛选按钮 2026-10-22 21:02:40 +08:00
Zhou Weidong 9cf1efc316 fix(home): 移除二级标签筛选按钮 2026-10-22 21:02:29 +08:00
Zhou Weidong 3655d94869 merge: 修正窄筛选行横向居中 2026-10-22 20:59:14 +08:00
Zhou Weidong 196ce69a12 fix(recommend): 修正窄筛选行横向居中 2026-10-22 20:59:02 +08:00
Zhou Weidong c7a0e277f2 merge: 统一全部短剧筛选左对齐 2026-10-22 20:56:53 +08:00
Zhou Weidong 57c9e6b477 fix(recommend): 统一全部短剧筛选左对齐 2026-10-22 20:56:45 +08:00
Zhou Weidong 64bc1997ba merge: 修正筛选左起始与横向滑动 2026-10-22 20:53:16 +08:00
Zhou Weidong 21bb716af4 fix(recommend): 修正筛选左起始与横向滑动 2026-10-22 20:53:07 +08:00
Zhou Weidong a40adbb99a merge: 按截图重做全部短剧筛选区 2026-10-22 20:50:03 +08:00
Zhou Weidong d79e200650 style(recommend): 按截图重做全部短剧筛选区 2026-10-22 20:49:54 +08:00
Zhou Weidong 3b9ff8a306 merge: 对齐全部短剧筛选行 2026-10-22 20:46:21 +08:00
Zhou Weidong af4d918938 fix(recommend): 对齐全部短剧筛选行 2026-10-22 20:46:13 +08:00
Zhou Weidong 3289012f64 merge: 固定全部短剧筛选区并统一间距 2026-10-22 20:43:22 +08:00
Zhou Weidong e147002e00 fix(recommend): 固定全部短剧筛选区并统一间距 2026-10-22 20:43:15 +08:00
Zhou Weidong 099d05064e merge: 重排全部短剧筛选与网格 2026-10-22 20:39:09 +08:00
Zhou Weidong 4eb411ef28 style(recommend): 重排全部短剧筛选与网格 2026-10-22 20:39:00 +08:00
Zhou Weidong 14885d9efa merge: 支持横向切换三个内容页 2026-10-22 20:35:06 +08:00
Zhou Weidong 17faa749b2 feat(recommend): 支持横向切换三个内容页 2026-10-22 20:34:58 +08:00
Zhou Weidong 5fce65ac57 merge: 修正标题与观看全集跳转 2026-10-22 20:31:48 +08:00
Zhou Weidong 357492acb2 fix(recommend): 修正标题与观看全集跳转 2026-10-22 20:31:41 +08:00
Zhou Weidong 23799353b5 merge: 修复进度条拖动树稳定性 2026-10-22 20:27:07 +08:00
Zhou Weidong cf6d12b7c8 fix(player): 修复进度条拖动树稳定性 2026-10-22 20:26:58 +08:00
Zhou Weidong 5af6caea6e merge: 修正进度条按下与拖动状态 2026-10-22 20:23:32 +08:00
Zhou Weidong d66ff13d0f fix(player): 修正进度条按下与拖动状态 2026-10-22 20:23:24 +08:00
Zhou Weidong 7599da1d40 merge: 收窄拖动清屏范围并提升灵敏度 2026-10-22 20:19:29 +08:00
Zhou Weidong 04f1f5a73f fix(recommend): 收窄拖动清屏范围并提升灵敏度 2026-10-22 20:19:21 +08:00
Zhou Weidong f5721592f7 merge: 收窄观看全集按钮 2026-10-22 20:16:22 +08:00
Zhou Weidong 5abee5f69a style(recommend): 收窄观看全集按钮 2026-10-22 20:16:13 +08:00
Zhou Weidong 3690fb1bc4 merge: 修正进度拖动与底栏联动 2026-10-22 20:15:14 +08:00
Zhou Weidong 2765343340 fix(recommend): 修正进度拖动与底栏联动 2026-10-22 20:15:07 +08:00
Zhou Weidong afe7a49ef0 merge: 恢复进度拖动前浮层状态 2026-10-22 20:08:53 +08:00
Zhou Weidong b6666290ba fix(recommend): 恢复进度拖动前浮层状态 2026-10-22 20:08:45 +08:00
Zhou Weidong c478eebb0e merge: 修正进度取消状态更新编译错误 2026-10-22 20:04:51 +08:00
Zhou Weidong d731b36c1f fix(recommend): 修正进度取消状态更新编译错误 2026-10-22 20:04:43 +08:00
Zhou Weidong ac4a800b78 merge: 稳定进度拖动与清屏状态 2026-10-22 20:02:02 +08:00
Zhou Weidong 30b55afdd4 fix(recommend): 稳定进度拖动与清屏状态 2026-10-22 20:01:54 +08:00
Zhou Weidong 76c4f1d411 merge: 精简底部信息并优化进度拖动 2026-10-22 19:55:16 +08:00
Zhou Weidong d66ac8327e fix(recommend): 精简底部信息并优化进度拖动 2026-10-22 19:55:07 +08:00
Zhou Weidong ccda41b15b merge: 修正热播信息行与操作栏位置 2026-10-22 19:46:52 +08:00
Zhou Weidong 0c06a6dccd fix(recommend): 修正热播信息行与操作栏位置 2026-10-22 19:46:44 +08:00
Zhou Weidong 8737937875 merge: 对齐沉浸式推荐页视觉布局 2026-10-22 19:42:33 +08:00
Zhou Weidong 5dd3492aea feat(recommend): 对齐沉浸式推荐页视觉布局 2026-10-22 19:42:22 +08:00
Zhou Weidong 77689a3eaa merge: 再次加宽首页胶片带素材 2026-10-22 19:34:07 +08:00
Zhou Weidong b22ac6ab04 chore(home): 再次加宽首页胶片带素材 2026-10-22 19:34:00 +08:00
Zhou Weidong 2fdff34ed0 merge: 加宽首页胶片带素材 2026-10-22 19:32:25 +08:00
Zhou Weidong f9e9334934 chore(home): 加宽首页胶片带素材 2026-10-22 19:32:20 +08:00
Zhou Weidong 856a825a74 merge: 更新首页胶片带导航素材 2026-10-22 19:31:01 +08:00
Zhou Weidong df960f84ea chore(home): 更新首页胶片带导航素材 2026-10-22 19:30:52 +08:00
Zhou Weidong bfe1a2f9e8 merge: 放大底部导航图标与文字 2026-10-22 19:30:11 +08:00
Zhou Weidong fd2b0dbf23 style(home): 放大底部导航图标与文字 2026-10-22 19:30:03 +08:00
Zhou Weidong 5860302ce7 merge: 首页图标区分实心选中态 2026-10-22 19:29:18 +08:00
Zhou Weidong 4592b5dfba feat(home): 首页图标区分实心选中态 2026-10-22 19:29:12 +08:00
Zhou Weidong 6a10992812 merge: 更新首页导航摄影机素材 2026-10-22 19:26:36 +08:00
Zhou Weidong 8ae2ed4d93 chore(home): 更新首页导航摄影机素材 2026-10-22 19:26:31 +08:00
Zhou Weidong b1963a718e merge: 使用胶片盘图片导航图标 2026-10-22 19:24:40 +08:00
Zhou Weidong f47e4dd298 feat(home): 使用胶片盘图片导航图标 2026-10-22 19:24:34 +08:00
Zhou Weidong c24518c1a4 merge: 更换底部首页导航图标 2026-10-22 19:22:09 +08:00
Zhou Weidong 97e2db1b7b feat(home): 更换底部首页导航图标 2026-10-22 19:22:01 +08:00
Zhou Weidong bd4bdf8aea merge: 修正二级筛选按钮右对齐 2026-10-22 19:19:56 +08:00
Zhou Weidong 38643e7fde fix(home): 修正二级筛选按钮右对齐 2026-10-22 19:19:50 +08:00
Zhou Weidong 2cfa655750 merge: 修正 ChangeNotifier 监听生命周期 2026-10-22 19:14:41 +08:00
Zhou Weidong 8936c3d6eb fix(client): 修正 ChangeNotifier 监听生命周期 2026-10-22 19:14:35 +08:00
Zhou Weidong 4291031ace merge: 优化短剧列表下拉刷新体验 2026-10-22 19:11:36 +08:00
Zhou Weidong 7945f3ffed fix(home): 优化短剧列表下拉刷新体验 2026-10-22 19:11:30 +08:00
Zhou Weidong f69a27bb80 merge: 修复分类分页控制器初始化 2026-10-22 19:07:41 +08:00
Zhou Weidong d6f1cf2a2c fix(home): 修复分类分页控制器初始化 2026-10-22 19:07:32 +08:00
Zhou Weidong 6c1f3b9eae merge: 支持滑动切换首页分类 2026-10-22 19:06:05 +08:00
Zhou Weidong 5282194ca5 feat(home): 支持滑动切换首页分类 2026-10-22 19:05:55 +08:00
Zhou Weidong b921f79a71 merge: 更新搜索栏图标比例与间距 2026-10-22 19:03:52 +08:00
Zhou Weidong 4fa9d36b45 assets(home): 更新搜索栏图标比例与间距 2026-10-22 19:03:42 +08:00
Zhou Weidong 024f5f3736 merge: 统一搜索栏图标视觉比例 2026-10-22 19:00:08 +08:00
Zhou Weidong 403cce0e3b assets(home): 统一搜索栏图标视觉比例 2026-10-22 18:59:58 +08:00
Zhou Weidong ae8546fcf9 merge: 收紧二级标签间距 2026-10-22 18:59:19 +08:00
Zhou Weidong 5a4e910554 fix(home): 收紧二级标签间距 2026-10-22 18:59:11 +08:00
Zhou Weidong 147662d085 merge: 添加搜索栏皇冠与礼物图标 2026-10-22 18:58:11 +08:00
Zhou Weidong 714e5caec3 assets(home): 添加搜索栏皇冠与礼物图标 2026-10-22 18:58:03 +08:00
Zhou Weidong c1d4b61936 merge: 使用图片渲染搜索栏入口 2026-10-22 18:52:51 +08:00
Zhou Weidong 953ddec587 fix(home): 使用图片渲染搜索栏入口 2026-10-22 18:52:43 +08:00
Zhou Weidong afd8927a9f merge: 放大搜索栏右侧图标 2026-10-22 18:48:29 +08:00
Zhou Weidong 8feede1763 fix(home): 放大搜索栏右侧图标 2026-10-22 18:48:20 +08:00
Zhou Weidong f4e5af19fe merge: 调整搜索栏右侧入口样式 2026-10-22 18:46:27 +08:00
Zhou Weidong 909762ac3b fix(home): 调整搜索栏右侧入口样式 2026-10-22 18:46:19 +08:00
Zhou Weidong acae05d354 merge: 修复样式常量与测试类型错误 2026-10-22 18:43:11 +08:00
Zhou Weidong ba0ce2f8c0 fix(home): 修复样式常量与测试类型错误 2026-10-22 18:43:02 +08:00
Zhou Weidong 62cb4d9809 merge: 稳定二级标签切换布局 2026-10-22 18:41:05 +08:00
Zhou Weidong b3ea85df41 fix(home): 稳定二级标签切换布局 2026-10-22 18:40:56 +08:00
Zhou Weidong 76296f2be0 merge: 对齐二级筛选图标中心线 2026-10-22 18:38:48 +08:00
Zhou Weidong 4304727ffd fix(home): 对齐二级筛选图标中心线 2026-10-22 18:38:41 +08:00
Zhou Weidong 8ac781bb5e merge: 重绘二级筛选三线图标 2026-10-22 18:37:22 +08:00
Zhou Weidong 448010115f fix(home): 重绘二级筛选三线图标 2026-10-22 18:37:16 +08:00
Zhou Weidong 8197abd55d merge: 调整二级筛选按钮尺寸 2026-10-22 18:36:35 +08:00
Zhou Weidong c6f3e3fa68 fix(home): 调整二级筛选按钮尺寸 2026-10-22 18:36:30 +08:00
Zhou Weidong 368fbe7ef1 merge: 调整短剧封面比例 2026-10-22 18:34:17 +08:00
Zhou Weidong 91f4de01a1 fix(home): 调整短剧封面比例 2026-10-22 18:34:11 +08:00
Zhou Weidong 3886c577eb merge: 调整封面播放量图标与位置 2026-10-22 18:33:38 +08:00
Zhou Weidong 0e7dcf4739 fix(home): 调整封面播放量图标与位置 2026-10-22 18:33:31 +08:00
Zhou Weidong f20e7ba7c5 merge: 调整封面播放量与状态角标 2026-10-22 18:31:09 +08:00
Zhou Weidong 8375d8b255 fix(home): 收紧封面状态角标 2026-10-22 18:31:03 +08:00
Zhou Weidong 972e542e5c fix(home): 调整封面播放量位置 2026-10-22 18:30:33 +08:00
Zhou Weidong e11f55777e merge: 调整一级分类未选字号 2026-10-22 18:29:28 +08:00
Zhou Weidong 325f7c1f5d fix(home): 调整一级分类未选字号 2026-10-22 18:29:22 +08:00
Zhou Weidong da69a058b4 merge: 优化二级标签选中样式 2026-10-22 18:28:51 +08:00
Zhou Weidong 1adcc16727 fix(home): 优化二级标签选中样式 2026-10-22 18:28:44 +08:00
Zhou Weidong 5da836dded merge: 调整播放量行内展示 2026-10-22 18:28:00 +08:00
Zhou Weidong f7782aef14 fix(home): 调整播放量行内展示 2026-10-22 18:27:55 +08:00
Zhou Weidong c9fce7745e merge: 修正封面角标与题材标签 2026-10-22 18:26:59 +08:00
Zhou Weidong e16faa258a fix(home): 修正封面角标与题材标签 2026-10-22 18:26:52 +08:00
Zhou Weidong 3abf69e886 merge: 收紧首页顶部区间距 2026-10-22 18:23:44 +08:00
Zhou Weidong ce310afa89 fix(home): 收紧首页顶部区间距 2026-10-22 18:23:37 +08:00
Zhou Weidong d3fdb3c631 merge: 收紧短剧卡片间距 2026-10-22 18:22:52 +08:00
Zhou Weidong 9543d850c8 fix(home): 收紧短剧卡片间距 2026-10-22 18:22:46 +08:00
Zhou Weidong 81dd33e8c7 merge: 调整首页分类与卡片标签样式 2026-10-22 18:22:11 +08:00
Zhou Weidong 4f6116896f fix(home): 调整首页分类与卡片标签样式 2026-10-22 18:22:05 +08:00
Zhou Weidong 7446431511 merge: 修正首页分类与卡片布局 2026-10-22 18:14:40 +08:00
Zhou Weidong aa4870cce6 fix(home): 修正首页分类与卡片布局 2026-10-22 18:14:33 +08:00
Zhou Weidong 10477562d4 merge: 对齐短剧列表字号与间距 2026-10-22 18:06:09 +08:00
Zhou Weidong d7a59e5a27 style(client): 对齐短剧列表字号与间距 2026-10-22 18:06:03 +08:00
Zhou Weidong d03dcf162e merge: 移除全局两侧悬浮元素 2026-10-22 17:58:51 +08:00
Zhou Weidong 9cf832b318 refactor(navigation): 移除全局两侧悬浮元素 2026-10-22 17:58:43 +08:00
Zhou Weidong 5cead7ce00 merge: 重排我的页并新增迷你播放器 2026-10-22 17:45:05 +08:00
Zhou Weidong 79bdef4b43 docs(mine): 回填我的页改版记录 2026-10-22 17:44:59 +08:00
Zhou Weidong cbc6d80cd4 feat(mine): 重排我的页并新增迷你播放器 2026-10-22 17:44:48 +08:00
Zhou Weidong 5aa8170951 merge: 修正推荐子页内容映射 2026-10-22 17:37:03 +08:00
Zhou Weidong d06967c6ab fix(recommend): 修正推荐子页内容映射 2026-10-22 17:36:58 +08:00
Zhou Weidong af6b9bb3db merge: 追剧页新增三栏与双列网格 2026-10-22 17:32:17 +08:00
Zhou Weidong 97e3f4fc47 feat(following): 新增追剧三栏与双列网格 2026-10-22 17:32:09 +08:00
Zhou Weidong 73380ee090 merge: 推荐页新增分区流与全部短剧 2026-10-22 17:23:41 +08:00
Zhou Weidong f9aec827cf docs(recommend): 回填推荐页改版记录 2026-10-22 17:23:36 +08:00
Zhou Weidong f1b5418954 feat(recommend): 新增分区流全部短剧与剧单 2026-10-22 17:23:22 +08:00
Zhou Weidong 0ef2438100 merge: 统一我的页深色视觉风格 2026-10-22 17:07:32 +08:00
Zhou Weidong e40f4779c0 docs(mine): 回填我的页视觉统一记录 2026-10-22 17:07:27 +08:00
Zhou Weidong 83129acae4 feat(mine): 统一我的页深色视觉风格 2026-10-22 17:07:09 +08:00
Zhou Weidong 47bf6458e8 merge: 首页切换三列探索网格 2026-10-22 17:04:08 +08:00
Zhou Weidong 1bb227e083 docs(home): 回填首页网格改版记录 2026-10-22 17:04:03 +08:00
Zhou Weidong dd96792e33 feat(home): 首页改为三列探索网格 2026-10-22 17:03:45 +08:00
Zhou Weidong 6dea9d479e merge: 对齐四 Tab 底栏布局规格 2026-10-22 16:38:29 +08:00
Zhou Weidong 5362f97744 docs(nav): 回填底栏布局规格记录 2026-10-22 16:38:25 +08:00
Zhou Weidong 81787dc3aa style(nav): 对齐四 Tab 底栏布局规格 2026-10-22 16:38:14 +08:00
Zhou Weidong 667fdc5f0a merge: 合并四 Tab 底部导航 2026-10-22 16:21:39 +08:00
Zhou Weidong 729c226c95 docs(nav): 回填四 Tab 底部导航记录 2026-10-22 16:21:30 +08:00
Zhou Weidong 91e869d589 feat(nav): 底部导航改为首页推荐追剧我的 2026-10-22 16:21:05 +08:00
Zhou Weidong ec3cb8f163 merge: 客户端切换深色视觉风格 2026-10-22 13:49:38 +08:00
Zhou Weidong 4bc04fd8ae style(theme): 客户端切换深色视觉风格 2026-10-22 13:49:20 +08:00
Zhou Weidong 295c21513c merge: 观看历史改用分页契约 2026-10-22 13:21:36 +08:00
Zhou Weidong 5b2d8445dc refactor(watch): 观看历史改用分页契约 2026-10-22 13:21:27 +08:00
Zhou Weidong 1924b8626f chore(merge): 修复我的收藏与观看历史列表不显示 2026-10-22 13:10:03 +08:00
Zhou Weidong a48433f01d fix(profile): 修复我的收藏与观看历史列表不显示 2026-10-22 13:09:55 +08:00
Zhou Weidong f2ae0c70b6 merge: 收藏卡片展示简介 2026-10-22 12:45:31 +08:00
Zhou Weidong 57f3029018 feat(library): 收藏卡片展示简介 2026-10-22 12:45:27 +08:00
Zhou Weidong 9093700a04 merge: 优化观看历史标题加载 2026-10-22 12:43:06 +08:00
Zhou Weidong 78114fa46d fix(library): 优化观看历史标题加载 2026-10-22 12:42:58 +08:00
Zhou Weidong abfca7a3be merge: 收藏与观看历史支持分页 2026-10-22 12:34:39 +08:00
Zhou Weidong 235b50598a feat(library): 收藏与观看历史支持分页 2026-10-22 12:34:27 +08:00
Zhou Weidong 63d3b0f7ca merge: 增加 Android Studio 调试 HTTP 日志 2026-10-22 12:16:02 +08:00
Zhou Weidong 7a588898fa feat(debug): 增加 HTTP 请求响应日志 2026-10-22 12:15:51 +08:00
Zhou Weidong dc4b60270d merge: 合并进度条雪碧图预览 2026-10-22 11:40:30 +08:00
Zhou Weidong 12b733d111 feat(player): 进度条拖动预览改用雪碧图 2026-10-22 11:40:23 +08:00
Zhou Weidong 89a94f85c2 merge: 合并进度条画面预览 2026-10-22 11:03:25 +08:00
Zhou Weidong 51c56d884b feat(player): 增加进度条拖动画面预览 2026-10-22 11:03:17 +08:00
Zhou Weidong d06dce9753 merge: 合并播放入口无缓冲提示 2026-10-22 10:47:28 +08:00
Zhou Weidong 7ad4681049 fix(player): 收敛播放入口缓冲提示 2026-10-22 10:47:16 +08:00
Zhou Weidong f302b396b7 merge: 合并 Seek 播放位置修复 2026-10-22 10:41:12 +08:00
Zhou Weidong 5268adc999 fix(player): 记住拖动后的实际播放位置 2026-10-22 10:41:01 +08:00
Zhou Weidong 47ca21406a merge: 合并播放位置连续性修复 2026-10-22 10:28:07 +08:00
Zhou Weidong 759a02375a fix(player): 修复详情往返播放位置丢失 2026-10-22 10:27:58 +08:00
Zhou Weidong fe49c6d8b2 merge: 合并屏幕常亮与自动续播 2026-10-22 10:17:53 +08:00
Zhou Weidong a25c500371 feat(player): 保持屏幕常亮并自动恢复播放 2026-10-22 10:17:43 +08:00
Zhou Weidong 5bf10221a8 merge: 合并播放器加载与锁定进度条优化 2026-10-22 10:09:44 +08:00
Zhou Weidong 623c184243 fix(player): 优化加载指示器与锁定进度条 2026-10-22 10:09:33 +08:00
Zhou Weidong 7b068e8eb4 merge: 合并登录异常居中吐司 2026-10-22 09:58:08 +08:00
Zhou Weidong d076c57283 fix(auth): 登录异常改为居中吐司 2026-10-22 09:57:57 +08:00
Zhou Weidong 11e37a92b5 merge: 合并登录异常居中弹窗 2026-10-22 09:50:15 +08:00
Zhou Weidong b0002abf82 feat(auth): 登录异常改为居中弹窗 2026-10-22 09:50:00 +08:00
Vicky a1c0af3f00 merge: 将 release/v1.0.0 同步到 main 2026-10-21 20:07:35 +08:00
Vicky b5bfc1d7b2 merge: 将 develop 同步到 release/v1.0.0 2026-10-21 20:07:30 +08:00
Zhou Weidong 9b78e43702 merge: 合并 K98.3 视频控制器释放修复 2026-10-21 20:00:34 +08:00
Zhou Weidong f3ec12e3c3 fix(player): 修复切换频道时复用已释放的视频控制器 2026-10-21 20:00:15 +08:00
Zhou Weidong 45a53180d9 merge: 合并 K98.2 feed 更新构建期修复 2026-10-21 19:45:54 +08:00
Zhou Weidong d227ac69fb fix(home): 修复 feed 更新触发的 build 期 setState 2026-10-21 19:45:38 +08:00
Zhou Weidong 862b7e92dd merge: 合并 K98.1 列表直达播放页 2026-10-21 19:33:36 +08:00
Zhou Weidong 8b53adc2be feat(library): 收藏与观看记录直接进入播放页 2026-10-21 19:33:22 +08:00
Zhou Weidong ecdff54f2e merge: 合并 K98 登录键盘避让 2026-10-21 19:27:21 +08:00
Zhou Weidong 6c2f77926b fix(auth): 登录注册与手机号登录键盘避让 2026-10-21 19:26:46 +08:00
Vicky 1d2ba93e48 merge: 同步 release 剔除 x86_64 到 main 2026-10-21 19:13:33 +08:00
Vicky c08df9af40 merge: 同步 release 剔除 x86_64 到 release/v1.0.0 2026-10-21 19:13:28 +08:00
Zhou Weidong 86588d7faa build(android): release 包显式剔除 x86_64 原生库 2026-10-21 19:13:27 +08:00
Vicky be166f5856 merge: 同步生产包 ABI 约束到 main 2026-10-21 19:10:11 +08:00
Vicky e531c78997 merge: 同步生产包 ABI 约束到 release/v1.0.0 2026-10-21 19:10:10 +08:00
Zhou Weidong 91a622733c build(android): 生产包 abiFilters 仅保留 arm64-v8a 与 armeabi-v7a 2026-10-21 19:10:05 +08:00
Vicky 8ed6cd9fd6 merge: 同步生产打包单包说明到 main 2026-10-21 19:07:03 +08:00
Vicky c1d55b91a2 merge: 同步生产打包单包说明到 release/v1.0.0 2026-10-21 19:06:57 +08:00
Zhou Weidong 08980ff09b docs(readme): 生产包合并 arm64-v8a 与 armeabi-v7a 单包输出 2026-10-21 19:06:56 +08:00
Vicky 3dcfa02372 merge: 同步 README 生产打包说明到 main 2026-10-21 19:03:06 +08:00
Vicky b6826f7483 merge: 同步 README 生产打包说明到 release/v1.0.0 2026-10-21 19:03:01 +08:00
Zhou Weidong 30a6bcccfc docs(readme): 记录 Android 生产打包只保留 arm64-v8a 与 armeabi-v7a 2026-10-21 19:02:57 +08:00
Vicky cd3d84cf9a merge: 将 release/v1.0.0 同步到 main 2026-10-21 18:58:26 +08:00
Vicky 010ce326ea merge: 将 develop 同步到 release/v1.0.0 2026-10-21 18:58:12 +08:00
Zhou Weidong a42d3a26ac merge: 合并 K97.1 剧场搜索框圆角调整 2026-10-21 18:55:00 +08:00
Zhou Weidong c278503d0e fix(theater): 减小剧场搜索框圆角 2026-10-21 18:54:49 +08:00
Zhou Weidong 76f347cc35 merge: 合并 K97 iOS 评论键盘避让 2026-10-21 18:47:04 +08:00
Zhou Weidong 349ea12404 fix(comment): 修复 iOS 评论输入弹窗键盘避让 2026-10-21 18:46:43 +08:00
Zhou Weidong dfc33027d8 merge: 合并 K96 剧场分类分页 2026-10-21 18:24:28 +08:00
Zhou Weidong 13a14f3e85 feat(theater): 剧场分类列表服务端分页 2026-10-21 18:24:14 +08:00
Zhou Weidong f8769f2e1d Revert "merge: 合并 K95.2 首切频道封面过渡"
This reverts commit a2a491d056, reversing
changes made to 838ca2ce2a.
2026-10-21 18:07:25 +08:00
Zhou Weidong a2a491d056 merge: 合并 K95.2 首切频道封面过渡 2026-10-21 18:06:17 +08:00
Zhou Weidong c9adf8b399 fix(home): 频道切换用封面过渡 2026-10-21 18:06:09 +08:00
Zhou Weidong 838ca2ce2a merge: 合并 K95.1 首次切换频道无感 2026-10-21 17:54:10 +08:00
Zhou Weidong 677e49fa6e fix(home): 首切频道预取并保留播放画面 2026-10-21 17:53:58 +08:00
Zhou Weidong dfa79bed88 merge: 合并 K95 频道切换加载修复 2026-10-21 17:46:49 +08:00
Zhou Weidong 3c4d65a9b2 fix(home): 修复频道快速切换卡 loading 2026-10-21 17:46:42 +08:00
Zhou Weidong c8392b2f2a merge: 合并 K94 首页类型 Feed 2026-10-21 17:34:02 +08:00
Zhou Weidong ae61d9ec4c feat(home): 首页按内容类型分频道 Feed 并支持分页 2026-10-21 17:33:52 +08:00
Zhou Weidong 4d0be47ee3 merge: 合并 K90 点赞与评论按集 2026-10-21 16:15:44 +08:00
Zhou Weidong 83822a0cbb feat(player): 点赞与评论改为按集 2026-10-21 16:15:29 +08:00
Zhou Weidong e6f34977f5 merge: 合并 K87.1 下拉刷新冻结位移修复 2026-10-21 15:23:51 +08:00
Zhou Weidong 35860e056b fix(ui): 下拉刷新刷新期间冻结下拉位移 2026-10-21 15:23:46 +08:00
Zhou Weidong 01bef7f758 merge: 合并 K87 下拉刷新完成后回弹 2026-10-21 15:12:46 +08:00
Zhou Weidong 45e8707f1e feat(ui): 下拉刷新完成后才回弹 2026-10-21 15:12:41 +08:00
Zhou Weidong d39d8d43c1 merge: 合并 K86.22 评论输入面板安全区修复 2026-10-21 15:00:26 +08:00
Zhou Weidong 3fbfa9ab81 fix(player): 去除评论输入面板安全区导致的收键盘位移 2026-10-21 15:00:20 +08:00
Zhou Weidong 036bd29855 merge: 合并 K86.21 评论输入浮层稳定定位修复 2026-10-21 14:54:12 +08:00
Zhou Weidong d2f2459a96 fix(player): 固定评论输入浮层位置避免收键盘跳动 2026-10-21 14:54:05 +08:00
Zhou Weidong e90217bdfd merge: 合并 K86.20 评论输入浮层打开状态修正 2026-10-21 14:48:10 +08:00
Zhou Weidong ea1a9f424b fix(player): 返回键仅收起评论输入键盘 2026-10-21 14:48:01 +08:00
Zhou Weidong 18a1d06821 merge: 合并 K86.20 评论输入初始键盘与聚焦修正 2026-10-21 14:43:56 +08:00
Zhou Weidong a66cdb2e1b fix(player): 修正评论输入浮层初始键盘高度与聚焦 2026-10-21 14:43:43 +08:00
Zhou Weidong 6c72f8bf22 merge: 合并 K86.19 评论输入实色填充保障 2026-10-21 14:35:54 +08:00
Zhou Weidong c892d9b995 fix(player): 评论输入面板下方改为实色填充 2026-10-21 14:35:40 +08:00
Zhou Weidong 2ae4aedac0 merge: 合并 K86.18 输入浮层实底填充修正 2026-10-21 14:30:04 +08:00
Zhou Weidong db6fbd58ea fix(player): 评论输入浮层补齐键盘区域实底 2026-10-21 14:29:52 +08:00
Zhou Weidong 8dc9d55106 merge: 合并 K86.18 评论输入透明浮层 2026-10-21 14:24:36 +08:00
Zhou Weidong 4908dc9d76 fix(player): 评论输入改为透明浮层并消除收起键盘抖动 2026-10-21 14:24:24 +08:00
Zhou Weidong 5b4526cb97 merge: 合并 K86.17 输入弹窗位置固定修正 2026-10-21 14:15:11 +08:00
Zhou Weidong bac42bc653 fix(player): 键盘收起后保持评论输入弹窗位置 2026-10-21 14:14:58 +08:00
Zhou Weidong bc971c17d5 merge: 合并 K86.17 输入弹窗收起规则修复 2026-10-21 14:10:20 +08:00
Zhou Weidong cb58f649c1 fix(player): 收起键盘不关闭评论输入弹窗 2026-10-21 14:10:09 +08:00
Zhou Weidong f9f77090d9 merge: 合并 K86.16 评论提示与输入弹层修复 2026-10-21 13:59:16 +08:00
Zhou Weidong 5d459dd08a fix(player): 评论成功提示改黑色浮层并去除输入弹窗遮罩 2026-10-21 13:59:04 +08:00
Zhou Weidong c3e6e02e47 merge: 合并 K86.15 评论发布成功提示 2026-10-21 13:47:46 +08:00
Zhou Weidong e86daf559a feat(player): 评论发布成功提示 2026-10-21 13:47:36 +08:00
Zhou Weidong 9c0c1b8982 merge: 合并 K86.14 评论输入紧凑弹窗修正 2026-10-21 13:37:04 +08:00
Zhou Weidong e0024ed123 fix(player): 评论输入弹窗改为紧凑高度 2026-10-21 13:36:52 +08:00
Zhou Weidong c8ac2c9823 merge: 合并 K86.14 评论输入独立弹窗 2026-10-21 13:31:54 +08:00
Zhou Weidong 282f51c24b fix(player): 评论输入改为独立弹窗 2026-10-21 13:31:41 +08:00
Zhou Weidong aa14d0a835 merge: 合并 K86.13 评论面板半屏高度 2026-10-21 13:20:33 +08:00
Zhou Weidong 50edbf40c4 fix(player): 评论面板固定半屏高度 2026-10-21 13:20:28 +08:00
Zhou Weidong fc877f25bf merge: 合并 K86.12 评论文字居中修复 2026-10-21 13:16:39 +08:00
Zhou Weidong f87b0d2689 fix(player): 评论入口文字垂直居中 2026-10-21 13:16:34 +08:00
Zhou Weidong 146cd2a29e merge: 合并 K86.11 评论假输入框入口 2026-10-21 13:12:39 +08:00
Zhou Weidong 425ea4c0b7 fix(player): 评论入口改为假输入框点击唤起 2026-10-21 13:12:30 +08:00
Zhou Weidong a142a6c318 merge: 合并 K86.10 评论面板高度与收起流程 2026-10-21 13:04:40 +08:00
Zhou Weidong d081cff3e4 fix(player): 评论面板保持高度并两段式收起 2026-10-21 13:04:29 +08:00
Zhou Weidong 9876772d06 merge: 合并 K86.9 评论面板键盘态布局修复 2026-10-21 13:00:20 +08:00
Zhou Weidong bc4c84a4ae fix(player): 修正评论面板键盘态布局重叠 2026-10-21 13:00:05 +08:00
Zhou Weidong a7c0576a03 merge: 合并 K86.8 评论列表与焦点保持 2026-10-21 12:53:56 +08:00
Zhou Weidong ad69ef7f54 fix(player): 聚焦时保留评论列表并稳定焦点 2026-10-21 12:53:46 +08:00
Zhou Weidong de99c09454 merge: 合并 K86.7 键盘弹出不压缩视频页 2026-10-21 12:48:01 +08:00
Zhou Weidong 1776c622ae fix(player): 键盘弹出不压缩视频页 2026-10-21 12:47:52 +08:00
Zhou Weidong 33b49c161a merge: 合并 K86.6 评论输入聚焦修复 2026-10-21 12:40:25 +08:00
Zhou Weidong c7b230263a fix(player): 修复评论输入聚焦时面板消失 2026-10-21 12:40:12 +08:00
Zhou Weidong 226ac5920c merge: 合并 K86.5 隐藏评论操作入口 2026-10-21 12:26:26 +08:00
Zhou Weidong 9bd46521aa fix(player): 评论暂时隐藏举报与删除入口 2026-10-21 12:26:14 +08:00
Zhou Weidong 838beaaa3e merge: 合并 K86.4 评论展开输入面板 2026-10-21 12:21:28 +08:00
Zhou Weidong 519cab20fe fix(player): 评论聚焦态改为展开输入面板 2026-10-21 12:21:17 +08:00
Zhou Weidong 8b51a7c18e merge: 合并 K86.3 Android 键盘窗口修正 2026-10-21 12:14:19 +08:00
Zhou Weidong dbeb9883a5 fix(android): 键盘弹出不再压缩整页 2026-10-21 12:14:09 +08:00
Zhou Weidong 5160ab437d merge: 合并 K86.2 评论面板间距与键盘修正 2026-10-21 12:07:39 +08:00
Zhou Weidong ab476cab7d fix(player): 评论面板自适应高度并只顶起输入框 2026-10-21 12:07:30 +08:00
Zhou Weidong 97735763a1 merge: 合并 K86.1 评论面板补修 2026-10-21 12:00:37 +08:00
Zhou Weidong 2659c29501 fix(player): 评论总数含回复并改用相对时间 2026-10-21 12:00:27 +08:00
Zhou Weidong 22cb74ded4 merge: 合并 K86 评论面板重做 2026-10-21 11:53:07 +08:00
Zhou Weidong 7862e0be09 fix(player): 评论面板按参考图重做 2026-10-21 11:52:59 +08:00
Zhou Weidong bdd3a53632 merge: 合并 K85 收藏与评论数实时刷新 2026-10-21 11:40:08 +08:00
Zhou Weidong 5e2b8d9e4d fix(player): 收藏与评论数实时刷新 2026-10-21 11:39:58 +08:00
Zhou Weidong 97bbced02c merge: 合并 K83-A 互动栏图标资源 2026-10-21 11:02:20 +08:00
Zhou Weidong cbb9f60f7f feat(player): 互动栏改用参考图同款图标资源 2026-10-21 11:02:05 +08:00
Zhou Weidong 35de4095b0 merge: 合并 K82-A 右侧互动栏图标升级 2026-10-21 10:52:31 +08:00
Zhou Weidong 4b1aea29c7 feat(player): 右侧互动栏图标按参考图替换并放大 2026-10-21 10:52:14 +08:00
Zhou Weidong e6a31a60df merge: 合并 K81-A 更多面板文字减重 2026-10-21 10:43:51 +08:00
Zhou Weidong 622dc2f9e8 fix(player): 更多面板文字减重并缩小标签 2026-10-21 10:43:36 +08:00
Zhou Weidong 62f87040d6 merge: 合并 K80-A 更多面板重做 2026-10-21 10:34:32 +08:00
Zhou Weidong 7fccd16750 feat(player): 更多面板按参考图重做 2026-10-21 10:34:18 +08:00
Zhou Weidong c06a67c4b0 merge: 合并 K79-A 进度拖动防闪 2026-10-21 10:24:47 +08:00
Zhou Weidong 1993ff1102 fix(player): 修复拖动进度条时加载态闪烁 2026-10-21 10:24:34 +08:00
Zhou Weidong 72708630ba merge: 合并 K78-A 底部导航放大 2026-10-21 10:16:28 +08:00
Zhou Weidong d6b2d308eb feat(nav): 底部导航图标与文字放大 2026-10-21 10:16:16 +08:00
Zhou Weidong 0ba6ea9c52 merge: 合并 K77-A 播放加载进度态 2026-10-21 10:07:47 +08:00
Zhou Weidong cf4b07aa69 feat(player): 未就绪改由进度条展示加载态 2026-10-21 10:07:34 +08:00
Zhou Weidong 76cc52ad66 merge: 合并 K76-A 开发默认地址 2026-10-21 09:58:09 +08:00
Zhou Weidong 99b45e4f12 chore(config): 开发默认地址改回 192.168.31.187 2026-10-21 09:57:58 +08:00
Zhou Weidong c0697c01e3 merge: 合并 K75-A 首页淡入转场 2026-10-21 09:23:48 +08:00
Zhou Weidong 8c04099d97 feat(player): 首页进详情与播放页改为淡入转场 2026-10-21 09:23:30 +08:00
Zhou Weidong 3a636ca894 merge: 合并 K74-A 开发本机地址 2026-10-21 09:17:06 +08:00
Zhou Weidong d13b601ae8 chore(config): 开发默认地址改回本机 10.39.237.1 2026-10-21 09:16:37 +08:00
Zhou Weidong 2577797772 merge: 将 C3 埋点同步到 main 2026-10-21 09:09:34 +08:00
Zhou Weidong 05e2952815 merge: 将 C3 埋点同步到 release/v1.0.0 2026-10-21 09:07:42 +08:00
Zhou Weidong 827c972da0 merge: 补齐客户端埋点 SDK 2026-10-21 06:32:08 +08:00
Zhou Weidong 92bfc76e50 feat: C3 客户端埋点 SDK(EventTracker 批量上报 + 核心事件) 2026-10-21 06:27:56 +08:00
Zhou Weidong 6ae9e8e043 merge: 将 release/v1.0.0 同步到 main 2026-10-20 22:26:48 +08:00
Zhou Weidong 182b7e3ac2 merge: 将 develop 同步到 release/v1.0.0 2026-10-20 22:23:32 +08:00
Zhou Weidong 1dbc3acd80 merge: 合并 K71-A 首页导航安全区底色 2026-10-20 22:13:00 +08:00
Zhou Weidong 66e9062ba5 fix(home): 首页深色底部导航补齐安全区底色 2026-10-20 22:11:56 +08:00
Zhou Weidong ac1202c329 merge: 合并 K70-A 开发默认公网地址 2026-10-20 21:56:12 +08:00
Zhou Weidong 0c6a103c1c chore(config): 开发默认地址改为公网 39.162.50.99 2026-10-20 21:55:51 +08:00
Zhou Weidong 8bb365db2a merge: 将 release/v1.0.0 合入 main 2026-10-20 20:38:58 +08:00
Zhou Weidong 0cb57d084d merge: 将 develop 合入 release/v1.0.0
# Conflicts:
#	lib/core/config/app_config.dart
#	lib/main.dart
#	test/config/server_endpoint_test.dart
2026-10-20 20:36:43 +08:00
Zhou Weidong 51c363540a merge: 合并 K68-A 锁定编译期接口地址 2026-10-20 20:21:00 +08:00
Zhou Weidong 9ebb54ecfd fix(config): 指定 API_BASE_URL 时跳过地址探测 2026-10-20 20:20:46 +08:00
Zhou Weidong 2c5ff3579c merge: 合并 K67-A 删除无引用代码与资源 2026-10-20 20:06:31 +08:00
Zhou Weidong 3fefd544fb chore(cleanup): 删除无引用代码与无用资源 2026-10-20 20:06:17 +08:00
Zhou Weidong d2174c5b74 merge: 合并 K66-A 启动页停留至首页就绪 2026-10-20 19:57:28 +08:00
Zhou Weidong 7871b0cfbb feat(home): 启动页停留至首页就绪 2026-10-20 19:57:13 +08:00
Zhou Weidong c76b388814 merge: 合并 K65-A 冷启动首页预取 2026-10-20 19:51:55 +08:00
Zhou Weidong 9b31d83e39 perf(home): 冷启动提前拉取首页数据 2026-10-20 19:51:41 +08:00
Zhou Weidong 1199db760f merge: 合并 K64-A 原生品牌启动图 2026-10-20 19:42:03 +08:00
Zhou Weidong 8f60046ec3 feat(splash): 原生开屏改为品牌启动图 2026-10-20 19:41:50 +08:00
Zhou Weidong 210ccc97cb merge: 合并 K63-A 首页 feed 跳转登录页暂停播放 2026-10-20 19:25:05 +08:00
Zhou Weidong 699fd427b8 fix(player): 路由覆盖时暂停播放 2026-10-20 19:24:49 +08:00
Zhou Weidong 911f13f464 merge: 合并 K62 安卓桌面图标资源 2026-10-20 19:16:42 +08:00
Zhou Weidong 4c8ae7bcec fix(android): 按设计源图重做桌面图标资源 2026-10-20 19:16:29 +08:00
Zhou Weidong 51e94029e4 merge: 合并 K61 导航栏闪白修复 2026-10-20 19:04:38 +08:00
Zhou Weidong 0aa5a99a22 fix(flutter): 修复详情与播放器底部闪白 2026-10-20 19:04:25 +08:00
Zhou Weidong 9eec9ed77a merge: 合并 K60 flutter_native_splash 原生开屏 2026-10-20 18:51:54 +08:00
Zhou Weidong 983e99a4b6 feat(flutter): 接入 flutter_native_splash 原生开屏 2026-10-20 18:51:28 +08:00
Zhou Weidong 7c2fd090e0 merge: 合并 K59 隐藏视频调试文字 2026-10-20 18:43:52 +08:00
Zhou Weidong a756faca6c fix(flutter): 隐藏视频常驻调试文字 2026-10-20 18:43:41 +08:00
Zhou Weidong da549492b0 merge: 合并 K58 浅色开屏 2026-10-20 18:38:10 +08:00
Zhou Weidong 398dc3f8b4 feat(flutter): 优化浅色开屏与原生过渡 2026-10-20 18:37:59 +08:00
Zhou Weidong 576f2b95f4 merge: 合并 K57 视频首帧防闪 2026-10-20 18:29:20 +08:00
Zhou Weidong 6b397015b1 fix(flutter): 修复 iOS 视频首帧闪屏 2026-10-20 18:29:10 +08:00
Zhou Weidong ca6776580f merge: 合并 K56 服务地址编辑器 2026-10-20 18:23:58 +08:00
Zhou Weidong c259b0b543 feat(flutter): 增加 debug 服务地址编辑器 2026-10-20 18:23:44 +08:00
Zhou Weidong eab5d3e230 merge: 合并 K55 目录加载诊断 2026-10-20 18:17:46 +08:00
Zhou Weidong 730a96559b feat(flutter): 增加目录加载诊断与前台重试 2026-10-20 18:17:36 +08:00
Zhou Weidong d68734cdab merge: 合并 iOS shared_preferences 兼容修复 2026-10-20 18:08:44 +08:00
Zhou Weidong aac9195e05 fix(flutter): 钉住 shared_preferences_foundation 2.5.6 兼容当前 Xcode。 2026-10-20 18:08:17 +08:00
Zhou Weidong ae11846b63 merge: 合并 K54 iOS 网络修复 2026-10-20 18:01:25 +08:00
Zhou Weidong a63334f5a5 fix(flutter): 修复 iOS 启动网络加载 2026-10-20 18:01:12 +08:00
Zhou Weidong 23059578d2 merge: 合并 K53 详情页首帧修复 2026-10-20 17:54:19 +08:00
Zhou Weidong bb92996db5 fix(flutter): 修复详情页首帧闪屏 2026-10-20 17:54:08 +08:00
Zhou Weidong a11ba50b13 merge: 合并 K52 原生开屏清理 2026-10-20 17:45:44 +08:00
Zhou Weidong fe5b6e6efa feat(flutter): 移除原生静态开屏 2026-10-20 17:45:33 +08:00
Zhou Weidong 53591abcd0 merge: 合并 K50 开屏品牌强化 2026-10-20 17:40:23 +08:00
Zhou Weidong c147645824 feat(flutter): 强化开屏品牌标识展示 2026-10-20 17:40:13 +08:00
Zhou Weidong fcf30ff39c merge: 合并 K50 图标最终资源 2026-10-20 17:36:55 +08:00
Zhou Weidong dfaff9714a fix(flutter): 同步水果短剧品牌图标最终资源 2026-10-20 17:36:49 +08:00
Zhou Weidong 04609ace7c merge: 合并 K50 品牌图标资源 2026-10-20 17:34:25 +08:00
Zhou Weidong 7e60e5ebc4 feat(flutter): 替换水果短剧品牌图标与开屏图 2026-10-20 17:34:15 +08:00
Vicky 375b013b1c merge: 合并 K51 详情页灰屏修复 2026-10-20 17:33:05 +08:00
Zhou Weidong 353196aaa3 fix(flutter): 修复详情页跳转灰屏 2026-10-20 17:32:53 +08:00
Zhou Weidong da78d8723d merge: 合并 K50 品牌开屏动画 2026-10-20 17:20:16 +08:00
Zhou Weidong 15570f117a feat(flutter): 重做品牌开屏动画 2026-10-20 17:20:06 +08:00
Zhou Weidong 499e42b511 merge: 合并 K49 加速态清屏按钮修正 2026-10-20 17:12:37 +08:00
Zhou Weidong 5034ac7670 fix(flutter): 隐藏首页加速态恢复清屏按钮 2026-10-20 17:12:28 +08:00
Zhou Weidong 030a0e7986 merge: 合并分类弧线方向修正 2026-10-20 17:09:46 +08:00
Zhou Weidong 420da29658 fix(flutter): 调整分类弧线开口方向 2026-10-20 17:09:37 +08:00
Zhou Weidong 3ce2599754 merge: 合并 K48 分类 tab 修正 2026-10-20 17:07:33 +08:00
Zhou Weidong 5af70c345a fix(flutter): 优化分类弧线与自动滚动 2026-10-20 17:07:25 +08:00
Zhou Weidong 3041849344 merge: 合并下拉刷新主色文案修正 2026-10-20 17:02:28 +08:00
Zhou Weidong 107ae40d2a fix(flutter): 统一下拉刷新主色文案 2026-10-20 17:02:20 +08:00
Zhou Weidong 8bcd882aa5 merge: 合并 K47 剧场搜索清空修正 2026-10-20 17:00:14 +08:00
Zhou Weidong 9870502901 fix(flutter): 增加剧场搜索清空按钮 2026-10-20 17:00:03 +08:00
Zhou Weidong fb0585c9cf merge: 合并下拉刷新页面跟随修正 2026-10-20 16:57:36 +08:00
Zhou Weidong 69eaacd74f fix(flutter): 修复下拉刷新页面跟随 2026-10-20 16:57:28 +08:00
Zhou Weidong 8f3049f374 merge: 合并 K46-A 榜单新剧分页与预约刷新 2026-10-20 16:53:15 +08:00
Zhou Weidong c4dcb714ab feat(flutter): 增加榜单新剧分页与预约刷新 2026-10-20 16:53:07 +08:00
Zhou Weidong 805762f761 merge: 合并 K45-A 文字下拉刷新 2026-10-20 16:47:20 +08:00
Zhou Weidong 5bdcb989bb feat(flutter): 统一文字下拉刷新 2026-10-20 16:47:13 +08:00
Zhou Weidong 0ad2cd5e95 merge: 合并 K44-A 个人资料页修正 2026-10-20 16:42:34 +08:00
Zhou Weidong b2a8bfa59f fix(flutter): 精简个人资料并修正保存文案 2026-10-20 16:42:27 +08:00
Zhou Weidong dc73f4e3bb merge: 合并 K43-A 首页冷启动灰屏修复 2026-10-20 16:39:55 +08:00
Zhou Weidong 0d2680e6c1 fix(flutter): 修复首页冷启动灰屏 2026-10-20 16:39:48 +08:00
Zhou Weidong 37802b24bf merge: 合并 K42-A 收藏页星标修正 2026-10-20 16:37:04 +08:00
Zhou Weidong 1d8582dfaa fix(flutter): 调整收藏页星标操作 2026-10-20 16:36:56 +08:00
Zhou Weidong 4c9bb18403 merge: 合并 K42-A 观看历史删除按钮修正 2026-10-20 16:34:38 +08:00
Zhou Weidong f62324084e fix(flutter): 优化观看历史删除按钮 2026-10-20 16:34:31 +08:00
Zhou Weidong 9f4dc93d11 merge: 合并 K42-A 收藏与观看历史页重做 2026-10-20 16:32:08 +08:00
Zhou Weidong 794dc87083 feat(flutter): 重做收藏与观看历史页 2026-10-20 16:31:59 +08:00
Zhou Weidong 7285a1cd4e merge: 合并 K41-A 登录页重设计 2026-10-20 16:21:14 +08:00
Zhou Weidong 81bb1543bb feat(flutter): 修复登录输入并升级登录页 2026-10-20 16:21:06 +08:00
Zhou Weidong f72cb41670 merge: 合并详情页顶部按钮统一 2026-10-20 16:16:01 +08:00
Zhou Weidong c1dd033581 fix(flutter): 统一详情页顶部圆形按钮 2026-10-20 16:15:53 +08:00
Zhou Weidong 230f16a44a merge: 合并 K40-B 评论入口登录闸 2026-10-20 16:12:34 +08:00
Zhou Weidong 870a170fad fix(flutter): 评论入口增加登录闸 2026-10-20 16:12:27 +08:00
Zhou Weidong a5c84b0297 merge: 合并 K39 返回按钮优化 2026-10-20 16:09:19 +08:00
Zhou Weidong 8493c4ef3b fix(flutter): 优化二级页面返回按钮样式 2026-10-20 16:09:10 +08:00
Zhou Weidong 457d2fa6f2 merge: 合并 K40-B 评论登录闸 2026-10-20 16:05:54 +08:00
Zhou Weidong d0c1a5f499 fix(flutter): 评论输入增加登录闸 2026-10-20 16:05:47 +08:00
Zhou Weidong 1eea9fafe7 merge: 合并 K40-A 播放加载修复 2026-10-20 15:59:32 +08:00
Zhou Weidong fa5fd124a4 fix(flutter): 优化播放加载占位与主题色 2026-10-20 15:59:23 +08:00
Zhou Weidong 9864c8e102 merge: 合并 K39-A 二级页头统一 2026-10-20 15:55:21 +08:00
Zhou Weidong 1c6c92170d feat(flutter): 统一二级页头并恢复新剧暖色 2026-10-20 15:55:12 +08:00
Zhou Weidong 7b2970d5c5 merge: 合并 K38-A 短剧详情页重做 2026-10-20 15:47:00 +08:00
Zhou Weidong 69faa7818c feat(flutter): 重做短剧详情页 2026-10-20 15:46:53 +08:00
Zhou Weidong 7250b5f0f9 merge: 合并 K37-A 剧场筛选页 2026-10-20 15:39:53 +08:00
Zhou Weidong 850f8e7c8c feat(flutter): 新增剧场筛选页 2026-10-20 15:39:46 +08:00
Zhou Weidong 85ba2a4fec merge: 合并 K36-A 首映厅与档期预约 2026-10-20 15:28:09 +08:00
Zhou Weidong 4f81bcf2b1 feat(flutter): 新剧首映厅与档期预约页 2026-10-20 15:28:09 +08:00
Zhou Weidong fdee89b345 merge: 合并 K34-H 吸顶闪动修复 2026-10-20 15:25:15 +08:00
Zhou Weidong 6463436a0d fix(flutter): 吸顶分类行背景常驻清除闪动 2026-10-20 15:25:15 +08:00
Zhou Weidong fcc1995d8b merge: 合并 K35-A 创作页重做 2026-10-20 15:21:54 +08:00
Zhou Weidong 6e9a93c920 feat(flutter): 去掉剧场封面角标并重做创作页 2026-10-20 15:21:54 +08:00
Zhou Weidong 72410b2a87 merge: 合并 K34-G 剧场吸顶修复 2026-10-20 15:17:46 +08:00
Zhou Weidong d1abfd47c7 fix(flutter): 修复剧场吸顶分类行闪烁并压缩间距 2026-10-20 15:17:46 +08:00
Zhou Weidong 963f376159 merge: 合并 K34-E 卡片直达播放追加 2026-10-20 15:15:30 +08:00
Zhou Weidong 720fd26935 fix(flutter): 剧场与榜单卡片直接进播放页 2026-10-20 15:15:27 +08:00
Zhou Weidong 9949b61560 merge: 合并 K34-E 榜单与吸顶优化 2026-10-20 15:13:12 +08:00
Zhou Weidong 04a1a4ee82 feat(flutter): 榜单前三铺满与剧场分类吸顶下划线 2026-10-20 15:13:12 +08:00
Zhou Weidong d80fff74ba merge: 合并 K34-D 榜单三强横排 2026-10-20 15:08:43 +08:00
Zhou Weidong 881088a428 feat(flutter): 榜单页三强横排与第四名起列表 2026-10-20 15:08:43 +08:00
Zhou Weidong ffedde0db5 merge: 合并 K34-C 榜单新剧预约页 2026-10-20 15:03:07 +08:00
Zhou Weidong bfba2b7c72 feat(flutter): 剧场搜索框改白底并新增榜单新剧预约页 2026-10-20 15:03:02 +08:00
Zhou Weidong 7a75ee6de8 merge: 合并 K34-B 剧场搜索框样式 2026-10-20 14:58:18 +08:00
Zhou Weidong eccd3c6dc1 fix(flutter): 剧场搜索框改为浅灰胶囊 2026-10-20 14:58:18 +08:00
Zhou Weidong 63ace3b574 merge: 合并 K34-A 剧场页精致化 2026-10-20 14:52:46 +08:00
Zhou Weidong 5283ab2549 feat(flutter): 剧场页搜索与吸顶筛选精致化 2026-10-20 14:52:41 +08:00
Zhou Weidong f9d4f982f0 merge: 合并 K33-K 首页 feed 固定顶栏 2026-10-20 14:46:13 +08:00
Zhou Weidong 9175a7f253 fix(flutter): 首页 feed 顶栏固定悬浮 2026-10-20 14:46:08 +08:00
Zhou Weidong 1cb4c56261 merge: 合并 K33 暂停按钮尺寸调整 2026-10-20 14:41:27 +08:00
Zhou Weidong 551da77e67 fix(flutter): 缩小播放暂停按钮 2026-10-20 14:41:27 +08:00
Zhou Weidong 33de925f65 merge: 合并 K33-J 清屏进度与长按恢复 2026-10-20 14:38:12 +08:00
Zhou Weidong b2c219d6bd fix(flutter): 清屏态进度可拖动并长按恢复播放 2026-10-20 14:38:09 +08:00
Zhou Weidong 8327c1dd13 merge: 合并 K33-I feed 清屏与寻址恢复 2026-10-20 14:33:39 +08:00
Zhou Weidong c87798fb16 fix(flutter): feed 清屏保留顶栏并寻址恢复播放 2026-10-20 14:33:35 +08:00
Zhou Weidong 9b99d96a07 merge: 合并 K33-H 画中画回前台复位 2026-10-20 14:29:02 +08:00
Zhou Weidong bc8f3bcab7 fix(flutter): 回前台复位残留画中画状态 2026-10-20 14:28:05 +08:00
Zhou Weidong 28ff9a7642 merge: 合并 K33-G feed rail 与更多弹窗 2026-10-20 14:23:40 +08:00
Zhou Weidong 7e415f6f4f fix(flutter): feed rail 底部对齐并复用播放页更多 2026-10-20 14:23:36 +08:00
Zhou Weidong e800f5971f merge: 合并 K33-F feed 长按与刷新 2026-10-20 14:19:20 +08:00
Zhou Weidong ea9ea6e606 fix(flutter): feed 长按加速与刷新转圈 2026-10-20 14:19:14 +08:00
Zhou Weidong 564c0dbbef merge: 合并 K33-E 首页 feed 刷新与进度优化 2026-10-20 14:11:46 +08:00
Zhou Weidong 82fe237c24 fix(flutter): 首页 feed 静默刷新与进度条细节 2026-10-20 14:11:42 +08:00
Zhou Weidong a3126723d2 merge: 合并 K33-D 首页 feed 精修 2026-10-20 14:05:28 +08:00
Zhou Weidong 636b369bd4 fix(flutter): 首页 feed 去底色并优化整集条 2026-10-20 14:05:24 +08:00
Zhou Weidong f94926cff5 merge: 合并 K33-C 弹幕文案清理 2026-10-20 13:58:27 +08:00
Zhou Weidong 521ebfc1ab fix(flutter): 清理未使用的弹幕文案 key 2026-10-20 13:58:26 +08:00
Zhou Weidong c3f5f43744 merge: 合并 K33-C 首页 feed 精修 2026-10-20 13:56:55 +08:00
Zhou Weidong 321c62664c fix(flutter): 精修首页 feed 交互与底色 2026-10-20 13:56:51 +08:00
Zhou Weidong 435fbbbbb1 merge: 合并 K33-B 深色首页导航追加 2026-10-20 13:48:45 +08:00
Zhou Weidong d36d62f6a6 fix(flutter): 去除关注 tab 并加深色首页导航 2026-10-20 13:48:42 +08:00
Zhou Weidong ea28420489 merge: 合并 K33-B 首页 feed 浮层重做 2026-10-20 13:46:24 +08:00
Zhou Weidong ee6f0d7e72 feat(flutter): 按参考图重做首页 feed 浮层 2026-10-20 13:46:20 +08:00
Zhou Weidong d6b055dc62 merge: 合并 K33-A 失效轮播用例清理 2026-10-20 13:35:40 +08:00
Zhou Weidong fe84244e7d test(flutter): 移除失效的首页轮播用例 2026-10-20 13:35:40 +08:00
Zhou Weidong 53cd2dbba1 merge: 合并 K33-A 首页沉浸式 feed 2026-10-20 13:35:13 +08:00
Zhou Weidong b7926d64a5 feat(flutter): 首页改为沉浸式 feed 流 2026-10-20 13:35:09 +08:00
Zhou Weidong 698fb12e07 merge: 合并 K32-A 识别入口移除 2026-10-20 13:27:07 +08:00
Zhou Weidong 5e2fba1746 fix(flutter): 移除搜索框识剧入口 2026-10-20 13:27:03 +08:00
Zhou Weidong edfef61ee5 merge: 合并 K32-A 剧场页重做 2026-10-20 13:24:24 +08:00
Zhou Weidong 5a51af4264 feat(flutter): 剧场页按参考图重做 2026-10-20 13:24:21 +08:00
Zhou Weidong 2abb8c7032 merge: 合并 K31-G 首页通知铃铛 2026-10-20 13:11:49 +08:00
Zhou Weidong 524b7f5b35 feat(flutter): 首页顶栏改为通知铃铛入口 2026-10-20 13:11:46 +08:00
Zhou Weidong e32fa58979 merge: 合并 K31-F 移除封面锁定角标 2026-10-20 13:08:07 +08:00
Zhou Weidong 1fc25ec5d1 fix(flutter): 首页剧卡移除封面锁定角标 2026-10-20 13:08:03 +08:00
Zhou Weidong 40e23793b4 merge: 合并 K31-E 首页剧卡压缩 2026-10-20 13:03:22 +08:00
Zhou Weidong 124ba75ab8 feat(flutter): 压缩首页剧卡并把播放次数上封面 2026-10-20 13:03:19 +08:00
Zhou Weidong 8e2cd5c838 merge: 合并 K31-D 首页频道吸顶 2026-10-20 12:56:17 +08:00
Zhou Weidong 7cc65b7b09 feat(flutter): 首页频道行滚动吸顶 2026-10-20 12:56:13 +08:00
Zhou Weidong cfc41efddc merge: 合并 K31-C 频道选中态主题色 2026-10-20 12:50:40 +08:00
Zhou Weidong 0e48c0773e fix(flutter): 首页频道选中态改用主题色 2026-10-20 12:50:37 +08:00
Zhou Weidong 290b92c6a7 merge: 合并 K31-B 首页频道与轮播改版 2026-10-20 12:45:16 +08:00
Zhou Weidong a24f1fa7c0 feat(flutter): 首页频道叠加轮播并改下划线选中 2026-10-20 12:45:13 +08:00
Zhou Weidong 36c71e63b4 merge: 合并 K31-A 加速态底部修正 2026-10-20 12:39:55 +08:00
Zhou Weidong 033ae5987c fix(flutter): 加速态隐藏底部选集与清屏按钮 2026-10-20 12:39:52 +08:00
Zhou Weidong 7a7bf0c207 merge: 合并 K31-A 播放页长按手势 2026-10-20 12:36:38 +08:00
Zhou Weidong 96074bc5e8 feat(flutter): 播放页长按手势与加速态 2026-10-20 12:36:35 +08:00
Zhou Weidong 7bb2714941 merge: 合并 K30-F 半透明播放按钮追加 2026-10-20 12:30:09 +08:00
Zhou Weidong a72adcfc3f fix(flutter): 放大播放图标并加半透明底 2026-10-20 12:30:06 +08:00
Zhou Weidong b8d2617873 merge: 合并 K30-F 播放按钮放大追加 2026-10-20 12:27:17 +08:00
Zhou Weidong 8013cc18ad fix(flutter): 放大透明播放按钮 2026-10-20 12:27:13 +08:00
Zhou Weidong ac73bf2208 merge: 合并 K30-F 简介灰字与透明暂停按钮 2026-10-20 12:24:41 +08:00
Zhou Weidong 02763c3dfb fix(flutter): 简介灰字统一并透明暂停按钮 2026-10-20 12:24:37 +08:00
Zhou Weidong 427e7a7897 merge: 合并 K30-E 选集条箭头靠右 2026-10-20 12:19:23 +08:00
Zhou Weidong 90769de3b7 fix(flutter): 选集条箭头靠右排列 2026-10-20 12:19:20 +08:00
Zhou Weidong ced0fae64a merge: 合并 K30-D 评论输入框尺寸修正 2026-10-20 12:16:06 +08:00
Zhou Weidong 8709b64dec fix(flutter): 修正评论输入框高度与圆角 2026-10-20 12:16:03 +08:00
Zhou Weidong edcd25fe68 merge: 合并 K30-C 评论面板视觉重做 2026-10-20 12:11:48 +08:00
Zhou Weidong 9493ad23bb fix(flutter): 重做评论面板视觉 2026-10-20 12:11:44 +08:00
Zhou Weidong ced1ec3d9e merge: 合并 K30-B 播放页数量与评论面板 2026-10-20 12:06:23 +08:00
Zhou Weidong bed6bbb75b feat(flutter): 播放页互动数量与评论面板收紧 2026-10-20 12:06:21 +08:00
Zhou Weidong e39aa766ac merge: 合并 K29.9 播放页底部细节 2026-10-20 11:55:31 +08:00
Zhou Weidong d25519bed7 fix(flutter): 调整播放页底部信息排版 2026-10-20 11:55:28 +08:00
Zhou Weidong 55d22a9940 merge: 合并 K29.8 更多面板小窗播放 2026-10-20 11:48:02 +08:00
Zhou Weidong 0af5de9641 fix(flutter): 更多面板增加小窗播放入口 2026-10-20 11:47:58 +08:00
Zhou Weidong 3b2bdb8e5a merge: 合并 K29.7 更多面板单行排列 2026-10-20 11:44:45 +08:00
Zhou Weidong f083a161e6 fix(flutter): 更多面板倍速清晰度改为单行排列 2026-10-20 11:44:42 +08:00
Zhou Weidong 1e3e5b64dc merge: 合并 K29.6 更多面板精简 2026-10-20 11:40:15 +08:00
Zhou Weidong c4797cd239 fix(flutter): 精简播放页更多面板 2026-10-20 11:40:15 +08:00
Zhou Weidong 08440b0160 merge: 合并 K29.5 进度条保留修正 2026-10-20 11:35:48 +08:00
Zhou Weidong d849834ce6 fix(flutter): 清屏时保留进度条 2026-10-20 11:35:48 +08:00
Zhou Weidong b26ca6cb9f merge: 合并 K29.5 清屏范围 2026-10-20 11:33:23 +08:00
Zhou Weidong 3068eb99da fix(flutter): 收窄清屏范围并保留底栏 2026-10-20 11:33:23 +08:00
Zhou Weidong a26b0071e4 merge: 合并 K29.4 透明播放页 2026-10-20 11:29:02 +08:00
Zhou Weidong 0af68f2455 fix(flutter): 播放页改为透明叠层 2026-10-20 11:29:02 +08:00
Zhou Weidong d762511412 merge: 合并 K29.3 深色播放器 2026-10-20 11:25:28 +08:00
Zhou Weidong 1dbe2cb8e9 fix(flutter): 恢复播放器深色外观 2026-10-20 11:25:28 +08:00
Zhou Weidong b2284b3a74 merge: 合并 K29.2 底栏清屏 2026-10-20 11:19:14 +08:00
Zhou Weidong 1666bc220e fix(flutter): 底栏右侧改为清屏 2026-10-20 11:19:14 +08:00
Zhou Weidong 9429257d62 merge: 合并 K29.1 浅色播放页底栏 2026-10-20 11:14:08 +08:00
Zhou Weidong e2162773e2 fix(flutter): 改为浅色播放页底栏 2026-10-20 11:14:08 +08:00
Zhou Weidong cc26c5472b merge: 合并 K29.1 播放页视觉收紧 2026-10-20 11:12:12 +08:00
Zhou Weidong cf93d8d794 fix(flutter): 收紧播放页底部与互动栏 2026-10-20 11:12:12 +08:00
Zhou Weidong 7b4a792c12 merge: 合并 K29 全屏胶囊双态切换 2026-10-20 11:03:53 +08:00
Zhou Weidong 6dbeb3e505 fix(flutter): 支持全屏胶囊双态切换 2026-10-20 11:03:53 +08:00
Zhou Weidong c4ad5d72d7 merge: 合并 K29 播放页与语言切换 2026-10-20 11:01:55 +08:00
Zhou Weidong 8c43e9e385 feat(flutter): 重构播放页并增加语言切换 2026-10-20 11:01:38 +08:00
Zhou Weidong f1efbc5be8 merge: 合并 K28-C 播放页与操作面板 2026-10-20 10:42:05 +08:00
Zhou Weidong 9a4269b847 feat(flutter): 重设计播放页与操作面板 2026-10-20 10:41:56 +08:00
Zhou Weidong 148fbc3c93 merge: 合并 K28-B 创作与我的页面 2026-10-20 10:28:38 +08:00
Zhou Weidong 03c3964353 feat(flutter): 重设计创作与我的页面 2026-10-20 10:28:31 +08:00
Zhou Weidong d50c6e6d9f merge: 合并 K28-A2 剧场与 Hero 重设计 2026-10-20 10:24:26 +08:00
Zhou Weidong 73aef4e44e feat(flutter): 重设计剧场与首页 Hero 2026-10-20 10:24:19 +08:00
Zhou Weidong 678b976ce1 merge: 合并 K28-S 四槽导航修正 2026-10-20 10:18:18 +08:00
Zhou Weidong 2f1a4c5c69 fix(flutter): 回退底部导航为四槽 2026-10-20 10:18:12 +08:00
Zhou Weidong e448003f1b merge: 合并 K28-S 浅色暖橙视觉主题 2026-10-20 10:15:12 +08:00
Zhou Weidong 634b63e16c feat(flutter): 应用浅色暖橙视觉主题 2026-10-20 10:15:03 +08:00
Zhou Weidong 0d2367431d merge: 合并 K28-A1 客户端四 Tab 首页 2026-10-20 09:57:17 +08:00
Zhou Weidong 49f3e63ed0 feat(flutter): 完成 K28-A1 四 Tab 首页 2026-10-20 09:57:10 +08:00
Zhou Weidong f981f88e0d merge: 合并 K28-A 客户端 UI 重构 2026-10-20 09:49:51 +08:00
Zhou Weidong 959edc0a32 feat(flutter): 重构四 Tab 与浏览页 2026-10-20 09:49:42 +08:00
Zhou Weidong 99c72e2c32 chore(config): 探测地址改为本机后端 127.0.0.1:8081 2026-10-20 09:26:12 +08:00
Zhou Weidong 6022d789ed chore(tools): 添加短剧资源批量上线脚本 2026-10-20 09:11:45 +08:00
Zhou Weidong 842e716ea6 merge: 红果式三 Tab 剧场与极简我的页 2026-10-20 05:29:54 +08:00
Zhou Weidong 12e57de3b3 chore(l10n): 清理已移除语言入口文案 2026-10-20 05:29:21 +08:00
Zhou Weidong 35338cbfc8 feat(app): 剧场页九宫格与极简我的页 2026-10-20 05:28:51 +08:00
Zhou Weidong dc8ca7b743 feat(app): 底部导航精简为三 Tab 2026-10-20 05:14:52 +08:00
Zhou Weidong 19caaa0a22 fix(app): 增加公网地址回退,远程/移动网络可连后端 2026-10-20 05:04:36 +08:00
Zhou Weidong 7ca7d20630 merge: 第二轮 App 接口对接 2026-10-20 04:39:51 +08:00
Zhou Weidong 7850dfc746 style(app): 格式化并拆分长函数 2026-10-20 04:38:36 +08:00
Zhou Weidong 2b2b7e9913 feat(app): 播放器增加清晰度档位入口 2026-10-20 04:37:42 +08:00
Zhou Weidong 30a5fa0776 feat(app): 剧场列表增加语种筛选 2026-10-20 04:35:48 +08:00
Zhou Weidong ade0cc3566 feat(app): 搜索改为调用后端并渲染卡片结果 2026-10-20 04:34:12 +08:00
Zhou Weidong 730e38622f feat(app): 接入金币充值与会员商品下单 2026-10-20 04:32:03 +08:00
Zhou Weidong ec2db6842e merge: Flutter P0-P2 App 对接完成 2026-10-19 23:58:11 +08:00
Zhou Weidong dfc650456a feat(app): 展示真实点赞与评论计数 2026-10-19 23:56:12 +08:00
Zhou Weidong 7b927c794e feat(app): 接入首页轮播 banner 2026-10-19 23:53:03 +08:00
Zhou Weidong 061aac6577 fix(app): 移除设置页调试服务器地址 2026-10-19 23:51:15 +08:00
Zhou Weidong 2b95c79931 feat(app): 增加首页榜单 tab 2026-10-19 23:50:03 +08:00
Zhou Weidong 136ee74618 feat(app): 完成 P0 钱包解锁、下载落盘与手机号登录 2026-10-19 23:46:47 +08:00
Zhou Weidong de941c841c fix(app): 对齐 release 修复(播放明文HTTP、shared_preferences 降级防崩溃、双网络访问) 2026-10-19 22:43:04 +08:00
jianghaifeng 5c174b007c Merge pull request 'Release/v1.0.0' (#1) from release/v1.0.0 into main
Reviewed-on: #1
2026-10-19 13:42:16 +00:00
Zhou Weidong 35364c949a chore(release): 合并 develop 到 release/v1.0.0 2026-10-19 21:39:21 +08:00
Zhou Weidong 814b2c0b7b chore(config): 默认服务地址改为公网 39.162.50.99:8081 2026-10-19 21:12:16 +08:00
Zhou Weidong 3d0e0804ca fix(app): 修复视频播放明文HTTP与双网络访问切换 2026-10-19 18:37:38 +08:00
Zhou Weidong 190e4f9c67 merge: 恢复开发默认服务器地址 2026-10-19 18:31:58 +08:00
Zhou Weidong d75b69621f chore(config): 恢复开发默认服务器地址 2026-10-19 18:31:23 +08:00
Zhou Weidong 75438c204d merge: 更新开发默认服务器地址 2026-10-19 18:27:51 +08:00
Zhou Weidong cb2fa920ea chore(config): 更新开发默认服务器地址 2026-10-19 18:27:17 +08:00
Zhou Weidong 53b77263ca merge: 更新开发默认服务器地址 2026-10-19 18:20:12 +08:00
Zhou Weidong 501446b3a6 chore(config): 更新开发默认服务器地址 2026-10-19 18:19:36 +08:00
Zhou Weidong 75dacf22ab merge: 增加瀑布流与下拉刷新 2026-10-19 18:11:05 +08:00
Zhou Weidong d271c43feb feat(discover): 增加瀑布流与下拉刷新 2026-10-19 18:10:30 +08:00
Zhou Weidong cabc0e72f5 merge: 点击页面取消搜索焦点 2026-10-19 18:04:38 +08:00
Zhou Weidong 884687d10a fix(discover): 点击页面取消搜索焦点 2026-10-19 18:04:00 +08:00
Zhou Weidong bbae8cf4ed merge: 封面完整显示并移除图上标题 2026-10-19 18:02:20 +08:00
Zhou Weidong 4c2384fea9 fix(discover): 封面完整显示并移除图上标题 2026-10-19 18:01:37 +08:00
Zhou Weidong 149b51b2c1 merge: 固定发现页顶栏 2026-10-19 17:55:26 +08:00
Zhou Weidong 16175803ed fix(discover): 固定发现页顶栏 2026-10-19 17:54:44 +08:00
Zhou Weidong 1eaab2a150 merge: 调整 rail 倍速弹窗与进度时间 2026-10-19 17:44:40 +08:00
Zhou Weidong 4d7173d035 fix(player): 调整 rail 倍速弹窗与进度时间 2026-10-19 17:44:06 +08:00
Zhou Weidong 87628e7d10 merge: 倍速改为底部弹窗选择 2026-10-19 17:32:52 +08:00
Zhou Weidong 3a7e0bc4b7 feat(player): 倍速改为底部弹窗选择 2026-10-19 17:32:17 +08:00
Zhou Weidong 5ca5846a26 merge: 共享清屏状态并左贴集数 2026-10-19 17:29:39 +08:00
Zhou Weidong 5b7a8bd775 fix(player): 共享清屏状态并左贴集数 2026-10-19 17:29:01 +08:00
Zhou Weidong 99a57ec502 merge: 中心按钮仅暂停时显示 2026-10-19 17:20:56 +08:00
Zhou Weidong 71e317f4ab fix(player): 中心按钮仅暂停时显示 2026-10-19 17:20:19 +08:00
Zhou Weidong b1747afbe8 merge: 保持清屏并常驻中心按钮 2026-10-19 17:14:40 +08:00
Zhou Weidong 0d977da75b fix(player): 保持清屏并常驻中心按钮 2026-10-19 17:14:03 +08:00
Zhou Weidong bc5feebf39 merge: 修正清屏态点击与滑动复位 2026-10-19 17:06:32 +08:00
Zhou Weidong aea130be11 fix(player): 修正清屏态点击与滑动复位 2026-10-19 17:05:52 +08:00
Zhou Weidong a2de5e8eb7 merge: 增加退出清屏浮动按钮 2026-10-19 16:53:57 +08:00
Zhou Weidong e2e809e2d5 fix(player): 增加退出清屏浮动按钮 2026-10-19 16:53:23 +08:00
Zhou Weidong 3fcfd68660 merge: 仅在暂停时显示播放按钮 2026-10-19 16:51:20 +08:00
Zhou Weidong 0eabb0a763 fix(player): 仅在暂停时显示播放按钮 2026-10-19 16:50:44 +08:00
Zhou Weidong 60ec19f08d merge: 改为显式清屏交互 2026-10-19 16:44:31 +08:00
Zhou Weidong f613033088 fix(player): 改为显式清屏交互 2026-10-19 16:43:50 +08:00
Zhou Weidong f4e1a5424e merge: 修正详情选集等宽网格 2026-10-19 16:33:20 +08:00
Zhou Weidong fbfb9769a0 fix(detail): 选集改为等宽懒加载网格 2026-10-19 16:32:09 +08:00
Zhou Weidong cbbd4c80e4 merge: 修正六屏顶栏与内容间距 2026-10-19 16:26:01 +08:00
Zhou Weidong 2fd92cdbea fix(ui): 修正六屏顶栏与内容间距 2026-10-19 16:25:25 +08:00
Zhou Weidong e03ea76505 merge: 统一暂停按钮并自动隐藏控制层 2026-10-19 16:16:53 +08:00
Zhou Weidong 03e34b3b57 fix(player): 统一暂停按钮并自动隐藏控制层 2026-10-19 16:16:14 +08:00
Zhou Weidong 568f051b7a merge: 修复详情暂停重复按钮 2026-10-19 16:07:42 +08:00
Zhou Weidong f0c53542ef fix(player): 移除详情暂停重复按钮 2026-10-19 16:07:06 +08:00
Zhou Weidong c82502559b merge: 清理 K26 旧 UI 与死代码 2026-10-19 15:59:03 +08:00
Zhou Weidong 5422581f8b refactor(ui): 清理 K26 旧 UI 与死代码 2026-10-19 15:58:22 +08:00
Zhou Weidong a5398daab3 merge: K26 客户端六屏按预览校正 2026-10-19 15:51:04 +08:00
Zhou Weidong 8767b5644b test(ui): 更新三 Tab 预览断言 2026-10-19 15:50:24 +08:00
Zhou Weidong fecc5b0d27 feat(ui): 新增登录入口与我的页预览 2026-10-19 15:50:15 +08:00
Zhou Weidong 21d457fd98 feat(ui): 校正首页发现详情播放器预览 2026-10-19 15:50:11 +08:00
Zhou Weidong 87db548591 feat(theme): 校正 K26 预览色板与导航 2026-10-19 15:50:07 +08:00
Zhou Weidong eefabb6898 merge: 修复播放器全屏进入方式 2026-10-19 15:24:40 +08:00
Zhou Weidong 1c94b727fb fix(player): 恢复详情与选集全屏播放 2026-10-19 15:24:06 +08:00
Zhou Weidong 04fddce1d1 merge: K25 客户端详情页与播放器 2026-10-19 15:17:18 +08:00
Zhou Weidong 689063a694 feat(player): 新增卡片式播放器 2026-10-19 15:16:40 +08:00
Zhou Weidong ec163f905d feat(detail): 重做详情页视觉结构 2026-10-19 15:16:36 +08:00
Zhou Weidong 4553606ec2 merge: K24 客户端 UI 对齐设计稿 2026-10-19 15:02:44 +08:00
Zhou Weidong e8c1859e70 feat(ui): 重做三 Tab 首页与发现页 2026-10-19 15:01:45 +08:00
Zhou Weidong eae1c533ce feat(theme): 接入 Obsidian Flare 设计系统 2026-10-19 15:01:41 +08:00
Zhou Weidong b1c69e22af feat(auth): 合并 K21 登录页优化 2026-10-19 10:34:37 +08:00
Zhou Weidong 8a96f341c8 feat(auth): 优化登录页布局与字段反馈 2026-10-19 10:33:33 +08:00
Zhou Weidong e44fbdf6f2 style(ui): 合并 K20 客户端视觉统一 2026-10-19 10:23:38 +08:00
Zhou Weidong 5f1df401dc fix(ui): 保留空态既有字号口径 2026-10-19 10:23:24 +08:00
Zhou Weidong c5bfd006fa style(ui): 统一页面字号间距与状态样式 2026-10-19 10:21:28 +08:00
Zhou Weidong df5fbe5b08 feat(ui): 增加视觉 token 与统一导航 2026-10-19 10:16:12 +08:00
Zhou Weidong 42ce1d1d84 fix(account): 合并网络异常文案修复 2026-10-19 10:00:48 +08:00
Zhou Weidong 27ab7cebfb fix(account): 本地化网络异常文案 2026-10-19 09:59:45 +08:00
Zhou Weidong 9b3ab9d5de feat(account): 合并 K19 账号中心 2026-10-19 09:55:37 +08:00
Zhou Weidong 7151bc9d8b style(account): 保持账号入口首屏可用 2026-10-19 09:54:10 +08:00
Zhou Weidong 6fce24d9ec refactor(account): 收敛账号页组件结构 2026-10-19 09:51:23 +08:00
Zhou Weidong da095115d4 feat(account): 增加账号中心页面与入口 2026-10-19 09:50:23 +08:00
Zhou Weidong c68f02449f feat(account): 增加账号中心页面逻辑 2026-10-19 09:48:07 +08:00
Zhou Weidong ef7f4ce3b6 feat(account): 同步资料保存后的登录态 2026-10-19 09:47:28 +08:00
Zhou Weidong 44f6feb249 feat(account): 接入个人中心接口模型 2026-10-19 09:47:06 +08:00
Zhou Weidong 7d68699523 fix(config): 合并开发默认服务器地址 2026-10-19 09:34:54 +08:00
Zhou Weidong 60043d1e41 fix(config): 更新开发默认服务器地址 2026-10-19 09:34:44 +08:00
Zhou Weidong 9df36bc358 feat(ui): 合并品牌启动页与选集交互 2026-10-19 09:28:27 +08:00
Zhou Weidong c1cb7d1c11 feat(ui): 更新品牌启动页与选集交互 2026-10-19 09:28:16 +08:00
Zhou Weidong 4778ce2c75 merge: 合并 K15 首页服务器设置修复 2026-10-17 23:13:07 +08:00
Zhou Weidong c4694c6f18 fix(flutter): 增加首页服务器设置并更新真机默认地址 2026-10-17 23:12:47 +08:00
Zhou Weidong 9fe62d13cb merge: 合并 K14 文档接口统计修正 2026-10-17 23:03:52 +08:00
Zhou Weidong 213c86e93c docs(client): 修正已接接口数量说明 2026-10-17 23:03:43 +08:00
Zhou Weidong 27e5286b10 merge: 合并 K14 客户端文档 2026-10-17 23:02:47 +08:00
Zhou Weidong 5a28845b5e docs(client): 重写 README 并补充任务与文件说明 2026-10-17 23:02:28 +08:00
Zhou Weidong f95325d581 merge: 合并 K13 开发服务器地址设置 2026-10-17 22:54:25 +08:00
Zhou Weidong 907562a874 feat(flutter): 增加 debug 运行时服务器地址设置 2026-10-17 22:54:07 +08:00
Zhou Weidong b76228905c merge: 合并 K12 首页与空态视觉修订 2026-10-17 22:44:40 +08:00
Zhou Weidong 2dd4627bdc feat(flutter): 精简首页标题并重做空态视觉 2026-10-17 22:44:21 +08:00
Zhou Weidong b5a8656ff0 merge: 合并 K11 首页三列网格 2026-10-17 22:34:38 +08:00
Zhou Weidong f0db631fa2 feat(flutter): 首页网格改为三列并首位显示全部 2026-10-17 22:34:14 +08:00
Zhou Weidong 0466b4b425 merge: 合并 K10 统一空态组件 2026-10-17 17:44:58 +08:00
Zhou Weidong 6d08330388 feat(flutter): 统一空列表占位组件 2026-10-17 17:44:44 +08:00
Zhou Weidong 19babb8f4a merge: 合并 iOS shared_preferences 编译修复 2026-10-17 17:30:50 +08:00
Zhou Weidong 2c30f83ab3 chore(ios): 固定 shared_preferences_foundation 2.5.6 2026-10-17 17:30:33 +08:00
Zhou Weidong a4415c872d chore(deps): 同步依赖锁文件 2026-10-17 17:27:07 +08:00
Zhou Weidong 2c381be5e8 merge: 合并 K9 首页全部剧目网格 2026-10-17 17:24:58 +08:00
Zhou Weidong cf03178cc5 feat(flutter): 首页改为两列全部剧目网格 2026-10-17 17:24:45 +08:00
Zhou Weidong 99f64ab870 merge: 合并 K8 登录页视觉重设计 2026-10-17 17:15:09 +08:00
Zhou Weidong 45c3c645ff feat(flutter): 重设计登录页视觉 2026-10-17 17:14:56 +08:00
Zhou Weidong 6cc5bfc3be merge: 合并首页轮播与观看历史真机修复 2026-10-17 17:00:20 +08:00
Zhou Weidong 5058853af3 fix(flutter): 注册目录仓库并修正首页轮播数据源 2026-10-17 17:00:07 +08:00
Zhou Weidong 3e8703b840 merge: 合并 K7 轮播与站内通知 2026-10-17 16:50:38 +08:00
Zhou Weidong b870956665 feat(flutter): 增加轮播自动播放与站内通知 2026-10-17 16:50:25 +08:00
Zhou Weidong b7b1c50427 merge: 合并 K6 导航与观看历史缺陷修复 2026-10-17 16:41:35 +08:00
Zhou Weidong 732483465c fix(flutter): 修复导航白线与观看记录错误透传 2026-10-17 16:41:24 +08:00
Zhou Weidong ac706a42a5 merge: 合并 K6 iOS SPM 构建修复 2026-10-17 16:32:22 +08:00
Zhou Weidong cc037a2956 fix(ios): 刷新 Flutter SPM 包后再准备框架 2026-10-17 16:32:11 +08:00
Zhou Weidong 0cedaab795 merge: 合并 K6 登录导航与震动反馈 2026-10-17 16:16:07 +08:00
Zhou Weidong 8a481d5c62 feat(flutter): 增加关键操作震动反馈 2026-10-17 16:15:46 +08:00
Zhou Weidong e609e3ad8d feat(flutter): 优化登录页与底部导航 2026-10-17 16:11:01 +08:00
Zhou Weidong e52d81881c merge: 合并 K5 克莱因蓝主题与印尼语适配 2026-10-17 15:58:29 +08:00
Zhou Weidong 10d448c8ba feat(flutter): 应用克莱因蓝主题与印尼语适配 2026-10-17 15:58:17 +08:00
Zhou Weidong e1147cfcba merge: 合并 K3 举报错误透传修复 2026-10-17 15:29:27 +08:00
Zhou Weidong 1488ee8f85 fix(flutter): 透传举报本人评论错误 2026-10-17 15:29:18 +08:00
Zhou Weidong 9db6935308 merge: 合并 K3 客户端互动能力 2026-10-17 15:26:51 +08:00
Zhou Weidong d361034226 feat(flutter): 接入互动评论点赞举报 2026-10-17 15:26:42 +08:00
Zhou Weidong 45f5f25640 merge: 合并 K2 客户端分类记录收藏与下载授权 2026-10-17 14:49:36 +08:00
Zhou Weidong cc4d7c5a9d feat(flutter): 接入分类记录收藏与下载授权 2026-10-17 14:49:16 +08:00
jianghaifeng 00280ca0da merge: 同步 main 的作者标识提交到 develop
main 的 eaf8fb4 机械性地给全仓加了 // Author 头,develop 侧包名已改 fruitshorts、
并删除了 demo_media/home_feed_category/home_feed_sections 三个文件,故按 develop 口径解决冲突:
保留 fruitshorts 包名与删除结果,补回 Author 头。
2026-10-17 13:48:31 +08:00
Vicky 84a1f58a0a test(client): 新增设备侧集成测试(真机跑通后端可达性)
红线第 10 条(设备验证必须设备侧取证)配套:integration_test/app_backend_test.dart
在手机进程里真实请求 /app/home/feed 与 /app/dramas/{id},并用真 App 断言首页剧目列表能渲染。
把 API_BASE_URL 打进设备日志,便于区分地址不对与设备连不上。

注意:真机执行由用户负责(用户 20:45 指令),本文件按原样交付、不再改动。
本项目 iOS 用 SPM,无 Podfile,不需要 pod install。
2026-10-16 18:05:58 +08:00
Vicky d7eff4ca59 feat(client): 对接详情/播放/进度续看/收藏(K1 看剧闭环)+ 三态 UI
后端接口从 6 个扩到 13 个,全部走真数据:

- 新增 api:playback_api(/app/episodes/{id}/play)、watch_api(/app/watch/*)、
  library_api(/app/favorites*);票据从 CatalogApi 搬到 PlaybackApi
- 新增 repository:PlaybackRepository(含票据 TTL 缓存,默认 10 分钟,时钟可注入)、
  FavoriteRepository、WatchHistoryRepository 换成服务端模型(每剧一条续看记录,删掉 demoProfileId)
- PlayerLogic:起播前取票(拿不到给三态 UI,失败可重试);accessType 驱动锁定文案,
  不在前端猜;签名地址原样交给播放器(只补 host,不动 path/query);
  进度上报节流 5s → 10s,切集/退出/切后台各补一次,播完带 finished
- 续看定位:服务端记录里的那一集才 seek(其它集从头播),续看位置进详情/播放页时读一次
- FavoriteController:收藏以服务端为准,乐观更新 + 失败回滚;退出登录清空
- UI 三态:详情页(加载/失败可重试/空剧集 + 续看进度条 + 收藏按钮)、
  播放页(票据加载中/失败可重试/后端判定不可播)、我的页(续看失败可重试、收藏三态、最近观看)
- l10n 新增 8 个键(en/zh/id 三份):网络/进度/收藏失败、锁定提示、续看文案等

测试:flutter test 188 全过(存量 27 个测试文件未跑红);新增票据过期重取、进度节流、
续看定位、收藏回滚、签名 query 原样保留、真实后端端到端联调(live_watch_flow)
2026-10-16 17:39:05 +08:00
Vicky 94ff9f1177 fix(client): 兼容后端 Long 字段字符串化(id 与计数)
后端 J1 起 Long/long 一律以字符串下发(含雪花 id 与 viewCount/expiresIn)。
客户端 id 方向本来就是 (json['id'] ?? '').toString(),不受影响;会被 as num? 打挂的
是 Long 计数,这里加统一兼容解析并替换 3 处:

- lib/core/data/models/json_numbers.dart(新增,接受数字或数字字符串)
- lib/core/data/models/auth_session.dart(expiresIn,后端 long)
- lib/core/data/models/drama.dart(viewCount,后端 Long ×2)
- test/core/json_id_parsing_test.dart(新增 8 条回归用例)

其余 as num?(durationSec/unlockCoin/number/totalEpisodes/freeEpisodes/code)对应后端
Integer/int,仍是数字,未改动;未碰任何业务逻辑与 UI。
2026-10-16 16:44:02 +08:00
Vicky 61454e36b1 feat(client): 改名为 FruitShorts 并新增 id-ID 印尼语文案(1-3 + 1-4 合并)
为什么:a) 应用对外名称要统一成 FruitShorts(产品名,nontonki 是原型代号);
b) 首发市场是印尼,界面必须有印尼语;c) 归档要求"新增 id-ID 与改名合并生成一次 l10n,别分两次"。

改名:
- pubspec name: nontonki → fruitshorts(Dart 包名),description 同步;28 个文件的 package: 导入前缀替换
- Android android:label、iOS CFBundleDisplayName/CFBundleName → FruitShorts
- 图标资源 assets/icon/nontonki.png → fruitshorts.png(含 pubspec 两处引用)
- README 同步(补印尼语说明,顺带修掉写错的模块名 reelshort-app-api → fruitshorts-app-api)

文案:
- 三份 arb 的 appName → FruitShorts、brandMark → FRUITSHORTS、shareFooter → FruitShorts(不留 demo 标记)
- 新增 lib/l10n/app_id.arb:174 个值键与 en/zh 完全一致,按印尼语习惯翻译(Coin→Koin、Episode→Episode)
- 一次性 flutter gen-l10n 生成 app_localizations_id.dart;id 自动进入 supportedLocales,默认 locale 未改
- 新增 test/config/l10n_id_test.dart:钉住 id 文案、supportedLocales 含 id、品牌文案无 demo 标记、占位符渲染

不动:applicationId / bundle id(lumadrama 遗留,等裁定)、目录名、业务逻辑与页面结构、语言选择页(仍只有 en/zh)

门禁:dart analyze lib test 零告警;flutter test 159 全过(基线 155 + 新增 4);残留扫描 0 命中;
三份 arb 键集一致(174/174/174);真机(iPhone iOS 26.6.1)构建安装启动成功,devicectl 列出显示名 FruitShorts
2026-10-16 10:16:59 +08:00
Vicky 0e5d550b50 test(client): 用例名统一为 should_<期望>_when_<条件>(155/155)
为什么:红线第 8 条门禁立即生效,要求 Flutter 测试命名合规;此前 155 条用例名是英文自然语言
或中文描述,合规数 0/155,属存量欠账,按"先补齐再上新功能"在本批处理。

- 25 个测试文件、155 条用例改名,按「文件 + 旧名」精确定位,每条断言命中恰好 1 次才替换
- 只改名字字面量:改动行 155 删 + 155 增,无一行不含字符串字面量(断言与用例体零改动)
- 门禁:dart analyze lib test = No issues found;flutter test = 155 全过
2026-10-16 09:41:14 +08:00
Vicky 1853e1845c refactor(client): 网络层由官方 http 换成 dio(对外行为不变)
为什么:权威文档(AGENTS.md / CODING_STANDARDS.md)强制网络层用 dio,差异清单 A 组第 1 题
用户裁定选 A;原来用官方 http 是在无文档状态下按口头要求选的。

- ApiClient 改用 dio + BaseOptions(validateStatus 恒真、8s 超时、ResponseType.bytes 自行 UTF-8 解码)
- 新增 AuthHeaderInterceptor(Authorization / X-Device-Id)与 EnvelopeInterceptor
  (解包 {code,message,data} + 错误码映射 + 401 判定)
- 401 → 刷新 → 重发一次在 ApiClient 里编排,刻意不放在拦截器内重发:dio 的排队拦截器
  在回调里再发请求会自锁
- 接口层(auth_api/catalog_api)签名、全部 controller/logic/page 未改动
- 测试:新增假 dio adapter 替代 MockClient,断言口径一条未改;用例名改为 should_<期望>_when_<条件>;
  新增 api_client_test(解包/错误映射/请求头/查询参数/跳过重试)与 live_auth_test(真实后端 POST 链路)
- pubspec:加 dio、移除已无引用的 http
- 实测:dart analyze lib test 零告警;flutter test 155 全过;真实联调(8081)GET/POST 链路均通
2026-10-16 09:30:46 +08:00
Vicky ae49b10407 chore(client): 归档发现页 widgets(原型阶段新增目录) 2026-10-15 17:53:54 +08:00
Vicky feecc349a0 chore(client): 归档原型阶段新增文件(配置、主题、模型与测试)
为什么:上一批归档只覆盖了「已跟踪文件的修改」,原型阶段新增的文件(app_config、theme、
home_feed/play_ticket 模型、catalog_api、测试目录等)还是未入库状态 —— 而已入库的代码会 import 它们,
留在工作区会导致 HEAD 处的代码编译不过。一并归档,让 HEAD 自洽。

纯归档,无行为改动。
2026-10-15 17:53:45 +08:00
Vicky e1b98df509 feat(client): C 端 1-2 账号链路(邮箱登录/注册 + 令牌与 401 + 匿名/登录分叉)
为什么:客户端此前完全没有账号概念(mine 页的"登录"只是本地 demo 开关),
而受限动作(收藏/评论)要对到具体账号。本批把账号链路接上,为后续互动、进度同步、下载打底。

- AuthApi + AuthController + AuthSessionStore:注册/登录/登出/刷新,令牌与设备号存 shared_preferences
  (用 SharedPreferencesAsync,与 engagement 仓库同一套 API)
- ApiClient:统一带 Authorization 与 X-Device-Id;401 自动刷新一次并重试,刷新失败清登录态走 onUnauthorized
- 登录/注册页(pages/auth,沿用 binding/logic/view 与现有视觉):邮箱格式、密码 8-64 位含字母数字本地先校验,
  错误提示走 l10n;登录/注册一键切换
- 匿名/登录分叉:首页、发现、详情保持匿名可看;收藏/点赞等受限动作走 ensureSignedIn() 弹底部提示引导登录
- mine 页展示真实账号(昵称 + uid),拿不到才回落到演示文案
- 测试:新增 auth 控制器(注册落盘/401 刷新重试/刷新失败清会话/登出)与登录页(校验/切换)用例;
  widget_test 改为预置本地会话(账号不再是本地开关);修掉 playback 用例对 host 大小写的环境依赖断言

门禁:flutter analyze 0 问题;flutter test 148 全过。
2026-10-15 17:53:35 +08:00
Vicky 77d5748dbb chore(client): 归档原型阶段未提交改动(binding/logic/view 分层与各页迭代)
为什么:客户端原型在 14d7bd3 之后又做了一轮(页面分层、发现页、分类滑动、播放器控件等),
一直留在工作区没入库。下一批账号链路要改其中的 app.dart / home_view / player_view / mine_view,
先归档这批既存改动,本批的实际 diff 才说得清边界。

只归档,不改行为:当前工作区 flutter analyze 0 问题、flutter test 148 全过。
2026-10-15 17:53:27 +08:00
627 changed files with 95004 additions and 6336 deletions

No files matched your search

+25
View File
@@ -0,0 +1,25 @@
# Author: fruit.com.cn
# ReelShort 客户端 CI:静态分析 + 测试门禁
name: flutter-ci
on:
push:
branches: [develop, main]
pull_request:
branches: [develop, main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
- name: 依赖
run: flutter pub get
- name: 静态分析(零告警门禁)
run: dart analyze --fatal-infos
- name: 单元测试(全绿门禁)
run: flutter test -r expanded
+10 -1
View File
@@ -45,7 +45,13 @@ app.*.map.json
/android/app/release
# Local demo media and internal planning are absent from the public repo.
/assets/
# Launcher/splash source icons are required at build time, so keep them tracked.
/assets/*
!/assets/icon/
!/assets/icon/*.png
!/assets/fonts/
!/assets/fonts/*.ttf
!/assets/fonts/OFL-*.txt
/server/
/docs/
/AGENTS.md
@@ -59,3 +65,6 @@ app.*.map.json
.env
.env.*
!.env.example
# IDE 的 Flutter Widget Preview 生成目录。
.widget_preview/
+206 -11
View File
@@ -1,17 +1,212 @@
一个使用 Flutter 开发的短剧播放 App 原型,面向海外用户,重点探索沉浸式刷剧、找剧和连续观看的移动端体验
# FruitShorts(ReelShort)客户端
## 主要功能
FruitShorts 是面向印尼市场的短剧 Flutter 客户端,已连接 `FruitServer` 的
`/app/**` 接口。功能覆盖:四 tab 主页(推荐视频流 / 找剧 / 追剧 / 我的)、
沉浸式播放与全屏剧集播放器、清晰度切换、剧单与排行榜、新剧页、搜索、观看记录、
收藏短剧、评论与点赞、站内通知、钱包与商品、下载落盘与离线播放、中英印尼三语,
以及 Android / iOS 双端画中画(退到后台自动进小窗)。
- **沉浸式首页**:竖屏播放,上下滑切换短剧,左右滑切换内容类别;支持拖动进度、横屏全屏观看。
- **剧集播放**:从详情页进入后按集连续播放,支持选集和本地续看。
- **找剧与个人页**:浏览、搜索和查看剧集详情;在个人页查看观看记录、收藏与喜欢的内容。
- **互动与多语言**:本地演示评论、系统分享,以及英文和简体中文界面。
- **画中画**:Android 接入系统画中画;iOS 画中画尚未实现。
## 技术栈
## 技术实现
- Flutter 3.47.7 / Dart 3.13.5(`environment.sdk: ^3.12.2`)
- Provider 状态管理与依赖注入
- dio 网络请求
- video_player 播放
- shared_preferences 与 `SharedPreferencesAsync` 本地配置
- path_provider + dart:io 下载落盘(先写 `.part`,完成后原子改名)
- flutter_localizations,支持 `en`、`zh`、`id`
- 画中画:Android 用 `floating` + `permission_handler`(需悬浮窗权限);
iOS 用原生 `AVPictureInPictureController` + 影子播放器(`ios/Runner/AppDelegate.swift`)
项目使用 Flutter、Provider 和 `video_player`。播放会话统一管理播放器生命周期;观看进度和演示互动数据保存在本机。首页、找剧、我的共用同一个根页面,切换时保留页面状态。
## 目录结构
## 当前说明
```text
lib/
core/
config/ 地址、语言和运行时配置
data/ models、remote API、repository、controller
formatting/ 文案与日期格式化
platform/ 平台能力:画中画通道、设备号、触觉
theme/ 颜色与主题
widgets/ 跨页面复用组件
l10n/ 三语 ARB 与生成代码
pages/ 业务页面
test/ 单元、组件、页面和联调测试
integration_test/ 设备侧集成测试
android/ Android 工程
ios/ iOS 工程与 SPM 配置
assets/ 图标、图片和本地媒体文件
```
现阶段的剧目与剧集是演示数据,多部剧暂时复用同一段测试视频。
## 已接接口
以下接口均在 `lib/core/data/remote/` 中有实际调用,共 47 个不同路径。
| 分组 | 接口 |
| --- | --- |
| 账号 | `POST /app/auth/register`、`/login`、`/refresh`、`/logout`、`/otp`;`GET /app/me`、`/app/me/devices`、`/app/me/export`;`POST /app/me/deactivate`、`/app/me/deactivate/cancel` |
| 内容 | `GET /app/home/feed`、`/app/feed`、`/app/discover/feed`、`/app/categories`、`/app/categories/{id}/dramas`、`/app/drama-categories`、`/app/dramas/page`、`/app/dramas/{id}`、`/app/tags`、`/app/search`、`/app/playlists`、`/app/playlists/{id}` |
| 播放 | `GET /app/episodes/{id}/play`、`/app/episodes/{id}/stats`;`POST /app/episodes/{id}/unlock` |
| 观看记录 | `POST/GET /app/watch/progress`、`GET /app/watch/recent`、`DELETE /app/watch/progress/{dramaId}`、`DELETE /app/watch/progress` |
| 收藏 | `POST /app/favorites/toggle`、`GET /app/favorites`、`DELETE /app/favorites/{dramaId}`、`POST /app/favorites/remove` |
| 下载 | `GET /app/downloads/policy`、`PUT /app/downloads/settings`、`POST/GET /app/downloads`、`GET /app/downloads/{id}/url`、`POST /app/downloads/{id}/progress`、`DELETE /app/downloads/{id}`、`DELETE /app/downloads` |
| 互动 | `GET/POST /app/comments`、`DELETE /app/comments/{id}`、`GET /app/comments/count`、`POST /app/likes/toggle`、`POST /app/reports` |
| 商业化 | `GET /app/wallet`、`/app/products`、`/app/orders` |
| 公告 | `GET /app/notices` |
客户端没有调用 `/app/config` 和创作者入驻接口。
## 运行
### 安装依赖
```bash
flutter pub get
```
### Android USB
手机开启 USB 调试并连接电脑后:
```bash
adb devices
adb reverse tcp:8081 tcp:8081
flutter run -d <device>
```
`adb reverse` 将手机的 `127.0.0.1:8081` 转发到电脑;USB 断开或重新连接后需要
重新执行。此时用调试条把地址改成 `http://127.0.0.1:8081`(见下节)。
Android 模拟器访问宿主机时,同样在调试条里填 `http://10.0.2.2:8081`。
### iOS
iOS 模拟器直接访问 Mac 本机服务,默认地址为 `http://127.0.0.1:8081`:
```bash
flutter run -d <simulator-udid>
```
iPhone 真机不能用 `127.0.0.1` 访问 Mac;真机联调时需让手机与 Mac 位于同一网络,
并通过调试入口临时填写 Mac 的局域网地址。
### 服务地址配置
地址是代码里的一个开关,**不做任何网络探测**,也不用传构建参数。两套环境各占
一个文件:
| 文件 | 用途 | 默认值 |
| --- | --- | --- |
| `lib/core/config/dev_config.dart` | 开发 / 联调 | `http://127.0.0.1:8081` |
| `lib/core/config/prod_config.dart` | 生产 | `http://39.162.50.99:8081` |
切换环境只改 `lib/core/config/app_config.dart` 里的一个布尔值:
```dart
static const bool useDevConfig = true; // false 即用生产地址
```
真机或 Android 模拟器联调时,通过调试入口覆盖地址,不修改默认开发配置。打生产包前把
`useDevConfig` 改成 `false`;release 构建若仍为 `true` 会在启动时直接报错,
避免线上包连到开发机。
debug 包在找剧页加载失败时会显示调试条,点「修改服务器地址」可临时改成当前
电脑 IP,保存后立即重载、不需要重新打包,该覆盖保存在本地偏好里(仅 debug
生效,release 不读取)。
## 画中画
| 平台 | 行为 |
| --- | --- |
| Android | 首次点播放页小窗按钮时申请悬浮窗权限;授权后退出 App 自动进入系统小窗。沉浸流与独立播放页共用一份引用计数,任一处活跃都算「在播」,避免互相顶掉 |
| iOS | 原生影子播放器承载 `AVPictureInPictureController`,只有播放页处于活跃状态时退到后台才自动进小窗;停在剧单等页面退后台不会触发 |
- 小窗内切集直接换源,不走页面翻页,可连续自动播放
- iOS 从系统小窗还原时用原生过渡层盖住纹理黑帧,等首帧就绪后再淡出
- 播放页「更多」面板里有「退出自动小窗」开关(仅支持自动小窗的平台显示),关掉后
退到后台不再进入小窗
## 环境注意
### iOS Swift 版本
`pubspec.yaml` 将 `shared_preferences_foundation` 固定为 `2.5.6`。该版本兼容
Xcode 16.2 / Swift 6.0.3;`2.6.0` 的生成代码使用 Swift 6.1 才支持的 trailing
comma。不要手工升级该依赖,`flutter pub get` 后应显示
`shared_preferences_foundation 2.5.6 (overridden)`。
### iOS SPM 包缺失
如果 Xcode 报 `Missing package product FlutterGeneratedPluginSwiftPackage`:
```bash
rm -rf ~/Library/Developer/Xcode/DerivedData/Runner-*
flutter clean
flutter pub get
xcodebuild -resolvePackageDependencies \
-project ios/Runner.xcodeproj \
-scheme Runner
```
随后重新打开工程构建。`Runner.xcscheme` 的 PreAction 会先执行
`flutter pub get`,再执行 Flutter prepare。
### Android libdartjni
若启动时报 `libdartjni.so` 加载失败,通常是旧安装中的原生库残留:
```bash
adb uninstall com.stormg.lumadrama
flutter run -d <device>
```
## 生产打包(Android)
两个渠道出两种产物,源码同一套:
| 渠道 | 产物 | 命令 | 体积 |
| --- | --- | --- | --- |
| Google Play | AAB(按设备下发) | `flutter build appbundle --release` | 上传 28MB,用户下载约 8MB |
| 国内各大市场 | 通用 APK(arm64 + v7) | `flutter build apk --release` | 约 13MB |
说明:
- `android/app/build.gradle.kts` 的 `release` 已配置 `abiFilters`,只保留 `arm64-v8a` 与 `armeabi-v7a`,插件也不会再带入 `x86_64` 原生库。
- `android/gradle.properties` 里固定了 `split-debug-info=build/symbols` 与 `dart-obfuscation=true`,**IDE 里点 Build APK / Build App Bundle 也会生效**:`libapp.so` 从 ~8MB/ABI 降到 ~1.1MB/ABI。
- `useLegacyPackaging = true`:原生库以压缩形式存储,这是国内 APK 从 40MB 降到 13MB 的主要原因;代价是安装时解压。
- **每次发版都要归档 `build/symbols`**,线上崩溃栈需要它还原。
- release 仍在用 debug 签名(`build.gradle.kts` 的 TODO),上架前必须换成正式 keystore。
- 不要用 `--split-per-abi`:与 `abiFilters` 冲突;Play 的 AAB 已经按设备拆好。
## 生产打包(iOS)
```bash
# 正式签名(需要 iOS Distribution 证书 + App Store 描述文件)
flutter build ipa --release
# 只有 Apple Development 证书时:出开发签名 IPA,只能装已注册设备
flutter build ipa --release --export-method development
```
产物在 `build/ios/ipa/*.ipa`。画中画依赖 `UIBackgroundModes` 里的 `audio` 与
`AVPictureInPictureController`,改动 `ios/Runner/Info.plist` 或 `AppDelegate.swift`
后要重新构建,热重载不会生效。
## 门禁
```bash
dart analyze
flutter test
flutter build apk --debug
```
当前基线:`dart analyze` 仅 1 条既存 info(`intl` 未在 `pubspec.yaml` 声明);
`flutter test` 全量 775 例通过。测试文件按模块放在 `test/` 下,用例名使用
`should_<期望>_when_<条件>`。真实后端联调测试在服务未启动时会跳过。
## 当前边界
- 下载会落盘并支持离线播放,但不支持断点续传:中途失败会丢弃 `.part` 重下。
- 支付与金币:客户端已有钱包、商品、下单接口与页面,商店票据校验依赖服务端配置凭据。
- 站内通知没有已读/未读和红点状态。
- 播放清晰度切换会保留旧画面 500ms 掩盖闪屏,切换瞬间的进度以相对位置续播。
+1
View File
@@ -1,4 +1,5 @@
/.gradle
/build/
/captures/
/local.properties
GeneratedPluginRegistrant.java
+24 -4
View File
@@ -8,7 +8,9 @@ plugins {
android {
namespace = "com.stormg.lumadrama"
compileSdk = flutter.compileSdkVersion
// permission_handler_android 14 的 AAR 元数据要求依赖方 compileSdk >= 37,
// 固定为 37(比 Flutter 默认的 36 高一档,只影响编译期 API 可见性)。
compileSdk = 37
ndkVersion = flutter.ndkVersion
compileOptions {
@@ -29,12 +31,20 @@ android {
buildTypes {
release {
// 生产包只保留 arm64-v8a 与 armeabi-v7a,避免插件带入 x86_64 原生库
ndk {
abiFilters += listOf("arm64-v8a", "armeabi-v7a")
}
// R8 裁剪未使用的 Java/Kotlin 代码与资源,减小 dex 与 res。
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
// 反编译保护(法律红线配套):开启 R8 混淆 + 资源压缩,发布前替换正式签名。
isMinifyEnabled = true
isShrinkResources = true
}
}
}
@@ -45,6 +55,16 @@ kotlin {
}
}
// 生产包不允许出现 x86_64:部分插件 AAR 会带入少量 x86_64 原生库,这里显式剔除。
androidComponents {
onVariants(selector().withBuildType("release")) { variant ->
variant.packaging.jniLibs.excludes.add("lib/x86_64/**")
// 原生库随 APK 压缩存储:下载体积明显小于解压后的体积,代价是安装时解压。
// 本包 90% 体积是 so,这一项对国内商店的 APK 分发影响最大。
variant.packaging.jniLibs.useLegacyPackaging = true
}
}
flutter {
source = "../.."
}
+11
View File
@@ -0,0 +1,11 @@
# Author: fruit.com.cn
#
# R8 保号规则:Flutter 引擎与插件自带的 consumer rules 已覆盖大部分场景,
# 这里只补本项目原生层通过反射/通道访问、裁剪后容易失效的入口。
# 画中画与权限通道由 MainActivity 直接注册,保留应用包名下的入口类。
-keep class com.stormg.lumadrama.** { *; }
# Flutter 引擎在 release 下通过 JNI 回调这些类,禁止改名。
-keep class io.flutter.** { *; }
-dontwarn io.flutter.embedding.**
@@ -6,4 +6,6 @@
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- 开发期后端是明文 HTTP,仅调试构建放开。 -->
<application android:usesCleartextTraffic="true"/>
</manifest>
+16 -10
View File
@@ -1,10 +1,15 @@
<!-- Author: fruit.com.cn -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission
android:name="android.permission.INTERNET"/>
<!-- 画中画自动触发依赖的「显示在其他应用上层」权限(部分 ROM 用它管控 PiP) -->
<uses-permission
android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application
android:label="Nontonki"
android:label="ymb"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:exported="true"
@@ -15,15 +20,14 @@
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
android:windowSoftInputMode="adjustNothing">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
@@ -33,7 +37,7 @@
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
android:value="2"/>
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
@@ -42,8 +46,10 @@
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
<action
android:name="android.intent.action.PROCESS_TEXT"/>
<data
android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
@@ -4,18 +4,48 @@ package com.stormg.lumadrama
import android.content.Intent
import android.app.PictureInPictureParams
import android.content.Context
import android.content.res.Configuration
import android.graphics.Bitmap
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.Settings
import android.telephony.TelephonyManager
import android.util.Rational
import android.content.pm.PackageManager
import android.view.WindowManager
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import java.io.ByteArrayOutputStream
import java.util.Locale
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicBoolean
class MainActivity : FlutterActivity() {
private val shareRequestCode = 9041
private val thumbnailExecutor: ExecutorService = Executors.newSingleThreadExecutor()
private val thumbnailHandler = Handler(Looper.getMainLooper())
private var pendingShare: MethodChannel.Result? = null
private var pipChannel: MethodChannel? = null
private var keyboardChannel: MethodChannel? = null
private var thumbnailChannel: MethodChannel? = null
private var imeInset = 0
override fun onCreate(savedInstanceState: Bundle?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
splashScreen.setOnExitAnimationListener { screen -> screen.remove() }
}
super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
@@ -39,6 +69,23 @@ class MainActivity : FlutterActivity() {
result.success(false)
}
}
// 设备标识:SSAID 免权限,卸载重装不变,避免客户端自己生成的 id 每次重装都变
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.stormg.lumadrama/device")
.setMethodCallHandler { call, result ->
when (call.method) {
"ssaid" -> {
val value = try {
Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
} catch (_: Exception) {
null
}
result.success(value)
}
// 设备区域:注册时作为服务端 IP 解析失败后的兜底
"region" -> result.success(currentRegion())
else -> result.notImplemented()
}
}
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.stormg.lumadrama/share")
.setMethodCallHandler { call, result ->
if (call.method != "shareText") {
@@ -62,8 +109,155 @@ class MainActivity : FlutterActivity() {
result.error("share_failed", error.message, null)
}
}
keyboardChannel = MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
"com.stormg.lumadrama/keyboard"
)
keyboardChannel?.setMethodCallHandler { call, result ->
if (call.method != "get") {
result.notImplemented()
return@setMethodCallHandler
}
val inset = currentImeInset()
emitImeInset(inset)
result.success(inset)
}
thumbnailChannel = MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
"com.stormg.lumadrama/thumbnail"
)
thumbnailChannel?.setMethodCallHandler { call, result ->
if (call.method != "frame") {
result.notImplemented()
return@setMethodCallHandler
}
val timeMs = (call.argument<Number>("timeMs"))?.toLong()
if (timeMs == null) {
result.success(null)
return@setMethodCallHandler
}
val localPath = call.argument<String>("localPath").orEmpty()
val remoteUrl = call.argument<String>("url").orEmpty()
val rawHeaders = call.argument<Map<*, *>>("headers")
val headers = rawHeaders?.entries
?.associate { it.key.toString() to it.value.toString() }
.orEmpty()
val responded = AtomicBoolean(false)
val task = thumbnailExecutor.submit {
val bytes = renderThumbnail(
timeMs = timeMs,
localPath = localPath,
remoteUrl = remoteUrl,
headers = headers
)
thumbnailHandler.post {
if (responded.compareAndSet(false, true)) {
result.success(bytes)
}
}
}
thumbnailHandler.postDelayed({
if (responded.compareAndSet(false, true)) {
task.cancel(true)
result.success(null)
}
}, thumbnailTimeoutMs)
}
val decorView = window.decorView
ViewCompat.setOnApplyWindowInsetsListener(decorView) { view, insets ->
emitImeInset(insets.getInsets(WindowInsetsCompat.Type.ime()).bottom)
ViewCompat.onApplyWindowInsets(view, insets)
}
ViewCompat.requestApplyInsets(decorView)
}
private fun currentImeInset(): Int =
ViewCompat.getRootWindowInsets(window.decorView)
?.getInsets(WindowInsetsCompat.Type.ime())
?.bottom
?: 0
/** 网络运营商国家优先,其次 SIM 卡,最后系统语言;都拿不到返回空串。 */
private fun currentRegion(): String {
return try {
val telephony = getSystemService(Context.TELEPHONY_SERVICE) as? TelephonyManager
val localeCountry = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
resources.configuration.locales.get(0).country
} else {
@Suppress("DEPRECATION")
resources.configuration.locale.country
}
val candidates = listOfNotNull(
telephony?.networkCountryIso,
telephony?.simCountryIso,
localeCountry
)
candidates.firstOrNull { it.isNotBlank() }?.uppercase(Locale.ROOT) ?: ""
} catch (_: Exception) {
// 个别 ROM 读运营商信息会抛 SecurityException,别让注册流程卡住
""
}
}
private fun emitImeInset(value: Int) {
if (value == imeInset) return
imeInset = value
keyboardChannel?.invokeMethod("insetsChanged", value)
}
private fun renderThumbnail(
timeMs: Long,
localPath: String,
remoteUrl: String,
headers: Map<String, String>
): ByteArray? {
val retriever = MediaMetadataRetriever()
var frame: Bitmap? = null
var scaled: Bitmap? = null
return try {
when {
localPath.isNotEmpty() -> retriever.setDataSource(normalizeLocalPath(localPath))
remoteUrl.isNotEmpty() -> retriever.setDataSource(remoteUrl, headers)
else -> return null
}
frame = retriever.getFrameAtTime(
timeMs * 1000,
MediaMetadataRetriever.OPTION_CLOSEST_SYNC
)
val source = frame ?: return null
scaled = if (source.width > thumbnailMaxWidth) {
val targetHeight = (source.height * thumbnailMaxWidth / source.width)
.coerceAtLeast(1)
Bitmap.createScaledBitmap(
source,
thumbnailMaxWidth,
targetHeight,
true
)
} else {
source
}
val outputBitmap = scaled ?: source
val output = ByteArrayOutputStream()
val compressed = outputBitmap.compress(
Bitmap.CompressFormat.JPEG,
thumbnailJpegQuality,
output
)
if (!compressed) return null
output.toByteArray()
} catch (_: Exception) {
null
} finally {
if (scaled != null && scaled !== frame) scaled.recycle()
frame?.recycle()
retriever.release()
}
}
private fun normalizeLocalPath(path: String): String =
if (path.startsWith("file:")) Uri.parse(path).path ?: path else path
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
pipChannel?.invokeMethod("onPipChanged", isInPictureInPictureMode)
@@ -77,4 +271,22 @@ class MainActivity : FlutterActivity() {
pendingShare = null
}
}
override fun onDestroy() {
ViewCompat.setOnApplyWindowInsetsListener(window.decorView, null)
keyboardChannel?.setMethodCallHandler(null)
keyboardChannel = null
thumbnailChannel?.setMethodCallHandler(null)
thumbnailChannel = null
thumbnailHandler.removeCallbacksAndMessages(null)
thumbnailExecutor.shutdownNow()
super.onDestroy()
}
companion object {
private const val TAG = "FruitPip"
private const val thumbnailMaxWidth = 240
private const val thumbnailJpegQuality = 70
private const val thumbnailTimeoutMs = 1500L
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Author: fruit.com.cn -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/splash_background" />
<item>
<shape android:shape="rectangle">
<gradient
android:type="radial"
android:centerX="0"
android:centerY="0"
android:gradientRadius="350dp"
android:startColor="#FF6B011A"
android:centerColor="#616B011A"
android:endColor="#006B011A" />
</shape>
</item>
<item>
<bitmap
android:gravity="center"
android:src="@drawable/splash_screen" />
</item>
</layer-list>
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Author: fruit.com.cn -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/splash_background" />
<item>
<shape android:shape="rectangle">
<gradient
android:type="radial"
android:centerX="0"
android:centerY="0"
android:gradientRadius="350dp"
android:startColor="#FF6B011A"
android:centerColor="#616B011A"
android:endColor="#006B011A" />
</shape>
</item>
<item>
<bitmap
android:gravity="center"
android:src="@drawable/splash_screen" />
</item>
</layer-list>
Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

@@ -1,11 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Author: fruit.com.cn -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/splash_background" />
<item>
<shape android:shape="rectangle">
<gradient
android:type="radial"
android:centerX="0"
android:centerY="0"
android:gradientRadius="350dp"
android:startColor="#FF6B011A"
android:centerColor="#616B011A"
android:endColor="#006B011A" />
</shape>
</item>
<item>
<bitmap
android:gravity="center"
android:src="@drawable/splash_icon" />
android:src="@drawable/splash_screen" />
</item>
</layer-list>
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Author: fruit.com.cn -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/splash_background" />
<item>
<shape android:shape="rectangle">
<gradient
android:type="radial"
android:centerX="0"
android:centerY="0"
android:gradientRadius="350dp"
android:startColor="#FF6B011A"
android:centerColor="#616B011A"
android:endColor="#006B011A" />
</shape>
</item>
<item>
<bitmap
android:gravity="center"
android:src="@drawable/splash_screen" />
</item>
</layer-list>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

@@ -1,11 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Author: fruit.com.cn -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/splash_background" />
<item>
<shape android:shape="rectangle">
<gradient
android:type="radial"
android:centerX="0"
android:centerY="0"
android:gradientRadius="350dp"
android:startColor="#FF6B011A"
android:centerColor="#616B011A"
android:endColor="#006B011A" />
</shape>
</item>
<item>
<bitmap
android:gravity="center"
android:src="@drawable/splash_icon" />
android:src="@drawable/splash_screen" />
</item>
</layer-list>
@@ -1,6 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Author: fruit.com.cn -->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 813 B

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -1,11 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Author: fruit.com.cn -->
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:windowSplashScreenBackground">@color/splash_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#000000</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
<item name="android:windowSplashScreenIconBackgroundColor">@color/splash_background</item>
<item name="android:windowSplashScreenAnimationDuration">0</item>
</style>
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@color/splash_background</item>
</style>
</resources>
@@ -1,12 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Author: fruit.com.cn -->
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
@@ -15,6 +18,6 @@
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
<item name="android:windowBackground">@color/splash_background</item>
</style>
</resources>
@@ -1,11 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Author: fruit.com.cn -->
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:windowSplashScreenBackground">@color/splash_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#000000</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
<item name="android:windowSplashScreenIconBackgroundColor">@color/splash_background</item>
<item name="android:windowSplashScreenAnimationDuration">0</item>
</style>
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">@color/splash_background</item>
</style>
</resources>
+3 -3
View File
@@ -2,6 +2,6 @@
<!-- Author: fruit.com.cn -->
<resources>
<color name="splash_background">#FF000000</color>
<color name="ic_launcher_background">#D74A7D</color>
</resources>
<color name="splash_background">#000000</color>
<color name="ic_launcher_background">#E9142E</color>
</resources>
+5 -2
View File
@@ -1,12 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Author: fruit.com.cn -->
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
@@ -15,6 +18,6 @@
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
<item name="android:windowBackground">@color/splash_background</item>
</style>
</resources>
@@ -6,4 +6,6 @@
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- 开发期后端是明文 HTTP,仅调试构建放开。 -->
<application android:usesCleartextTraffic="true"/>
</manifest>
+5
View File
@@ -6,3 +6,8 @@ android.useAndroidX=true
android.newDsl=false
# This builtInKotlin flag was added by the Flutter template
android.builtInKotlin=false
# release 包默认剥离 Dart 调试符号并混淆:libapp.so 由 ~8MB 降到 ~1MB/ABI。
# 符号表输出到 build/symbols,崩溃分析时用它还原堆栈,发布前务必归档。
split-debug-info=build/symbols
dart-obfuscation=true
+93
View File
@@ -0,0 +1,93 @@
Copyright 2021 The Outfit Project Authors (https://github.com/Outfitio/Outfit-Fonts)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
+93
View File
@@ -0,0 +1,93 @@
Copyright 2020 The Plus Jakarta Sans Project Authors (https://github.com/tokotype/PlusJakartaSans)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1004 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 734 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

+99
View File
@@ -0,0 +1,99 @@
# 客户端文件说明
## 目录树
```text
.
├── android/ Android 工程与 Gradle 配置
├── assets/
│ ├── icon/ 启动图标
│ ├── images/ 本地图片
│ └── media/ 本地视频文件(当前未在 pubspec 声明)
├── integration_test/ 设备侧集成测试
├── ios/
│ ├── Flutter/ Flutter 生成配置与 SPM 临时包
│ ├── Runner/ iOS Runner
│ ├── Runner.xcodeproj/ Xcode 工程与共享 Scheme
│ └── RunnerTests/ iOS 原生测试
├── lib/
│ ├── core/
│ │ ├── config/ 地址、语言、运行时服务器设置
│ │ ├── data/ models、remote、repositories、services
│ │ ├── formatting/ 多语言文案、日期和错误文本
│ │ ├── platform/ 平台能力封装
│ │ ├── theme/ 颜色与 ThemeData
│ │ └── widgets/ 通用组件
│ ├── l10n/ ARB 与本地化生成代码
│ └── pages/ 业务页面
├── test/ 单元和 Widget 测试
├── docs/ 客户端任务与文件说明
├── pubspec.yaml 依赖、资源和 Flutter 配置
└── README.md 运行、接口与环境说明
```
## 关键文件
| 路径 | 职责 |
| --- | --- |
| `lib/main.dart` | 初始化 Flutter、屏幕方向、debug 服务器覆盖并启动 App |
| `lib/app.dart` | Provider、repository、controller、ApiClient 和主题装配 |
| `lib/core/config/app_config.dart` | 地址优先级、超时、媒体 URL 和平台默认地址 |
| `lib/core/config/server_endpoint.dart` | debug 运行时地址规范化、读取、保存和清除 |
| `lib/core/config/locale_controller.dart` | 语言选择与系统语言回退 |
| `lib/core/data/models/` | 账号、剧目、评论、观看、下载、公告等数据结构 |
| `lib/core/data/remote/` | `/app/**` 接口调用;`api_client.dart` 统一鉴权和错误映射 |
| `lib/core/data/repositories/` | HTTP 与内存实现的 repository 边界 |
| `lib/core/data/services/` | 页面 controller、播放会话、分享、画中画等业务服务 |
| `lib/core/formatting/` | 本地化内容、日期与错误文本 |
| `lib/core/platform/haptics.dart` | 可注入的触觉反馈封装 |
| `lib/core/theme/app_colors.dart` | 克莱因蓝和界面颜色 token |
| `lib/core/theme/app_theme.dart` | light/dark ColorScheme 与组件主题 |
| `lib/core/widgets/empty_state.dart` | 统一空列表占位 |
| `lib/core/widgets/remote_image.dart` | 后端相对图片地址与失败占位 |
| `lib/core/widgets/auth_gate.dart` | 未登录操作拦截 |
| `lib/core/widgets/download_action.dart` | 下载授权操作 |
| `lib/pages/home/` | 根页面、四个底部 tab 和抽屉 |
| `lib/pages/player/` | 竖屏播放、选集、进度和播放器交互 |
| `lib/pages/discover/` | 首页/发现信息流、轮播、分类与三列剧目网格 |
| `lib/pages/drama_detail/` | 剧目详情与剧集入口 |
| `lib/pages/auth/` | 登录/注册页面与表单逻辑 |
| `lib/pages/mine/` | 我的页、片单、设置、语言与 debug 地址入口 |
| `lib/pages/favorites/` | 收藏列表 |
| `lib/pages/watch_records/` | 独立观看记录页 |
| `lib/pages/downloads/` | 下载授权与策略页面 |
| `lib/pages/notices/` | 站内通知分页和详情 |
| `lib/pages/interaction/` | 举报弹窗 |
| `lib/l10n/*.arb` | en/zh/id 源文案,修改后执行 `flutter gen-l10n` |
| `ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme` | iOS SPM 预构建脚本 |
## 测试结构
| 目录 | 覆盖范围 |
| --- | --- |
| `test/auth/` | 登录 controller、表单、视觉与交互 |
| `test/catalog/`、`test/category/` | 首页数据、分类和模型解析 |
| `test/comment/`、`test/interaction/` | 评论、点赞、举报和弹窗 |
| `test/config/` | 三语、系统语言和运行时服务器地址 |
| `test/core/` | ApiClient、ID 解析、触觉、分享和画中画 |
| `test/discover/`、`test/home/` | 首页、轮播、网格和导航 |
| `test/download/`、`test/library/`、`test/watch/` | 下载、收藏、观看记录 |
| `test/drama_detail/`、`test/player/`、`test/playback/` | 详情、播放器与播放队列 |
| `test/notice/` | 通知分页、空态、详情与入口 |
| `test/empty_state/`、`test/theme/` | 空态组件与主题 token |
| `test/integration/` | 依赖本机后端的联调测试,服务未启动时跳过 |
| `integration_test/app_backend_test.dart` | 设备侧后端连通性测试 |
| `test/support/` | fake repository、HTTP adapter、播放器和测试数据 |
## 生成物
以下内容由工具生成,不要手工修改:
- `lib/l10n/app_localizations*.dart`
- `*.g.dart`
- `.dart_tool/`
- `build/`
- `ios/Flutter/ephemeral/`
- `ios/Runner/GeneratedPluginRegistrant.h`
- `ios/Runner/GeneratedPluginRegistrant.m`
`pubspec.lock` 由 Dart 包管理工具维护,应提交但不手工编辑。
+38
View File
@@ -0,0 +1,38 @@
# 客户端任务清单
状态按当前 `develop` 中已合入的功能整理。契约章节以
`FruitServer/docs/api-phase1.md` 为准;任务过程记录见
`FruitServer/docs/plans/PROGRESS.md`。
## K1-K13
| 批次 | 范围 | 状态 | 关键提交或契约章节 | 备注 |
| --- | --- | --- | --- | --- |
| K1 | 详情、播放票据、观看进度、续看、收藏 | 已完成并合入 | §1.3-§1.5 | 已有三态和基础页面 |
| K2 | 分类、观看记录、收藏列表、下载授权管理 | 已完成并合入 | §1.2、§1.5、§1.6 | 下载只管理授权,不落盘 |
| K3 | 评论、回复、点赞、举报、分享 | 已完成并合入 | §1.7 | 分享使用系统分享面板 |
| K4 | 账号剩余能力 | 未开始 | §1.1 | Google 登录所需凭据和契约范围需核对 |
| K5 | 克莱因蓝主题、印尼语适配 | 已完成并合入 | 无接口变更 | 三语为 en/zh/id |
| K6 | 登录页、底部导航、触觉反馈 | 已完成并合入 | 无接口变更 | 触觉通过 `Haptics` 封装 |
| K7 | 首页轮播、站内通知 | 已完成并合入 | §1.3、§1.9.2 | 通知无已读态 |
| K8 | 登录页视觉重设计 | 已完成并合入 | 无接口变更 | 保留原有登录逻辑和校验 |
| K9 | 首页全部剧目双列网格 | 已完成并合入 | 无接口变更 | 数据源为 feed 分类结果 |
| K10 | 统一空列表占位 | 已完成并合入 | 无接口变更 | 评论弹窗使用 compact 变体 |
| K11 | 首页三列、分类首位“全部” | 已完成并合入 | 无接口变更 | 网格比例保持 2:3 |
| K12 | 首页去标题、空态视觉重做 | 已完成并合入 | 无接口变更 | 双层同心圆空态 |
| K13 | debug 运行时服务器地址 | 已完成并合入 | 无接口变更 | 保存后重启生效,release 不开放 |
## 待办
| 事项 | 状态 | 依赖或说明 |
| --- | --- | --- |
| K4 账号剩余能力 | 待办 | Google 登录需要客户端凭据、平台配置和后端契约,范围需核对 |
| B1 支付与金币 | 待办 | 客户端尚未接入 §1.8 的支付/权益闭环,商品、渠道和验证方式需核对 |
| 搜索 | 待办 | 当前没有独立搜索接口,接口契约和数据范围需核对 |
| 下载落盘 | 待办 | 需要新增文件下载、进度持久化、续传、离线播放和本地清理设计 |
## 维护规则
- 新批次完成后更新本表,不写未经确认的提交号。
- 功能状态以当前 `develop` 代码和自动化测试为准。
- “需核对”表示产品范围、接口或外部凭据尚未确认,不能按已完成处理。
@@ -0,0 +1,28 @@
# ReelShort 客户端深色视觉改造
## 目标
- 深色设为默认且唯一主题,页面、卡片、浮层、播放器和底部导航保持一致。
- 色板收敛到 `AppColors`,主题组件配置收敛到 `AppTheme`。
- 保持现有布局、信息结构、路由、业务逻辑和文案不变。
## 结果
- 背景 `#0B1020`、卡片 `#151B2E`、浮层 `#1C2436`、分割线白色 6%。
- 主文字白色 92%、次文字 64%、禁用 38%。
- 主色 `#FF6B2C`,辅助链接色 `#22D3EE`。
- 卡片 12、按钮/输入框 10、标签 6,阴影关闭,组件使用分割线分层。
- 页面级透明、遮罩、阴影、媒体底、玻璃面和渐变全部映射到 AppColors 语义 token。
- 系统栏统一浅色图标,AppBar 与底部导航使用深色表面。
## 验证
- 主题 token、对比度、系统栏、圆角与关键页 smoke 测试。
- `dart analyze lib test`
- `flutter test --no-pub`
- `flutter build apk --debug`
## 约束
- 不提交 `pubspec.lock` 漂移。
- 不启停服务、不操作真机。
@@ -0,0 +1,24 @@
# Android Studio 调试 HTTP 日志
## 目标
- 仅在调试构建中输出 Dio 请求、响应与异常日志。
- Android Studio Run 控制台通过 `debugPrint` 展示,logcat 标签保持 `flutter`。
- Authorization 脱敏,X-Device-Id 保留,二进制响应只显示字节数。
- 单条请求体或响应体最多 3000 字符。
## 实现步骤
1. 新增 `NetworkLogInterceptor`,支持注入日志回调并保留请求耗时。
2. 在 Envelope 解包前暂存原始 bytes;日志拦截器追加到拦截链末尾后读取暂存值。
3. 在 `ApiClient` 注册调试拦截器,`kDebugMode` 与常量开关同时满足时才输出。
4. 以 fake adapter 补单测,覆盖行格式、脱敏、UTF-8、截断、非 JSON 与二进制。
5. 运行 `dart analyze lib test` 和 `flutter test`,更新 PROGRESS 留痕。
## 风险与约束
- `ResponseType.bytes` 不能直接 `toString`,响应体统一 `utf8.decode`。
- Dio 响应拦截器按 FIFO 执行;链尾日志通过 Envelope 暂存的原始 bytes 读取,
避免拿到已解包后的 `data`。
- release/profile 通过 `kDebugMode` 短路,不输出任何 HTTP 日志。
- 不新增依赖,不提交现有 `pubspec.lock` 改动,不启停服务、不操作真机。
@@ -0,0 +1,20 @@
# 收藏卡片简介
## 目标
- 收藏卡片在标题/标签下方显示后端 `synopsis`,最多 2 行并省略溢出。
- 空白简介不创建文本节点、不占高度,不显示 `null/undefined`。
- 保持封面 80x106 和现有卡片高度,不引入布局溢出。
## 实现
1. `_FavoriteCard` 对 synopsis 做 trim 后条件渲染。
2. 简介使用 `Flexible` 占据标题、标签与底部统计之间的剩余空间,
`maxLines: 2`、`TextOverflow.ellipsis`、次要文案色和字号。
3. 无简介时继续使用原 Spacer,保持原布局。
4. widget 测试覆盖有简介、空简介和卡片高度稳定性。
## 约束
- 不改接口、后端、收藏分页。
- 不新增依赖,不提交现有 `pubspec.lock`,不启停服务、不操作真机。
@@ -0,0 +1,31 @@
# 屏幕常亮与前台自动续播
## 目标
- Android 与 iOS 前台保持屏幕常亮,不引入插件或依赖。
- 用户主动暂停后切后台再回前台,在播放器与票据状态安全时自动续播。
- 锁定、blocked、票据失败、错误态及浮层遮挡场景不得强制起播。
## 实施步骤
1. Android `MainActivity.onCreate` 添加 `FLAG_KEEP_SCREEN_ON`,保留现有渠道逻辑。
2. iOS `AppDelegate` 设置 `UIApplication.shared.isIdleTimerDisabled = true`。
3. 为 `PlaybackSession` 增加无浮层、路线可见且 active 播放器可用的恢复判断。
4. 调整 `PlayerLogic.onLifecycle`,在现有 PiP 复位和过期票据刷新流程后安全设置播放意图。
5. 补用户暂停恢复、锁定/票据失败不恢复、后台保存并暂停测试。
6. 执行 `dart analyze lib test` 与 `flutter test --no-pub`,回填 PROGRESS 后提交合并。
## 风险 / 待确认
- 真机常亮和生命周期行为由 PM 验收,本次不启停服务、不操作设备。
- PiP 继续沿用现有可见性策略,不额外修改后台播放行为。
## 执行结果
- Android 主 Activity 已添加 `FLAG_KEEP_SCREEN_ON`,未改动 Flutter 渠道与 PiP 逻辑。
- iOS AppDelegate 已设置 `UIApplication.shared.isIdleTimerDisabled = true`。
- 用户主动暂停后回前台可安全恢复;过期票据会等待重取完成后再恢复。
- locked、blocked、票据错误、会话错误、解锁/浮层遮挡和 PiP 场景均不会强制续播。
- 后台仍先保存进度并暂停。
- `dart analyze lib test` 零告警,`flutter test --no-pub` 580 项全过。
- 未启停服务,未操作真机。
@@ -0,0 +1,31 @@
# 收藏与观看历史分页 UI
## 目标
- `/app/favorites` 与 `/app/watch/recent` 按 `PageResult{total,list}` 分页接入。
- 收藏页、观看历史页支持首屏、触底加载、无更多、刷新、失败重试和总数展示。
- 观看记录直接使用后端 `dramaTitle/dramaCover/episodeNumber`,缺失时安全兜底。
## 实现步骤
1. 新增通用 `PageResult<T>`,更新 LibraryApi、WatchApi、Repository 与内存替身。
2. FavoriteController、WatchRecordsController 增加分页状态、请求代次、dramaId 去重。
3. 页面通过滚动通知触发 `loadMore`,复用下拉刷新并增加通用分页 footer。
4. 观看卡片优先渲染接口字段,标题为空时用本地化“第 N 集”,封面为空显示占位。
5. 补 API、Controller 和 widget 测试,运行 analyze 与全量 flutter test。
## 风险与约束
- 刷新期间保持旧列表,成功后原子替换并回到第 1 页;失败保留旧数据。
- loadMore 与 refresh 通过请求代次隔离,旧响应不得覆盖新列表。
- 空页立即停止加载;`loaded >= total` 后不再请求。
- 不新增依赖、不提交现有 `pubspec.lock`,不启停服务、不操作真机。
## 2026-10-22 范围修订
- 分页协议和 Controller 分页状态保持不改。
- 观看历史优先使用后端 `dramaTitle/dramaCover/episodeNumber`,只有标题为空时才预取详情。
- 标题为空且详情也未返回标题时,用本地化“第 N 集”兜底。
- 有标题的条目点击播放时再按需加载详情,不在列表加载阶段产生 N+1。
- 使用当前真实后端校验第 1/2 页请求参数、total 与 list;若运行实例仍忽略分页,
只在 PROGRESS 记录证据,不做前端全量兜底。
@@ -0,0 +1,28 @@
# 登录异常居中弹窗
## 目标
- 邮箱登录、邮箱注册及手机号 OTP 登录的提交异常改为屏幕居中弹窗。
- 本地表单校验继续使用现有内联字段错误,不触发弹窗。
## 实施步骤
1. 新增 `lib/core/widgets/centered_error_dialog.dart`,统一弹窗样式、遮罩关闭和确认按钮。
2. 补充登录失败、注册失败、确认按钮的中英文印尼语文案并重新生成 l10n。
3. 调整邮箱认证页:接口异常展示居中弹窗,提交 busy 在展示弹窗前复位。
4. 调整手机号弹窗:OTP 发送和验证码登录/注册异常展示居中弹窗,成功 SnackBar 保持不变。
5. 补接口异常、注册标题、本地校验不弹窗和手机号两条异常路径测试。
6. 执行 `dart analyze lib test` 与 `flutter test --no-pub`,回填 PROGRESS 后提交合并。
## 风险 / 待确认
- 同级 FruitServer 的 `INBOX.md` 当前顶部为已处理 K100,未找到本需求条目;以用户消息中的完整需求为准。
- 现有主工作区处于 `feature/payment-iap` 且有未提交 `pubspec.lock`,本次使用独立 worktree,不触碰该文件。
## 执行结果
- 已新增居中错误弹窗组件及三语言文案,邮箱登录/注册和手机号 OTP/验证码提交异常均改为弹窗。
- 本地字段校验仍走原内联错误;异常展示前提交 busy 已复位。
- 接口异常、注册标题、未知异常网络文案、遮罩关闭、确认关闭、本地校验不弹窗均有测试覆盖。
- `dart analyze lib test` 零告警,`flutter test --no-pub` 570 项全过。
- 未启停服务,未操作真机。
@@ -0,0 +1,29 @@
# 登录异常居中吐司
## 目标
- 将上一版认证异常 AlertDialog 替换为无交互的屏幕居中吐司。
- 吐司淡入展示约 2 秒后自动淡出,不遮罩、不拦截手势。
- 同一时刻只存在一个吐司,新消息替换旧消息。
## 实施步骤
1. 删除 `centered_error_dialog.dart`,新增 `centered_toast.dart`。
2. 认证页和手机号弹窗异常路径改用 `showCenteredToast`,保留 busy 复位和成功 SnackBar。
3. 清理不再使用的认证错误标题与确认按钮 l10n 键,重新生成 AppLocalizations。
4. 更新认证测试为吐司断言,并补单例替换、自动消失测试。
5. 执行 `dart analyze lib test` 与 `flutter test --no-pub`,回填 PROGRESS 后提交合并。
## 风险 / 待确认
- 吐司使用 root Overlay,认证路由退出时仍会按自身计时器自动清理。
- 测试必须 pump 超过展示和淡出时长,确保不遗留 Timer 或 OverlayEntry。
## 执行结果
- 已删除 AlertDialog 组件,新增 OverlayEntry 居中吐司。
- 邮箱和手机号异常改用吐司;busy 复位、成功 SnackBar 和本地内联校验保持不变。
- 已清理三个不再使用的 l10n 键并重新生成 AppLocalizations。
- 覆盖单例替换、点击穿透、自动消失、接口异常和本地校验不弹吐司。
- `dart analyze lib test` 零告警,`flutter test --no-pub` 573 项全过。
- 未启停服务,未操作真机。
@@ -0,0 +1,21 @@
# 播放入口无缓冲提示
## 目标
- 未初始化进度条显示静态 inactive 轨道,不出现流动加载高光。
- 仅真实 buffering 持续超过 350ms 时显示白色高光。
- 起播定位阶段不叠加 session 居中 loading;只有 ticket loading 居中显示。
- 同集返回复用 controller;重建时 initialize→seek→play。
## 实施步骤
1. 调整 SeekProgressBar 与 _FeedProgressBar 的初始化/缓冲状态判断。
2. 删除 PlayerView 的 session loading 居中圈,仅保留 ticket loading。
3. preparePlayer 统一通过 VideoPlayerPort.seekTo 定位缓存位置。
4. 更新进度条、全屏入口、详情返回和重建顺序测试。
5. 执行 `dart analyze lib test` 与 `flutter test --no-pub`,回填 PROGRESS 后提交合并。
## 风险 / 待确认
- 真实持续网络卡顿仍保留缓冲高光。
- 锁定集继续沿用静态空轨道和禁用 seek 行为。
@@ -0,0 +1,32 @@
# 播放位置连续性
## 目标
- 首页进入详情、详情返回首页、详情再进播放器时保持同一集同一秒。
- 新增进程内最近播放位置缓存,优先于服务端旧记录。
- 跨剧、跨集不串位;锁定/票据失败不因缓存强行起播;进程重启仍回服务端。
## 实施步骤
1. 新增 `PlaybackPositionCache`,按 episodeId 保存 dramaId/position/duration/updatedAt。
2. App Provider 注入单一缓存实例,PlayerPage 与详情页共享。
3. PlayerLogic 进度监听、saveCurrentProgress 写入缓存;loadResumePoint 优先命中缓存。
4. preparePlayer 对每个待准备集按 dramaId 校验缓存,并继续使用现有位置钳位。
5. 详情页续看状态优先使用该剧最新缓存,未命中再请求服务端。
6. 补缓存、缓存命中不请求服务端、跨剧隔离、服务端回落和详情返回位置测试。
7. 执行 `dart analyze lib test` 与 `flutter test --no-pub`,回填 PROGRESS 后提交合并。
## 风险 / 待确认
- 缓存仅保存在进程内,App 重启后自动回落到服务端观看记录。
- 片尾附近继续复用现有 `clampResumePosition` 完成保护。
## 执行结果
- 新增进程内按集缓存并通过 Provider 全局共享,详情页与播放器共用。
- 进度监听、`saveCurrentProgress` 和进入详情前会写入缓存。
- `loadResumePoint` 优先缓存,未命中或跨剧时才请求服务端。
- 详情页继续观看与 CTA 优先进缓存集;返回首页保持原播放器与位置。
- 已覆盖缓存读写/过期、跨剧隔离、缓存命中去网络、服务端回落和详情往返。
- `dart analyze lib test` 零告警,`flutter test --no-pub` 589 项全过。
- 未启停服务,未操作真机。
@@ -0,0 +1,21 @@
# Seek 后播放位置保持
## 目标
- 拖动进度条后,缓存与观看记录立即使用 seek 完成后的真实位置。
- 拖动后立即进详情/离开也不会回退到旧进度。
- 越界位置 clamp,锁定/票据失败不写无效缓存。
## 实施步骤
1. 增加播放位置 clamp 纯函数,缓存写入统一限制在 `0..duration`。
2. VideoPlayerPort 暴露 seek 方法,PlayerLogic 等待 seek 完成后再读取 controller。
3. PlayerLogic 统一当前播放快照,监听、缓存和上报共用同一份 position/duration。
4. 增加 pending seek 屏障,saveCurrentProgress 与离开路径等待 seek 和上报完成。
5. 补 seek 缓存、越界 clamp、seek 后进详情并返回测试。
6. 执行 `dart analyze lib test` 与 `flutter test --no-pub`,回填 PROGRESS 后提交合并。
## 风险 / 待确认
- 片尾与完成逻辑继续沿用现有 controller/播放器行为。
- 缓存仅在进程内,不改变服务端观看记录契约。
@@ -0,0 +1,30 @@
# 播放器 loading 与锁定进度条调整
## 目标
- 播放器票据与 session loading 圈统一为 24×24、线宽 2。
- 缓冲/加载进度高光改为白色。
- 未解锁剧集显示静态 inactive 轨道,不进入 loading,不允许拖动 seek。
## 实施步骤
1. 更新 `LoadingProgressTrack` 默认高光颜色,并补颜色断言。
2. 为 `SeekProgressBar`、`PlayerOverlayData`、`HomeFeedOverlay` 和 `_FeedProgressBar` 增加 locked 状态。
3. `PlayerPage` 使用 `logic.session.isLocked || episode.locked` 传入两个浮层。
4. 缩小 `player-ticket-loading` 与 `player-session-loading` 的尺寸和线宽。
5. 补首页/全屏锁定态、缓冲高光和两个 loading 指示器尺寸测试。
6. 执行 `dart analyze lib test` 与 `flutter test --no-pub`,回填 PROGRESS 后提交合并。
## 风险 / 待确认
- 锁定态只影响进度条 UI 与手势,不改播放、解锁或接口逻辑。
- 首页浮层锁定线沿用当前 `Colors.white24` inactive 色,避免改变非锁定轨道样式。
## 执行结果
- 两个播放器 loading 圈均为 24×24、线宽 2、主色不变。
- loading 高光默认改为白色,普通轨道与首页 white24 轨道颜色未变。
- 首页与全屏播放器均按剧集锁定态渲染静态空轨道并禁用 seek。
- 已覆盖锁定轨道高度、无 loading、无拖动 seek、loading 指示器尺寸和高光颜色。
- `dart analyze lib test` 零告警,`flutter test --no-pub` 576 项全过。
- 未启停服务,未操作真机。
@@ -0,0 +1,62 @@
# 播放器进度拖动画面预览
## 目标
首页 feed 与全屏播放器的进度条在拖动时显示当前时间点的视频画面缩略图、
当前时间与总时长;松手立即隐藏并提交 seek。缩略图未返回或失败时显示短剧
封面,不出现空白闪烁或错误文案。
## 性能红线
1. 拖动期间主线程无卡顿;图片解码和原生取帧不得阻塞 UI 线程。
2. 原生取帧并发数不超过 1。
3. 预览图最大宽度 240px。
4. Dart 内存 LRU 缓存不超过 24 帧。
5. 不新增 pub 依赖。
6. 不改后端契约。
## 实现
### 缩略图服务
- 新增 `PlaybackThumbnailService`,公开入口按 `episodeId + position` 取
`ByteData`,底层通过
`MethodChannel('com.stormg.lumadrama/thumbnail')` 调用原生。
- 时间按 1 秒分桶;同一 bucket 的在途请求合流。
- 最多 24 帧 LRU;成功和失败结果都做过期控制,避免旧拖动位置覆盖当前预览。
- 全局单飞;拖动期间只保留最新 bucket,尚未开始的旧请求直接丢弃。
- 拖动停顿 120ms 后最多预取相邻一个 bucket,松手取消尚未开始的请求。
- 原生异常、缺失实现和超过 1.5 秒无结果统一返回 `null`。
### 原生取帧
- Android 使用 `MediaMetadataRetriever`,优先本地下载路径,其次签名 URL;
`OPTION_CLOSEST_SYNC` 取帧,最大宽 240px,JPEG 质量 70,单线程 Executor,
完成后立即 release。
- iOS 使用 `AVAssetImageGenerator`,应用画面方向,最大尺寸约 240x426,
合理时间容差,后台串行队列生成 JPEG。
- 两端均保证只回调一次;失败或 1.5 秒超时返回 `null`,不向 UI 抛异常。
### UI
- 新增 `ScrubPreviewCard`:宽 120dp、圆角 8、白色 1px 描边,显示
`当前时间 / 总时长`;沿拖动点水平跟随并做安全边距 clamp。
- 首页 `_FeedProgressBar` 与全屏 `SeekProgressBar` 共用该卡片。
- 封面始终铺在卡片底层;`Image.memory` 仅在帧到达后覆盖,并限制
`cacheWidth: 240`。
- 开始拖动请求当前 bucket,拖动更新只保留最新位置;松手立即隐藏卡片并取消
未开始请求。
- `PlayerPage` 持有页面级服务,dispose 时清空缓存,避免跨页面串集。
## 测试
- 单测:bucket、LRU 淘汰、同 bucket 合流、过期请求丢弃、松手取消、
相邻 bucket 防抖、异常/超时回落。
- Widget:拖动显示预览卡片和时间、松手消失、无缩略图显示封面。
- 门禁:`dart analyze lib test` 与 `flutter test --no-pub`。
## 非目标
- 不改服务端接口或持久化策略。
- 不新增第三方依赖。
- 不启停服务、不操作真机;原生取帧实际效果由 PM 真机验收。
@@ -0,0 +1,46 @@
# 雪碧图 VTT 拖动预览
## 目标
将进度条拖动预览主链路改为服务端雪碧图 + WebVTT 局部裁剪,原生取帧只作为降级:
1. `Episode` 与 `PlayTicket` 解析 `thumbnailsUrl`。
2. VTT 只拉取/解析一次,内存缓存 10 分钟。
3. cue 使用起止时间 + 相对 sprite 文件名 + `xywh`,按 position 二分选择。
4. sprite 图最多缓存 2 张 `ui.Image`,拖动帧只绘制 sourceRect。
5. 无 VTT、空轴或图片失败时回退现有 `PlaybackThumbnailService`,再回退封面。
## 性能红线
- 拖动帧禁止网络请求与图片解码,只允许 cue 二分查找和 `RawImage.sourceRect` 绘制。
- VTT 每个 URL 只解析一次;sprite 解码不在 build/build paint 中执行。
- sprite ui.Image LRU 上限 2 张,页面 dispose 清空。
- 解码 sprite 最长边超过 2048px 时按比例缩小后常驻。
- 签名 `thumbnailsUrl` 不写入任何持久化缓存。
## 实施
- 新增 `SpriteThumbnailAxisService`:
- Dio 拉取 VTT,403 清缓存后重试一次;
- 解析和选 cue 为纯函数,解析失败返回空轴;
- 播放器就绪时预取当前 cue 及相邻 sprite;
- LRU2 sprite 缓存和 10 分钟 VTT 缓存。
- `PlayerPage` 持有页面级服务,按当前选中集预取,dispose 时清空。
- `ScrubPreviewCard` 优先读取已缓存 sprite frame,显示 `RawImage` 裁剪;
VTT 未就绪时暂显封面,轴为空/不可用后启动本地取帧,全部失败保留封面。
- 首页 feed 和全屏播放器都把 `thumbnailsUrl` 传到预览卡片。
- 不新增 pub 依赖、不改后端契约。
## 测试
- VTT 解析:时间、xywh、多 sprite 文件。
- cue 选择:命中、越界最近邻。
- sprite LRU 最多 2 张。
- 回退状态判定。
- Widget:sprite 裁剪且不触发本地取帧;无 URL 回退本地;全部失败显示封面。
- 门禁:`dart analyze lib test`、`flutter test --no-pub`。
## 风险
- 真实网络 VTT/sprite 可访问性和签名过期由 PM 真机验收。
- 本次不启动服务、不操作真机、不修改 Web/FruitServer。
File diff suppressed because it is too large. Load diff
+74
View File
@@ -0,0 +1,74 @@
// Author: fruit.com.cn
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/app.dart';
import 'package:fruitshorts/core/config/app_config.dart';
import 'package:fruitshorts/core/data/remote/api_client.dart';
import 'package:fruitshorts/core/data/remote/catalog_api.dart';
import 'package:integration_test/integration_test.dart';
/// 设备侧集成测试:**在手机进程里**真实请求开发机上的 app-api。
///
/// 跑法(红线第 10 条要求的"设备侧取证"):
/// ```
/// IP=$(ipconfig getifaddr en0)
/// flutter test integration_test -d <device-id> --dart-define=API_BASE_URL=http://$IP:8081
/// ```
/// 断言失败时会把实际使用的地址一起打出来,便于区分"地址不对"还是"设备连不上"。
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
final baseUrl = AppConfig.apiBaseUrl;
testWidgets('should_fetch_home_feed_and_drama_detail_when_device_reaches_backend', (
tester,
) async {
debugPrint('[device] API_BASE_URL=$baseUrl');
final client = ApiClient(baseUrl: baseUrl);
addTearDown(client.close);
final api = CatalogApi(client);
final feed = await api.homeFeed();
expect(
feed.dramas,
isNotEmpty,
reason: '设备侧拿不到首页列表(API_BASE_URL=$baseUrl)',
);
debugPrint('[device] 首页分区=${feed.sections.length} 剧目=${feed.dramas.length}');
final card = feed.dramas.first;
expect(card.id, isNotEmpty, reason: '设备侧首页卡片 id 为空');
final detail = await api.dramaDetail(card.id);
expect(detail.id, card.id, reason: '详情返回的 id 与卡片不一致');
expect(
detail.episodes,
isNotEmpty,
reason: '设备侧拿到的详情没有剧集(dramaId=${card.id})',
);
debugPrint('[device] 详情 id=${detail.id} 剧集=${detail.episodes.length}');
});
testWidgets('should_render_home_drama_list_when_app_boots_on_device', (
tester,
) async {
// 用真 App(真网络、真目录控制器):首页底部标签栏出现即代表"剧目列表加载成功",
// 也就是用户在真机上遇到的那个「剧目列表加载失败」已经消失。
await tester.pumpWidget(const LumaDramaApp());
var loaded = false;
for (var attempt = 0; attempt < 60 && !loaded; attempt++) {
await tester.pump(const Duration(milliseconds: 500));
loaded = find.byKey(const Key('home-tabs-bar')).evaluate().isNotEmpty;
}
expect(
loaded,
isTrue,
reason: '首页 30 秒内没渲染出剧目列表(API_BASE_URL=$baseUrl)',
);
debugPrint('[device] 首页剧目列表已渲染:API_BASE_URL=$baseUrl');
});
}
-1
View File
@@ -1,2 +1 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
-1
View File
@@ -1,2 +1 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
-43
View File
@@ -1,43 +0,0 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end
+7 -3
View File
@@ -11,6 +11,7 @@
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
8C4E1A022F9A4B1000C636F3 /* WirelessDataPrompt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4E1A012F9A4B1000C636F3 /* WirelessDataPrompt.swift */; };
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; };
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
@@ -49,6 +50,7 @@
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
8C4E1A012F9A4B1000C636F3 /* WirelessDataPrompt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WirelessDataPrompt.swift; sourceTree = "<group>"; };
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
@@ -122,6 +124,7 @@
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
8C4E1A012F9A4B1000C636F3 /* WirelessDataPrompt.swift */,
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
@@ -282,6 +285,7 @@
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
8C4E1A022F9A4B1000C636F3 /* WirelessDataPrompt.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */,
);
@@ -360,7 +364,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -487,7 +491,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -538,7 +542,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -6,11 +6,27 @@
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Bootstrap Flutter SPM Package"
scriptText = "#!/bin/sh&#10;set -e&#10;FLUTTER=&quot;&quot;&#10;if [ -n &quot;${FLUTTER_ROOT:-}&quot; ] &amp;&amp; [ -x &quot;$FLUTTER_ROOT/bin/flutter&quot; ]; then&#10; FLUTTER=&quot;$FLUTTER_ROOT/bin/flutter&quot;&#10;elif FLUTTER=&quot;$(command -v flutter)&quot;; then&#10; :&#10;elif [ -x &quot;$HOME/flutter/bin/flutter&quot; ]; then&#10; FLUTTER=&quot;$HOME/flutter/bin/flutter&quot;&#10;elif [ -x &quot;/opt/homebrew/bin/flutter&quot; ]; then&#10; FLUTTER=&quot;/opt/homebrew/bin/flutter&quot;&#10;fi&#10;if [ -z &quot;$FLUTTER&quot; ]; then&#10; echo &quot;error: Flutter executable not found. Set FLUTTER_ROOT or add Flutter to PATH.&quot; &gt;&amp;2&#10; exit 1&#10;fi&#10;if [ -z &quot;${SRCROOT:-}&quot; ]; then&#10; echo &quot;error: SRCROOT is not set; cannot locate the Flutter project.&quot; &gt;&amp;2&#10; exit 1&#10;fi&#10;cd &quot;$SRCROOT/..&quot;&#10;&quot;$FLUTTER&quot; pub get&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Prepare Flutter Framework Script"
scriptText = "/bin/sh &quot;$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh&quot; prepare&#10;">
scriptText = "#!/bin/sh&#10;set -e&#10;FLUTTER=&quot;&quot;&#10;if [ -n &quot;${FLUTTER_ROOT:-}&quot; ] &amp;&amp; [ -x &quot;$FLUTTER_ROOT/bin/flutter&quot; ]; then&#10; FLUTTER=&quot;$FLUTTER_ROOT/bin/flutter&quot;&#10;elif FLUTTER=&quot;$(command -v flutter)&quot;; then&#10; :&#10;elif [ -x &quot;$HOME/flutter/bin/flutter&quot; ]; then&#10; FLUTTER=&quot;$HOME/flutter/bin/flutter&quot;&#10;elif [ -x &quot;/opt/homebrew/bin/flutter&quot; ]; then&#10; FLUTTER=&quot;/opt/homebrew/bin/flutter&quot;&#10;fi&#10;if [ -z &quot;$FLUTTER&quot; ]; then&#10; echo &quot;error: Flutter executable not found. Set FLUTTER_ROOT or add Flutter to PATH.&quot; &gt;&amp;2&#10; exit 1&#10;fi&#10;FLUTTER_ROOT=&quot;$(cd &quot;$(dirname &quot;$FLUTTER&quot;)/..&quot; &amp;&amp; pwd -P)&quot;&#10;if [ -z &quot;${SRCROOT:-}&quot; ]; then&#10; echo &quot;error: SRCROOT is not set; cannot locate the Flutter project.&quot; &gt;&amp;2&#10; exit 1&#10;fi&#10;cd &quot;$SRCROOT&quot;&#10;&quot;$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh&quot; prepare&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
+588 -6
View File
@@ -1,5 +1,27 @@
import Flutter
import UIKit
import AVFoundation
import AVKit
private let thumbnailHTTPHeaderFieldsKey = "AVURLAssetHTTPHeaderFieldsKey"
private final class ThumbnailResponseGate {
private let lock = NSLock()
private var completed = false
func send(_ result: @escaping FlutterResult, data: Data?) {
lock.lock()
if completed {
lock.unlock()
return
}
completed = true
lock.unlock()
DispatchQueue.main.async {
result(data)
}
}
}
/// Keeps the system LaunchScreen as the only splash. Flutter would otherwise
/// instantiate that storyboard again and the icon appears a second time.
@@ -14,17 +36,348 @@ final class RunnerFlutterViewController: FlutterViewController {
}
}
/// iOS 影子播放器:独立 AVPlayer + AVPlayerLayer 专供画中画窗口。
/// Flutter 的 video_player 走纹理渲染,没有 AVPlayerLayer,无法直接创建
/// AVPictureInPictureController;小窗的画面与声音由影子接管,退出时把
/// 影子进度回传给 Dart,由 Flutter 播放器 seek 对齐后继续。
final class PipShadowCoordinator: NSObject, AVPictureInPictureControllerDelegate {
static let shared = PipShadowCoordinator()
private var player: AVPlayer?
private var playerLayer: AVPlayerLayer?
private var controller: AVPictureInPictureController?
private var endObserver: NSObjectProtocol?
private var possibleObservation: NSKeyValueObservation?
/// 画中画还原过渡层:原生窗口级红晕渐变,在系统还原动画开始时立即盖上,
/// 避免回前台瞬间 Flutter 表面未恢复显示黑帧;视频出帧后由 Dart 通知淡出。
private var restoreOverlay: UIView?
/// 系统要求 playerLayer 位于真实的 view 层级中才会判定画中画可用,
/// 因此用 1x1 透明宿主 view 承载,而不是直接挂在 window.layer 上。
private var hostView: UIView?
/// 影子图层就绪稍晚于创建,置位后由 KVO 自动拉起画中画。
private var pendingStart = false
private var startRetryWorkItems: [DispatchWorkItem] = []
weak var channel: FlutterMethodChannel?
/// active 变化回调;停止时把影子最终进度(毫秒)一并提供。
var onPipChanged: ((Bool, Int64) -> Void)?
/// 影子在画中画里播完当前集。
var onPlaybackEnded: (() -> Void)?
static var isSupported: Bool {
AVPictureInPictureController.isPictureInPictureSupported()
}
private var keyWindow: UIWindow? {
UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first { $0.isKeyWindow }
}
/// 首次创建影子:1x1 图层挂在 keyWindow 的宿主 view 里(肉眼不可见),静音待命。
/// [autoEnter] 为 true 时退后台由系统自动拉起画中画(需影子在播,见 armAutoEnter)。
func prepare(urlString: String, positionMs: Int64, autoEnter: Bool) {
teardown()
log("prepare begin supported=\(PipShadowCoordinator.isSupported) autoEnter=\(autoEnter)")
guard Self.isSupported else { return }
guard let url = URL(string: urlString) else {
log("prepare failed bad url \(urlString)")
return
}
guard let window = keyWindow else {
log("prepare failed no key window")
return
}
let item = AVPlayerItem(url: url)
let p = AVPlayer(playerItem: item)
p.isMuted = true
p.actionAtItemEnd = .pause
let layer = AVPlayerLayer()
layer.player = p
let host = UIView(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
host.isUserInteractionEnabled = false
host.backgroundColor = .clear
window.addSubview(host)
layer.frame = host.bounds
host.layer.addSublayer(layer)
hostView = host
guard let c = AVPictureInPictureController(playerLayer: layer) else {
p.pause()
log("prepare failed no pip controller")
return
}
c.canStartPictureInPictureAutomaticallyFromInline = autoEnter
c.delegate = self
player = p
playerLayer = layer
controller = c
// 静音起播让 item 完成首帧解码,isPictureInPicturePossible 才会变 true。
p.play()
possibleObservation = c.observe(
\.isPictureInPicturePossible,
options: [.new, .initial]
) { [weak self] controller, change in
let value = change.newValue ?? controller.isPictureInPicturePossible
self?.log("possible changed to \(value)")
guard let self, self.pendingStart, controller.isPictureInPicturePossible else { return }
self.pendingStart = false
// 刚翻 true 的同一 runloop 里调用会被系统静默拒绝,推到下一拍再进。
DispatchQueue.main.async {
guard self.controller === controller, controller.isPictureInPicturePossible else {
self.log("start via kvo aborted (shadow replaced)")
return
}
self.log("start via kvo")
controller.startPictureInPicture()
}
}
log("shadow ready, seeking to \(positionMs)ms")
if positionMs > 0 {
p.seek(
to: CMTime(value: positionMs, timescale: 1000),
toleranceBefore: .zero,
toleranceAfter: .zero
)
}
registerEndObserver(for: item)
activateAudioSession()
}
/// 退出 App 前(inactive)把影子对齐到当前进度并静音起播:
/// auto PiP 只在「player 正在播放 + 图层离屏」时由系统触发。
func armAutoEnter(positionMs: Int64) {
guard let player, let controller else {
log("arm failed, shadow missing")
return
}
guard controller.canStartPictureInPictureAutomaticallyFromInline else {
log("arm failed, autoEnter disabled on shadow")
return
}
log("arm auto enter at \(positionMs)ms")
if positionMs > 0 {
player.seek(
to: CMTime(value: positionMs, timescale: 1000),
toleranceBefore: CMTime(value: 800, timescale: 1000),
toleranceAfter: CMTime(value: 800, timescale: 1000)
)
}
player.play()
}
/// 回前台后取消待命:影子暂停回静音,等待下一次退后台。
func disarmAutoEnter() {
guard let player else { return }
log("disarm auto enter")
player.pause()
player.isMuted = true
}
/// 进入画中画:图层就绪则立即进入,否则 KVO/延时重试在就绪后自动进入。
/// 仅当影子从未创建成功(prepare 失败)时返回 false。
func start() -> Bool {
guard let controller else {
log("start failed, shadow not created")
return false
}
log("start requested possible=\(controller.isPictureInPicturePossible)")
pendingStart = true
if controller.isPictureInPicturePossible {
pendingStart = false
controller.startPictureInPicture()
}
scheduleStartRetries()
return true
}
/// KVO 偶发不触发或流加载较慢时兜底:就绪后再试两次。
private func scheduleStartRetries() {
startRetryWorkItems.forEach { $0.cancel() }
startRetryWorkItems.removeAll()
for delay in [1.0, 2.5] {
let item = DispatchWorkItem { [weak self] in
guard
let self,
self.pendingStart,
let controller = self.controller,
controller.isPictureInPicturePossible
else { return }
self.log("start via retry after \(delay)s")
self.pendingStart = false
controller.startPictureInPicture()
}
startRetryWorkItems.append(item)
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: item)
}
}
/// 画中画内换集:只换 AVPlayerItem,不动 player/layer,窗口不中断。
func replaceItem(urlString: String, positionMs: Int64) {
guard let player, let url = URL(string: urlString) else { return }
let item = AVPlayerItem(url: url)
player.replaceCurrentItem(with: item)
registerEndObserver(for: item)
if positionMs > 0 {
player.seek(
to: CMTime(value: positionMs, timescale: 1000),
toleranceBefore: .zero,
toleranceAfter: .zero
)
}
}
/// 进入画中画后解除静音并起播(此时 Flutter 播放器已被暂停)。
func unmuteAndPlay() {
player?.isMuted = false
player?.play()
}
func positionMs() -> Int64 {
Int64(max(0, player?.currentTime().seconds ?? 0) * 1000)
}
func stopPip() {
controller?.stopPictureInPicture()
}
func teardown() {
startRetryWorkItems.forEach { $0.cancel() }
startRetryWorkItems.removeAll()
possibleObservation?.invalidate()
possibleObservation = nil
pendingStart = false
if let endObserver {
NotificationCenter.default.removeObserver(endObserver)
}
endObserver = nil
player?.pause()
player?.replaceCurrentItem(with: nil)
playerLayer?.removeFromSuperlayer()
playerLayer = nil
hostView?.removeFromSuperview()
hostView = nil
controller?.delegate = nil
controller = nil
player = nil
}
/// 诊断日志:NSLog 之外同步转发到 Dart,便于在 flutter run 控制台观察。
private func log(_ message: String) {
NSLog("pip: %@", message)
channel?.invokeMethod("pipDebug", arguments: message)
}
private func registerEndObserver(for item: AVPlayerItem) {
if let endObserver {
NotificationCenter.default.removeObserver(endObserver)
}
endObserver = NotificationCenter.default.addObserver(
forName: .AVPlayerItemDidPlayToEndTime,
object: item,
queue: .main
) { [weak self] _ in
self?.log("item did play to end")
self?.onPlaybackEnded?()
}
}
/// 画中画的声音走影子播放器,需要 playback 类别的音频会话保活。
private func activateAudioSession() {
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback)
try AVAudioSession.sharedInstance().setActive(true)
} catch {
// 会话激活失败只影响后台出声,不阻断画中画。
}
}
func pictureInPictureControllerWillStartPictureInPicture(
_ pictureInPictureController: AVPictureInPictureController
) {
log("will start")
pendingStart = false
onPipChanged?(true, 0)
}
func pictureInPictureControllerWillStopPictureInPicture(
_ pictureInPictureController: AVPictureInPictureController
) {
log("will stop, showing restore overlay")
showRestoreOverlay()
}
func pictureInPictureControllerDidStopPictureInPicture(
_ pictureInPictureController: AVPictureInPictureController
) {
log("did stop")
showRestoreOverlay()
onPipChanged?(false, positionMs())
}
/// 盖原生过渡层(幂等):主题红晕渐变,不依赖 Flutter 渲染。
func showRestoreOverlay() {
guard let window = keyWindow else { return }
restoreOverlay?.removeFromSuperview()
let overlay = UIView(frame: window.bounds)
overlay.autoresizingMask = [.flexibleWidth, .flexibleHeight]
let gradient = CAGradientLayer()
gradient.frame = overlay.bounds
gradient.colors = [
UIColor(red: 0.298, green: 0.086, blue: 0.102, alpha: 1).cgColor,
UIColor(red: 0.118, green: 0.043, blue: 0.047, alpha: 1).cgColor,
UIColor(red: 0.063, green: 0.063, blue: 0.071, alpha: 1).cgColor,
]
gradient.locations = [0, 0.22, 0.62]
gradient.startPoint = CGPoint(x: 0.5, y: 0)
gradient.endPoint = CGPoint(x: 0.5, y: 1)
overlay.layer.addSublayer(gradient)
window.addSubview(overlay)
restoreOverlay = overlay
}
/// 淡出并移除过渡层(视频首帧已确认后由 Dart 调用)。
func hideRestoreOverlay() {
guard let overlay = restoreOverlay else { return }
restoreOverlay = nil
UIView.animate(
withDuration: 0.25,
animations: { overlay.alpha = 0 }
) { _ in overlay.removeFromSuperview() }
}
}
@main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
private let wirelessDataPrompt = WirelessDataPrompt()
private let thumbnailQueue = DispatchQueue(
label: "com.stormg.lumadrama.thumbnail",
qos: .userInitiated
)
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
UIApplication.shared.isIdleTimerDisabled = true
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
override func applicationDidBecomeActive(_ application: UIApplication) {
super.applicationDidBecomeActive(application)
requestWirelessDataPrompt()
}
func requestWirelessDataPrompt() {
wirelessDataPrompt.requestSystemPrompt()
}
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
registerWirelessDataChannel(engineBridge)
registerDeviceIdChannel(engineBridge)
registerPipChannel(engineBridge)
let channel = FlutterMethodChannel(
name: "com.stormg.lumadrama/share",
binaryMessenger: engineBridge.applicationRegistrar.messenger()
@@ -33,15 +386,244 @@ final class RunnerFlutterViewController: FlutterViewController {
guard call.method == "shareText",
let arguments = call.arguments as? [String: Any],
let text = arguments["text"] as? String,
!text.isEmpty,
let controller = self?.window?.rootViewController else {
!text.isEmpty else {
result(FlutterError(code: "share_unavailable", message: "Share sheet is unavailable", details: nil))
return
}
let activity = UIActivityViewController(activityItems: [text], applicationActivities: nil)
activity.popoverPresentationController?.sourceView = controller.view
activity.completionWithItemsHandler = { _, _, _, _ in result(nil) }
controller.present(activity, animated: true)
DispatchQueue.main.async {
guard let controller = self?.sharePresenter() else {
result(FlutterError(code: "share_unavailable", message: "Share sheet is unavailable", details: nil))
return
}
let activity = UIActivityViewController(activityItems: [text], applicationActivities: nil)
if let popover = activity.popoverPresentationController {
popover.sourceView = controller.view
popover.sourceRect = CGRect(
x: controller.view.bounds.midX,
y: controller.view.bounds.midY,
width: 0,
height: 0
)
popover.permittedArrowDirections = []
}
activity.completionWithItemsHandler = { _, _, _, _ in result(nil) }
controller.present(activity, animated: true)
}
}
let thumbnailChannel = FlutterMethodChannel(
name: "com.stormg.lumadrama/thumbnail",
binaryMessenger: engineBridge.applicationRegistrar.messenger()
)
thumbnailChannel.setMethodCallHandler { [weak self] call, result in
guard call.method == "frame" else {
result(FlutterMethodNotImplemented)
return
}
guard let self,
let arguments = call.arguments as? [String: Any],
let timeNumber = arguments["timeMs"] as? NSNumber else {
result(nil)
return
}
let localPath = arguments["localPath"] as? String ?? ""
let remoteUrl = arguments["url"] as? String ?? ""
let headers = arguments["headers"] as? [String: String] ?? [:]
let assetUrl: URL?
if !localPath.isEmpty {
assetUrl = localPath.hasPrefix("file:")
? URL(string: localPath)
: URL(fileURLWithPath: localPath)
} else if !remoteUrl.isEmpty {
assetUrl = URL(string: remoteUrl)
} else {
assetUrl = nil
}
guard let assetUrl else {
result(nil)
return
}
let options: [String: Any]? = headers.isEmpty
? nil
: [thumbnailHTTPHeaderFieldsKey: headers]
let asset = AVURLAsset(url: assetUrl, options: options)
let generator = AVAssetImageGenerator(asset: asset)
generator.appliesPreferredTrackTransform = true
generator.maximumSize = CGSize(width: 240, height: 426)
generator.requestedTimeToleranceBefore = CMTime(seconds: 0.5, preferredTimescale: 600)
generator.requestedTimeToleranceAfter = CMTime(seconds: 0.5, preferredTimescale: 600)
let time = CMTime(value: timeNumber.int64Value, timescale: 1000)
let gate = ThumbnailResponseGate()
self.thumbnailQueue.async {
do {
let image = try generator.copyCGImage(at: time, actualTime: nil)
let data = UIImage(cgImage: image).jpegData(compressionQuality: 0.7)
gate.send(result, data: data)
} catch {
gate.send(result, data: nil)
}
}
DispatchQueue.global(qos: .userInitiated).asyncAfter(
deadline: .now() + 1.5
) {
gate.send(result, data: nil)
}
}
}
/// 当前前台 Scene 的根控制器之上,最适合展示系统分享面板的控制器。
private func sharePresenter() -> UIViewController? {
let keyWindow = UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.first { $0.activationState == .foregroundActive }?
.windows.first { $0.isKeyWindow }
guard let rootController = keyWindow?.rootViewController else { return nil }
let presenter = topPresentedController(from: rootController)
guard presenter.viewIfLoaded?.window != nil else { return nil }
return presenter
}
/// 解析已展示页面、导航栈和标签页中的当前可见控制器。
private func topPresentedController(from controller: UIViewController) -> UIViewController {
if let presented = controller.presentedViewController {
return topPresentedController(from: presented)
}
if let navigation = controller as? UINavigationController,
let visible = navigation.visibleViewController {
return topPresentedController(from: visible)
}
if let tabs = controller as? UITabBarController,
let selected = tabs.selectedViewController {
return topPresentedController(from: selected)
}
return controller
}
/// iOS 画中画:影子播放器通道,与 Android 共用同一 channel 名。
private func registerPipChannel(_ engineBridge: FlutterImplicitEngineBridge) {
let channel = FlutterMethodChannel(
name: "com.stormg.lumadrama/pip",
binaryMessenger: engineBridge.applicationRegistrar.messenger()
)
let pip = PipShadowCoordinator.shared
pip.channel = channel
channel.setMethodCallHandler { call, result in
switch call.method {
case "isAvailable":
result(PipShadowCoordinator.isSupported)
case "enter":
guard let args = call.arguments as? [String: Any],
let url = args["url"] as? String else {
result(false)
return
}
let positionMs = (args["positionMs"] as? NSNumber)?.int64Value ?? 0
pip.prepare(urlString: url, positionMs: positionMs, autoEnter: false)
// 等一拍让 AVPlayerLayer 完成挂载再请求进入画中画。
DispatchQueue.main.async {
result(pip.start())
}
case "prepareShadow":
guard let args = call.arguments as? [String: Any],
let url = args["url"] as? String else {
result(nil)
return
}
pip.prepare(
urlString: url,
positionMs: (args["positionMs"] as? NSNumber)?.int64Value ?? 0,
autoEnter: (args["autoEnter"] as? NSNumber)?.boolValue ?? false
)
result(nil)
case "armAutoEnter":
pip.armAutoEnter(
positionMs: (call.arguments as? [String: Any])?["positionMs"] as? Int64 ?? 0
)
result(nil)
case "showRestoreOverlay":
pip.showRestoreOverlay()
result(nil)
case "hideRestoreOverlay":
pip.hideRestoreOverlay()
result(nil)
case "disarmAutoEnter":
pip.disarmAutoEnter()
result(nil)
case "unmute":
pip.unmuteAndPlay()
result(nil)
case "replaceItem":
guard let args = call.arguments as? [String: Any],
let url = args["url"] as? String else {
result(nil)
return
}
pip.replaceItem(
urlString: url,
positionMs: (args["positionMs"] as? NSNumber)?.int64Value ?? 0
)
result(nil)
case "stop":
pip.stopPip()
result(nil)
case "teardown":
pip.teardown()
result(nil)
default:
result(FlutterMethodNotImplemented)
}
}
pip.onPipChanged = { active, positionMs in
channel.invokeMethod(
"onPipChanged",
arguments: ["active": active, "positionMs": positionMs]
)
}
pip.onPlaybackEnded = {
channel.invokeMethod("onPipPlaybackEnded", arguments: nil)
}
}
/// 设备标识:IDFV 免权限,卸载本 App 不变,避免客户端自生成的 id 每次重装都变。
/// 同通道顺带提供设备区域(ISO-2),注册时作为服务端 IP 解析失败后的兜底。
private func registerDeviceIdChannel(_ engineBridge: FlutterImplicitEngineBridge) {
let channel = FlutterMethodChannel(
name: "com.stormg.lumadrama/device",
binaryMessenger: engineBridge.applicationRegistrar.messenger()
)
channel.setMethodCallHandler { call, result in
switch call.method {
case "idfv":
result(UIDevice.current.identifierForVendor?.uuidString)
case "region":
if #available(iOS 16.0, *) {
result(Locale.current.region?.identifier)
} else {
result(Locale.current.regionCode)
}
default:
result(FlutterMethodNotImplemented)
}
}
}
private func registerWirelessDataChannel(_ engineBridge: FlutterImplicitEngineBridge) {
let channel = FlutterMethodChannel(
name: WirelessDataPrompt.channelName,
binaryMessenger: engineBridge.applicationRegistrar.messenger()
)
channel.setMethodCallHandler { [weak self] call, result in
guard call.method == WirelessDataPrompt.awaitDecisionMethod else {
result(FlutterMethodNotImplemented)
return
}
guard let self else {
result(nil)
return
}
self.wirelessDataPrompt.awaitDecision(result)
}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 734 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 B

After

Width:  |  Height:  |  Size: 822 B

Loaded 100 of 627 files, more files were not shown because too many files have changed in this diff. Show more