From 7df26d3b1a50f049d85949cf321a6bbc7c9ef1c6 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Sat, 15 Nov 2025 18:11:34 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9iOS=E7=94=BB=E4=B8=AD?= =?UTF-8?q?=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/translation_controller.dart | 115 +++++++++++++++--- .../translation/views/translation_view.dart | 43 ++++--- 2 files changed, 121 insertions(+), 37 deletions(-) diff --git a/lib/modules/translation/controllers/translation_controller.dart b/lib/modules/translation/controllers/translation_controller.dart index 79cac6ab1..f15b1a0d0 100644 --- a/lib/modules/translation/controllers/translation_controller.dart +++ b/lib/modules/translation/controllers/translation_controller.dart @@ -2,12 +2,15 @@ import 'dart:io'; import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; import 'package:intl/intl.dart'; +import 'package:native_plugin/native_plugin.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:device_info_plus/device_info_plus.dart'; +import 'package:pip/pip.dart'; import '../../../data/services/ast_service.dart'; import '../../../data/services/bluetooth_manager.dart'; import '../../../data/services/music_manager.dart'; @@ -49,7 +52,12 @@ class TranslationController extends GetxController with WidgetsBindingObserver { final BleManager bleManager = Get.find(); final _musiceManager = Get.find(); late final TranslationUsageService _usageService; + // 安卓悬浮窗管理器 late final FloatingWindowManager _floatingWindowManager; + // 苹果悬浮窗管理器 + late final Pip _pip; + late final NativePlugin _nativePlugin; + int _pipContentView = 0; // ==================== 悬浮窗相关 ==================== final isFloatingWindowEnabled = false.obs; // 悬浮窗状态 @@ -135,7 +143,7 @@ class TranslationController extends GetxController with WidgetsBindingObserver { // 底部控制栏高度 double _bottomBarHeight = 0.0; - /// 初始化悬浮窗管理器 + /// 初始化安卓悬浮窗管理器 void _initFloatingWindowManager() { try { if (!Get.isRegistered()) { @@ -148,13 +156,68 @@ class TranslationController extends GetxController with WidgetsBindingObserver { } } + /// 初始化苹果悬浮窗管理器 + Future initPlatformState() async { + _pip = Pip(); + _nativePlugin = NativePlugin(); + try { + isFloatingWindowEnabled.value = await _pip.isActived(); + await _pip.registerStateChangedObserver( + PipStateChangedObserver( + onPipStateChanged: (state, error) { + isFloatingWindowEnabled.value = state == PipState.pipStateStarted; + + if (state == PipState.pipStateFailed) { + _pip.dispose(); + } + }, + ), + ); + } on PlatformException { + isFloatingWindowEnabled.value = false; + } + } + /// 切换悬浮窗功能 Future toggleFloatingWindow() async { if (isFloatingWindowEnabled.value) { - await _disableFloatingWindow(); + if (Platform.isAndroid) { + await _disableFloatingWindow(); + } else if (Platform.isIOS) { + await _pip.dispose(); + } } else { - await _enableFloatingWindow(); + if (Platform.isAndroid) { + await _enableFloatingWindow(); + } else if (Platform.isIOS) { + await _startPip(); + } + } + } + + Future _startPip() async { + if (_pipContentView == 0) { + _pipContentView = await _nativePlugin.createPipContentView(); } + final options = PipOptions( + autoEnterEnabled: true, + aspectRatioX: 375, + aspectRatioY: 400, + sourceRectHintLeft: 0, + sourceRectHintTop: 0, + sourceRectHintRight: 0, + sourceRectHintBottom: 0, + seamlessResizeEnabled: true, + useExternalStateMonitor: true, + externalStateMonitorInterval: 100, + contentView: _pipContentView, + preferredContentWidth: 375, + preferredContentHeight: 400, + controlStyle: 2, + ); + + await _pip.setup(options); + await _pip.start(); } /// 启用悬浮窗功能 @@ -217,16 +280,24 @@ class TranslationController extends GetxController with WidgetsBindingObserver { _languageManager.getChineseNameByAsrCode(item.targetLanguageCode) ?? 'unknown'; - final data = FloatingWindowData( - sourceText: item.sourceText, - translatedText: item.translatedText, - sourceLanguage: sourceLanguageName, - targetLanguage: targetLanguageName, - timestamp: item.timestamp, - isIntermediate: item.isIntermediate, - ); - - await _floatingWindowManager.updateFloatingWindowContent(data); + if (Platform.isAndroid) { + final data = FloatingWindowData( + sourceText: item.sourceText, + translatedText: item.translatedText, + sourceLanguage: sourceLanguageName, + targetLanguage: targetLanguageName, + timestamp: item.timestamp, + isIntermediate: item.isIntermediate, + ); + await _floatingWindowManager.updateFloatingWindowContent(data); + } else if (Platform.isIOS) { + await _nativePlugin.updatePipContentView({ + 'viewId': _pipContentView, + 'cn': item.sourceText, + 'en': item.translatedText, + 'lang': '$sourceLanguageName -> $targetLanguageName', + }); + } } /// 更改翻译模式 @@ -374,8 +445,13 @@ class TranslationController extends GetxController with WidgetsBindingObserver { _timerManager = RecordTimerController(); _historyManager = TranslationHistoryManager(); - // 初始化悬浮窗管理器 - _initFloatingWindowManager(); + if (Platform.isAndroid) { + // 初始化安卓悬浮窗管理器 + _initFloatingWindowManager(); + } else if (Platform.isIOS) { + // 初始化苹果悬浮窗管理器 + initPlatformState(); + } // 预先检查权限,避免在用户交互时阻塞 _preCheckPermissions(); @@ -665,6 +741,15 @@ class TranslationController extends GetxController with WidgetsBindingObserver { // _asrService.dispose(); // _astService.dispose(); + if (Platform.isAndroid) { + _disableFloatingWindow(); + } else if (Platform.isIOS) { + _pip.dispose(); + if (_pipContentView != 0) { + _nativePlugin.disposePipContentView(_pipContentView); + } + } + // 释放管理器资源 _timerManager.dispose(); _historyManager.dispose(); diff --git a/lib/modules/translation/views/translation_view.dart b/lib/modules/translation/views/translation_view.dart index 25362ae6e..c089c0ff2 100644 --- a/lib/modules/translation/views/translation_view.dart +++ b/lib/modules/translation/views/translation_view.dart @@ -235,28 +235,27 @@ class TranslationView extends GetView { return Row( children: [ // 悬浮窗切换按钮(仅在支持的模式下显示) - if (Platform.isAndroid) - Obx(() { - if (controller.currentMode.value == 'call' || - controller.currentMode.value == 'audioVideo') { - return IconButton( - icon: Icon( - controller.isFloatingWindowEnabled.value - ? Icons.picture_in_picture - : Icons.picture_in_picture_alt_outlined, - color: controller.isFloatingWindowEnabled.value - ? Colors.blue - : (isDarkMode ? Colors.white : Colors.black), - size: 24.sp, - ), - onPressed: controller.toggleFloatingWindow, - tooltip: controller.isFloatingWindowEnabled.value - ? '禁用悬浮窗' - : '启用悬浮窗', - ); - } - return const SizedBox.shrink(); - }), + Obx(() { + if (controller.currentMode.value == 'call' || + controller.currentMode.value == 'audioVideo') { + return IconButton( + icon: Icon( + controller.isFloatingWindowEnabled.value + ? Icons.picture_in_picture + : Icons.picture_in_picture_alt_outlined, + color: controller.isFloatingWindowEnabled.value + ? Colors.blue + : (isDarkMode ? Colors.white : Colors.black), + size: 24.sp, + ), + onPressed: controller.toggleFloatingWindow, + tooltip: controller.isFloatingWindowEnabled.value + ? '禁用悬浮窗' + : '启用悬浮窗', + ); + } + return const SizedBox.shrink(); + }), IconButton( icon: Icon( Icons.radio_button_checked,