diff --git a/apps/client/lib/modules/translation/controllers/translation_controller.dart b/apps/client/lib/modules/translation/controllers/translation_controller.dart index 6731f7d5..75ab3ec6 100644 --- a/apps/client/lib/modules/translation/controllers/translation_controller.dart +++ b/apps/client/lib/modules/translation/controllers/translation_controller.dart @@ -273,12 +273,7 @@ class TranslationController extends GetxController with WidgetsBindingObserver { /// 切换悬浮窗功能 Future toggleFloatingWindow() async { if (isFloatingWindowEnabled.value) { - if (Platform.isAndroid) { - await _disableFloatingWindow(); - } else if (Platform.isIOS) { - await SubtitlePip.stop(); - isFloatingWindowEnabled.value = false; - } + await _disableFloatingWindow(); } 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 _disableFloatingWindow() async { - await _floatingWindowManager.disableFloatingWindow(); + if (Platform.isIOS) { + await SubtitlePip.stop(); + } else if (Get.isRegistered()) { + 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();