diff --git a/README.md b/README.md index 004a776..6663617 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,154 @@ -一个使用 Flutter 开发的短剧播放 App 原型,面向海外用户,重点探索沉浸式刷剧、找剧和连续观看的移动端体验 +# FruitShorts(ReelShort)客户端 -## 主要功能 +FruitShorts 是面向印尼市场的短剧 Flutter 客户端。当前版本已连接 +`FruitServer` 的 `/app/**` 接口,支持沉浸播放、找剧、账号、观看记录、收藏、 +下载授权、评论互动、站内通知和中英印尼三语界面。 -- **沉浸式首页**:竖屏播放,上下滑切换短剧,左右滑切换内容类别;支持拖动进度、横屏全屏观看。 -- **剧集播放**:从详情页进入后按集连续播放,支持选集和本地续看。 -- **找剧与个人页**:浏览、搜索和查看剧集详情;在个人页查看观看记录、收藏与喜欢的内容。 -- **互动与多语言**:本地演示评论、系统分享,以及英文、简体中文和印尼语界面。 -- **画中画**:Android 接入系统画中画;iOS 画中画尚未实现。 +## 技术栈 -## 技术实现 +- Flutter 3.47.7 / Dart 3.13.5 +- Provider 状态管理与依赖注入 +- dio 网络请求 +- video_player 播放 +- shared_preferences 与 `SharedPreferencesAsync` 本地配置 +- flutter_localizations,支持 `en`、`zh`、`id` +- Android 系统画中画 -项目使用 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/ 图标、图片和本地媒体文件 +``` -目录数据来自短剧服务的客户端接口(`FruitServer` 的 `fruitshorts-app-api`,默认 `http://127.0.0.1:8081`): +## 已接接口 -- `GET /app/home/feed`、`GET /app/discover/feed`:轮播 + 分区,一次请求渲染整页。 - 每张卡片带 `preview`(首个可播免费集),首页竖屏播放可以直接起播; - 没有片源的剧不下发 `preview`,也不会出现在首页的滑动队列里 -- `GET /app/dramas/{id}`:详情(含剧集列表;可播放的剧集直接带播放地址) -- `GET /app/episodes/{id}/play`:播放票据,返回可访问性与地址 +以下接口均在 `lib/core/data/remote/` 中有实际调用。 -启动后端后直接 `flutter run` 或 Xcode 里 Run 即可,接口地址按运行环境自动选择 -(见 `lib/core/config/app_config.dart`): +| 分组 | 契约 | 接口 | +| --- | --- | --- | +| 账号 | §1.1 | `POST /app/auth/register`、`/login`、`/refresh`、`/logout` | +| 内容 | §1.3、§1.4 | `GET /app/home/feed`、`/app/discover/feed`、`/app/categories`、`/app/dramas/{id}`、`/app/episodes/{id}/play` | +| 观看记录 | §1.5 | `POST/GET /app/watch/progress`、`GET /app/watch/recent`、`DELETE /app/watch/progress/{dramaId}`、`DELETE /app/watch/progress` | +| 收藏 | §1.5 | `POST /app/favorites/toggle`、`GET /app/favorites`、`DELETE /app/favorites/{dramaId}` | +| 下载授权 | §1.6 | `GET /app/downloads/policy`、`PUT /app/downloads/settings`、`POST/GET /app/downloads`、`GET /app/downloads/{episodeId}/url`、`POST /app/downloads/{episodeId}/progress`、`DELETE /app/downloads/{episodeId}`、`DELETE /app/downloads` | +| 互动 | §1.7 | `GET/POST /app/comments`、`DELETE /app/comments/{id}`、`POST /app/likes/toggle`、`POST /app/reports` | +| 公告 | §1.9.2 | `GET /app/notices` | -| 运行环境 | 默认地址 | -| --- | --- | -| iOS 模拟器 / 桌面端 | `http://127.0.0.1:8081` | -| iPhone 真机 | [`devHostBaseUrl`],即电脑当前的局域网地址(`ipconfig getifaddr en0`) | -| Android 模拟器 | 需手动指定 `--dart-define=API_BASE_URL=http://10.0.2.2:8081` | +客户端没有调用 `/app/config` 或创作者入驻接口。 -任何环境都可以用 `--dart-define=API_BASE_URL=http://x.x.x.x:8081` 覆盖默认值。 -电脑换网络后局域网地址会变,改 `devHostBaseUrl` 即可;也可以换成 Bonjour 名字 -(`http://$(scutil --get LocalHostName).local:8081`),换网络也不用改。 +## 运行 -真机调试还需注意:手机和电脑在同一个 Wi-Fi;首次启动时 iPhone 会弹「本地网络」权限, -必须允许;iOS 的明文 HTTP 与局域网访问已在 `ios/Runner/Info.plist` 里放行 -(`NSAllowsLocalNetworking`、`NSLocalNetworkUsageDescription`),改动这些配置后 -要重新构建安装,热重载不生效;如果仍连不上,到「系统设置 → 网络 → 防火墙 → 选项」 -把 `java` 设为允许传入连接。 +### 安装依赖 -封面与视频都走 `http://:8081/files/**`,本地不再打包演示素材 -(原先 `assets/media` 里的演示视频有 316MB,已从打包清单移除)。 +```bash +flutter pub get +``` -## 当前说明 +### Android USB -收藏、点赞、评论与观看进度仍保存在本机(后端互动接口尚未开发); -金币与会员相关界面仍是占位数据。 +手机开启 USB 调试并连接电脑后: + +```bash +adb devices +adb reverse tcp:8081 tcp:8081 +flutter run -d \ + --dart-define=API_BASE_URL=http://127.0.0.1:8081 +``` + +`adb reverse` 将手机的 `127.0.0.1:8081` 转发到电脑;USB 断开或重新连接后需要 +重新执行。 + +Android 模拟器访问宿主机时使用: + +```bash +flutter run --dart-define=API_BASE_URL=http://10.0.2.2:8081 +``` + +### iOS + +手机与 Mac 需在同一网络,首次启动允许“本地网络”权限: + +```bash +flutter run -d \ + --dart-define=API_BASE_URL=http://:8081 +``` + +### Debug 运行时地址 + +debug 版本可在「我的 → 设置 → 服务器地址」修改后端地址,保存后重启 App 生效。 +地址优先级: + +```text +本地运行时设置 > --dart-define(API_BASE_URL) > 平台默认地址 +``` + +因此不带 `--dart-define` 构建的 debug APK 也可在安装后改成当前电脑 IP。 +release 版本不显示入口,也不会读取本地覆盖。 + +## 环境注意 + +### 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 \ + --dart-define=API_BASE_URL=http://127.0.0.1:8081 +``` + +## 门禁 + +```bash +dart analyze lib test +flutter test +flutter build apk --debug +``` + +测试文件按模块放在 `test/` 下,用例名使用 +`should_<期望>_when_<条件>`。真实后端联调测试在服务未启动时会跳过。 + +## 当前边界 + +- 下载仅管理服务端授权,不落盘、不续传、不支持离线播放。 +- 没有独立搜索接口,搜索能力尚未形成完整闭环。 +- 站内通知没有已读/未读和红点状态。 +- 支付、金币和会员权益尚未形成客户端闭环。 +- Android 支持系统画中画;iOS 画中画尚未接入。 diff --git a/docs/FILES.md b/docs/FILES.md new file mode 100644 index 0000000..cf68bc5 --- /dev/null +++ b/docs/FILES.md @@ -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 包管理工具维护,应提交但不手工编辑。 diff --git a/docs/TASKS.md b/docs/TASKS.md new file mode 100644 index 0000000..e1932eb --- /dev/null +++ b/docs/TASKS.md @@ -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` 代码和自动化测试为准。 +- “需核对”表示产品范围、接口或外部凭据尚未确认,不能按已完成处理。