Browse Source

修 iOS 上切到同传/面对面时字幕小窗关不掉、且中断 changeTranslationMode

_floatingWindowManager 是 late 且只在 onInit 的 `Platform.isAndroid` 分支里赋值
(iOS 走 initPlatformState 那套 SubtitlePip)。全项目 5 处引用里 4 处都带平台守卫,
**只有 _disableFloatingWindow() 那处没有** —— 而它恰恰会被 changeTranslationMode 调到
(从多媒体/通话翻译切到同传或面对面时,小窗不受支持要自动关掉)。

iOS 上后果是连锁的,且没有一条指向真因:LateInitializationError 从
changeTranslationMode 中途抛出,后面的 _reinitializeAsrService() 再也走不到;
小窗又没真的停掉,继续浮在同传/面对面页上盖住顶部的标题与按钮;偏偏小窗开关按钮
只在 call/audioVideo 两个模式才渲染,用户连关都关不掉。

- _disableFloatingWindow() 分平台,并收敛成 toggle / onClose / changeTranslationMode
  三处共用的唯一出口;Android 分支补 Get.isRegistered 守卫。
- 切到不支持小窗的模式时改成**无条件**关一次,不再依赖 isFloatingWindowEnabled:
  iOS 上这个标志位由 initPlatformState 里那次异步的 SubtitlePip.isActive() 回填,
  和 onInit 紧接着调用的 changeTranslationMode 是竞态,回填晚一步就整个跳过。
  没开着时多关一次是无害的。

真机验证(iPhone 12 / iOS 26.6.2):改前在运行中的 App 里直接取 _floatingWindowManager
确实抛 LateInitializationError;改后走完整路径(多媒体翻译 → 开小窗 → 切同传),
changeTranslationMode 正常返回、pip=false、widget 树里 ErrorWidget 数为 0。
安卓侧(华为 ALN-AL80,debug + release)同传/面对面两页渲染不受影响。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
main
Rodger-Wang 4 weeks ago
parent
commit
c95996c251
  1. 53
      apps/client/lib/modules/translation/controllers/translation_controller.dart

53
apps/client/lib/modules/translation/controllers/translation_controller.dart

@ -273,12 +273,7 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
/// 切换悬浮窗功能
Future<void> toggleFloatingWindow() async {
if (isFloatingWindowEnabled.value) {
if (Platform.isAndroid) {
await _disableFloatingWindow();
} else if (Platform.isIOS) {
await SubtitlePip.stop();
isFloatingWindowEnabled.value = false;
}
} else {
if (Platform.isAndroid) {
await _enableFloatingWindow();
@ -364,9 +359,24 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
}
}
/// 禁用悬浮窗功能
/// 关掉悬浮字幕窗。
///
/// ⚠️ **必须分平台**:[_floatingWindowManager] 是 `late` 且只在
/// [onInit] 的 `Platform.isAndroid` 分支里赋值(iOS 走
/// [initPlatformState] 那套 SubtitlePip),在 iOS 上碰它就是
/// LateInitializationError。而本方法会被 [changeTranslationMode] 调到
/// ——从多媒体/通话翻译切到同传或面对面时,小窗不受支持要自动关掉。
/// 少了这层判断,iOS 上的后果是连锁的、且没有一条指向真因:
/// 异常从 [changeTranslationMode] 中途抛出,后面的
/// [_reinitializeAsrService] 再也走不到;小窗又没真的停掉,
/// 继续浮在同传/面对面页上盖住顶部的标题与按钮;偏偏小窗开关按钮
/// 只在 call/audioVideo 两个模式才渲染,用户连关都关不掉。
Future<void> _disableFloatingWindow() async {
if (Platform.isIOS) {
await SubtitlePip.stop();
} else if (Get.isRegistered<FloatingWindowManager>()) {
await _floatingWindowManager.disableFloatingWindow();
}
isFloatingWindowEnabled.value = false;
// Get.snackbar(
@ -451,24 +461,15 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
await _ttsService.setTtsMode('normal');
}
// 检查悬浮窗支持情况
if (isFloatingWindowEnabled.value && !_isFloatingWindowSupportedMode()) {
// 当前模式不支持悬浮窗,自动禁用
// 小窗只支持 call / audioVideo,切到别的模式一律关掉。
// ⚠️ **不要再加 `isFloatingWindowEnabled.value &&` 这个前置条件**:
// iOS 上这个标志位是 [initPlatformState] 里那次异步的
// `SubtitlePip.isActive()` 回填的,而 onInit 紧接着就调本方法,
// 两者是竞态——回填晚一步,这里就当成「没开」直接跳过,小窗继续浮在
// 同传/面对面页上盖住顶部按钮,而小窗开关只在 call/audioVideo 渲染,
// 用户连关都关不掉。没开着时多关一次是无害的。
if (!_isFloatingWindowSupportedMode()) {
await _disableFloatingWindow();
// Get.snackbar(
// '模式已切换',
// '悬浮窗仅支持通话和音视频翻译模式',
// snackPosition: SnackPosition.TOP,
// );
} else if (!isFloatingWindowEnabled.value &&
_isFloatingWindowSupportedMode()) {
// 切换到支持悬浮窗的模式,提示用户可以启用
// Get.snackbar(
// '模式已切换',
// '可以启用悬浮窗显示翻译结果',
// snackPosition: SnackPosition.TOP,
// duration: const Duration(seconds: 4),
// );
}
if (mode == 'call') {
@ -1087,11 +1088,7 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
// _asrService.dispose();
// _astService.dispose();
if (Platform.isAndroid) {
_disableFloatingWindow();
} else if (Platform.isIOS) {
unawaited(SubtitlePip.stop());
}
unawaited(_disableFloatingWindow());
// 释放管理器资源
_timerManager.dispose();

Loading…
Cancel
Save