# floating_ui_plugin 该目录是一个本地 Flutter 插件包,当前在项目中主要承担「悬浮 UI」相关的原生能力: - Android:翻译悬浮窗(系统悬浮窗 / overlay window)+ 录音悬浮条(计时/暂停/关闭)+ 悬浮窗权限检查/跳转。 - iOS:为系统画中画(PiP)提供可渲染的原生内容 View,并支持动态更新翻译文本。 > 说明:Flutter 包名为 floating_ui_plugin(见本插件 pubspec.yaml),主工程通过 path 依赖引用本目录。 ## 主要能力 ### 1) Android 翻译悬浮窗(Translation Floating Window) - 通道:`MethodChannel('floating_window')` - 原理:Flutter 调用通道方法 → Android 启动/更新 `FloatingWindowService` → service 通过系统 WindowManager 显示 overlay view。 - 交互回传:service 会反向调用同一通道,把状态/点击/关闭事件回传给 Flutter。 支持的方法(Flutter → Android): - `enableFloatingWindow`:启动悬浮窗服务(如果已在运行,相当于确保存在)。 - `disableFloatingWindow`:停止悬浮窗服务。 - `showFloatingWindow`:显示悬浮窗(当前实现等价于启动服务)。 - `hideFloatingWindow`:隐藏悬浮窗(stopSelf)。 - `updateContent(Map)`:更新展示内容。 - `setPosition({x,y})`:设置悬浮窗位置。 回传的方法(Android → Flutter): - `onFloatingWindowStateChanged({state})`:`hidden|minimized|expanded` - `onFloatingWindowClosed()`:用户关闭。 - `onFloatingWindowClicked()`:用户点击语言区域(service 内部会 bringAppToFront)。 数据协议(updateContent): - `sourceText` / `translatedText` - `sourceLanguage` / `targetLanguage` - `timestamp`(毫秒时间戳) - `isIntermediate`(是否中间态) 对应实现: - Android 插件入口:[NativePlugin.kt](file:///Users/liuxiamin/work/flutter_projects/deep_voice/local_plugins/floating_ui_plugin/android/src/main/kotlin/com/example/native_plugin/NativePlugin.kt) - 悬浮窗服务:[FloatingWindowService.kt](file:///Users/liuxiamin/work/flutter_projects/deep_voice/local_plugins/floating_ui_plugin/android/src/main/kotlin/com/example/native_plugin/FloatingWindowService.kt) ### 2) Android 悬浮窗权限(Overlay Permission) - 通道:`MethodChannel('overlay_permission')` - 方法: - `hasOverlayPermission`:是否具备 SYSTEM_ALERT_WINDOW - `requestOverlayPermission`:跳转系统页面申请 对应实现:[NativePlugin.kt](file:///Users/liuxiamin/work/flutter_projects/deep_voice/local_plugins/floating_ui_plugin/android/src/main/kotlin/com/example/native_plugin/NativePlugin.kt) ### 3) Android 录音悬浮条(Recording Floating Bar) - 通道:`MethodChannel('native_plugin')` - 原理:Flutter 下发 show/update/hide → Android 启动/更新 `RecordingFloatingService` → service 以 overlay 方式显示一个小型悬浮条。 支持的方法(Flutter → Android): - `showRecordingFloatingBar({duration, recordingType, isPaused})` - `updateRecordingFloatingBar({duration, recordingType, isPaused})` - `hideRecordingFloatingBar()` 用户操作回传(Android → Flutter): - 事件通道:`EventChannel('native_plugin/recording_floating_actions')` - action 字符串: - `togglePause`:点击暂停/继续 - `openMeetingRecord`:点击中间区域,拉起 App 并打开录音页 - `closeFloatingBar`:关闭悬浮条 对应实现: - 服务:[RecordingFloatingService.kt](file:///Users/liuxiamin/work/flutter_projects/deep_voice/local_plugins/floating_ui_plugin/android/src/main/kotlin/com/example/native_plugin/RecordingFloatingService.kt) - 事件桥接:[NativePlugin.emitRecordingFloatingAction](file:///Users/liuxiamin/work/flutter_projects/deep_voice/local_plugins/floating_ui_plugin/android/src/main/kotlin/com/example/native_plugin/NativePlugin.kt#L26-L39) ## iOS:PiP 内容 View(Picture-in-Picture Content View) - 通道:`MethodChannel('native_plugin')` - 目标:创建一个用于 PiP 渲染的原生 `UIView`(`PlayerView`),并支持后续更新显示内容。 支持的方法: - `createPipContentView()`:返回一个 `viewId`(本质是 PlayerView 的指针值)。 - `updatePipContentView({viewId?, lang, cn, en})`:更新内容。 - `disposePipContentView(viewId)`:释放。 对应实现: - 插件入口:[NativePlugin.swift](file:///Users/liuxiamin/work/flutter_projects/deep_voice/local_plugins/floating_ui_plugin/ios/Classes/NativePlugin.swift) - 具体 UI:[PlayerView.swift](file:///Users/liuxiamin/work/flutter_projects/deep_voice/local_plugins/floating_ui_plugin/ios/Classes/PlayerView.swift) ## 在主工程中的调用链路(现状) ### Android:翻译悬浮窗 - 入口:翻译页控制器 [TranslationController](file:///Users/liuxiamin/work/flutter_projects/deep_voice/lib/modules/translation/controllers/translation_controller.dart) - `toggleFloatingWindow()` → `_enableFloatingWindow()` - 管理器:[FloatingWindowManager](file:///Users/liuxiamin/work/flutter_projects/deep_voice/lib/core/services/floating_window_manager.dart) - `enableFloatingWindow()`:先通过 [OverlayPermissionUtil](file:///Users/liuxiamin/work/flutter_projects/deep_voice/lib/core/utils/overlay_permission_util.dart) 请求权限,再调用 `TranslationFloatingWindowApi.enable()` - `_setupMethodCallHandler()`:接收 `onFloatingWindowStateChanged/onFloatingWindowClicked/onFloatingWindowClosed` 并更新状态/路由跳转 - 内容更新:`TranslationController._updateFloatingWindowWithTranslation()` → `FloatingWindowManager.updateFloatingWindowContent()` → `updateContent(Map)` ### Android:录音悬浮条 - 入口:会议录音控制器 [MeetingRecordController](file:///Users/liuxiamin/work/flutter_projects/deep_voice/lib/modules/meeting/controllers/meeting_record_controller.dart) - `showBackgroundRecordingIndicator()` → `_startAndroidRecordingFloatingBar()` → `NativePlugin.showRecordingFloatingBar()` - 定时器每秒 `updateRecordingFloatingBar()` 更新 duration/isPaused - `_setupRecordingFloatingBarActionListener()` 监听 `recordingFloatingActionStream` 并处理 `togglePause/openMeetingRecord/closeFloatingBar` ### iOS:翻译悬浮显示(PiP) - 入口同样在 [TranslationController](file:///Users/liuxiamin/work/flutter_projects/deep_voice/lib/modules/translation/controllers/translation_controller.dart) - `_startPip()`:`NativePlugin.createPipContentView()` → 将 viewId 传给第三方 PiP 组件的 `PipOptions.contentView` - `_updateFloatingWindowWithTranslation()`:`NativePlugin.updatePipContentView({viewId, cn, en, lang})` ## 权限与 Manifest - Android 需要:`android.permission.SYSTEM_ALERT_WINDOW` - 本插件自带 AndroidManifest 声明(包含两项 service): - [android/src/main/AndroidManifest.xml](file:///Users/liuxiamin/work/flutter_projects/deep_voice/local_plugins/floating_ui_plugin/android/src/main/AndroidManifest.xml) ## 平台差异与注意事项 - Android 与 iOS 的“悬浮显示”实现不同:Android 用系统悬浮窗;iOS 走系统 PiP。 - `createPipContentView/updatePipContentView/disposePipContentView` 仅 iOS 实现;Android 侧没有实现这些 method,请务必用 `Platform.isIOS` 保护调用。 - `floating_window` / `overlay_permission` / `native_plugin/recording_floating_actions` 都是固定通道名,避免在其他地方重复注册同名通道。 - 工程 `android/app/src/main/kotlin/com/yimai/eaimar/` 下存在同名通道的旧实现文件(未在 MainActivity 注册),当前实际走的是本插件实现;如后续要启用 app 侧实现,需要先解决通道名冲突与 service 声明问题。