FruitShorts(ReelShort)客户端

FruitShorts 是面向印尼市场的短剧 Flutter 客户端,已连接 FruitServer 的 /app/** 接口。功能覆盖:四 tab 主页(推荐视频流 / 找剧 / 追剧 / 我的)、 沉浸式播放与全屏剧集播放器、清晰度切换、剧单与排行榜、新剧页、搜索、观看记录、 收藏短剧、评论与点赞、站内通知、钱包与商品、下载落盘与离线播放、中英印尼三语, 以及 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)

目录结构

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 和创作者入驻接口。

运行

安装依赖

flutter pub get

Android USB

手机开启 USB 调试并连接电脑后:

adb devices
adb reverse tcp:8081 tcp:8081
flutter run -d <device> \
  --dart-define=API_BASE_URL=http://127.0.0.1:8081

adb reverse 将手机的 127.0.0.1:8081 转发到电脑;USB 断开或重新连接后需要 重新执行。

Android 模拟器访问宿主机时使用:

flutter run --dart-define=API_BASE_URL=http://10.0.2.2:8081

iOS

手机与 Mac 需在同一网络,首次启动允许“本地网络”权限:

flutter run -d <udid> \
  --dart-define=API_BASE_URL=http://<Mac局域网IP>:8081

Debug 运行时地址

debug 包在找剧页加载失败时会显示调试条,点其中的「修改服务器地址」可直接改成 当前电脑 IP,保存后立即重载,不用重新打包。地址优先级:

本地运行时设置 > --dart-define(API_BASE_URL) > AppConfig.defaultBaseUrl

devHostBaseUrl 默认写死为 http://192.168.31.243:8081 (lib/core/config/app_config.dart)。debug 包启动时按 「环回 127.0.0.1 → devHostBaseUrl → 公网」顺序探测可用地址;release 包不探测, 固定使用公网地址。换 Wi-Fi、路由器换网段后 devHostBaseUrl 会失效,此时要么传 --dart-define,要么改这个常量。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:

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 加载失败,通常是旧安装中的原生库残留:

adb uninstall com.stormg.lumadrama
flutter run -d <device> \
  --dart-define=API_BASE_URL=http://127.0.0.1:8081

生产打包(Android)

生产包只包含以下两个 ABI,打成一个 APK,不做 split:

ABI 适用设备
arm64-v8a 现代手机(64 位)
armeabi-v7a 老 32 位机
flutter build apk --release --target-platform android-arm64,android-arm
# 产出单个:build/app/outputs/flutter-apk/app-release.apk(约 37MB)

说明:

  • android/app/build.gradle.kts 的 release 已配置 abiFilters,只保留 arm64-v8a 与 armeabi-v7a,插件也不会再带入 x86_64 原生库。
  • x86_64 只用于模拟器,不进入生产包。
  • 不要直接运行 flutter build apk --release:默认会编译三套原生库,包体约 57MB。
  • 只有需要进一步压缩单机下载体积时,才考虑 --split-per-abi 出分架构包(arm64 约 21MB、armv7 约 19MB),默认不用。

生产打包(iOS)

# 正式签名(需要 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 后要重新构建,热重载不会生效。

门禁

dart analyze
flutter test
flutter build apk --debug

当前基线:dart analyze 仅 1 条既存 info(intl 未在 pubspec.yaml 声明); flutter test 全量 775 例通过。测试文件按模块放在 test/ 下,用例名使用 should_<期望>_when_<条件>。真实后端联调测试在服务未启动时会跳过。

当前边界

  • 下载会落盘并支持离线播放,但不支持断点续传:中途失败会丢弃 .part 重下。
  • 支付与金币:客户端已有钱包、商品、下单接口与页面,商店票据校验依赖服务端配置凭据。
  • 站内通知没有已读/未读和红点状态。
  • 播放清晰度切换会保留旧画面 500ms 掩盖闪屏,切换瞬间的进度以相对位置续播。
S
Description
No description provided
Readme
10 MiB
0 Stars 1 Watchers 0 Forks
Languages
Dart 97.5%
PowerShell 1.1%
Swift 0.8%
Kotlin 0.4%
Python 0.2%