diff --git a/lib/core/utils/app_lifecycle_manager.dart b/lib/core/utils/app_lifecycle_manager.dart index 6c4e178c5..5ba7e1399 100644 --- a/lib/core/utils/app_lifecycle_manager.dart +++ b/lib/core/utils/app_lifecycle_manager.dart @@ -1,6 +1,11 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; +import 'package:agent_service/agent_service.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; +import '../../modules/meeting/controllers/meeting_record_controller.dart'; +import '../../routes/app_routes.dart'; import 'logger.dart'; import '../translations/app_translations.dart'; //import '../../modules/settings/controllers/settings_controller.dart'; @@ -23,17 +28,19 @@ class AppLifecycleManager extends GetxService with WidgetsBindingObserver { final RxBool _isInForeground = true.obs; bool get isInForeground => _isInForeground.value; + bool? _lastCanWakeUp; + @override void onInit() { super.onInit(); // 注册应用生命周期观察者 WidgetsBinding.instance.addObserver(this); Logger.info('AppLifecycleManager 已初始化'); + unawaited(refreshBleWakeupPolicy()); } @override void didChangeAppLifecycleState(AppLifecycleState state) { - final previousState = _appLifecycleState.value; _appLifecycleState.value = state; _lifecycleStateStream.value = state; @@ -44,6 +51,7 @@ class AppLifecycleManager extends GetxService with WidgetsBindingObserver { case AppLifecycleState.resumed: _isInForeground.value = true; _handleAppResumed(); + unawaited(refreshBleWakeupPolicy()); break; case AppLifecycleState.paused: case AppLifecycleState.inactive: @@ -56,6 +64,48 @@ class AppLifecycleManager extends GetxService with WidgetsBindingObserver { } } + Future refreshBleWakeupPolicy({String? route}) async { + final currentRoute = route ?? Get.currentRoute; + + final bool hasMeetingRecordingTask = _hasMeetingRecordingTask(); + final bool shouldDisableWakeup = hasMeetingRecordingTask || + _isMeetingRoute(currentRoute) || + _isTranslationRoute(currentRoute); + + final bool canWakeUp = !shouldDisableWakeup; + if (_lastCanWakeUp == canWakeUp) return; + _lastCanWakeUp = canWakeUp; + + try { + Logger.info('lxm---设置是否可以ble唤醒agent: $canWakeUp'); + await AgentService.setCanWakeUp(canWakeUp); + } catch (e) { + Logger.warning('setCanWakeUp失败: $e'); + } + } + + bool _isMeetingRoute(String route) { + if (route.isEmpty) return false; + if (route == Routes.profileMeeting) return true; + return route == Routes.meeting || route.startsWith('${Routes.meeting}/'); + } + + bool _isTranslationRoute(String route) { + if (route.isEmpty) return false; + if (route == Routes.translationMiddleware) return true; + if (route == Routes.pictureTranslation) return true; + if (route == Routes.textTranslation) return true; + return route == Routes.translation || + route.startsWith('${Routes.translation}/'); + } + + bool _hasMeetingRecordingTask() { + if (!Get.isRegistered()) return false; + final mrc = Get.find(); + return mrc.isRecording.value || + mrc.timerStatus.value != TimerStatus.stopped; + } + /// 处理应用进入前台 void _handleAppResumed() { // Logger.info('应用进入前台'); diff --git a/lib/main.dart b/lib/main.dart index 8ceedabe1..001c35645 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; @@ -8,6 +10,7 @@ import 'package:get_storage/get_storage.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'core/utils/app_lifecycle_manager.dart'; import 'routes/app_pages.dart'; import 'routes/app_routes.dart'; import 'core/translations/app_translations.dart'; @@ -127,6 +130,14 @@ class MainApp extends StatelessWidget { initialBinding: InitialBinding(), initialRoute: Routes.splash, getPages: AppPages.pages, + routingCallback: (routing) { + if (!Get.isRegistered()) return; + unawaited( + Get.find().refreshBleWakeupPolicy( + route: routing?.current, + ), + ); + }, debugShowCheckedModeBanner: false, translations: AppTranslations(), localizationsDelegates: const [ diff --git a/lib/modules/home/controllers/home_controller.dart b/lib/modules/home/controllers/home_controller.dart index 6073ecdcb..0dede3002 100644 --- a/lib/modules/home/controllers/home_controller.dart +++ b/lib/modules/home/controllers/home_controller.dart @@ -11,7 +11,6 @@ import '../../../data/services/ble_manager.dart'; import '../../../data/services/ota_check_service.dart'; import '../../../data/models/appconfig.dart'; import '../../../data/models/appconfig_model.dart'; -import '../../meeting/controllers/meeting_record_controller.dart'; /// 连接状态枚举 enum ConnectionStatus { @@ -241,13 +240,9 @@ class HomeController extends GetxController { Get.toNamed(Routes.pairing); } - void openTranslation() async { + void openTranslation() { AgentService.interruptCurrentResponse(); - AgentService.setCanWakeUp(false); - Logger.d(_tag, 'liwei-----------关闭唤醒功能'); - await Get.toNamed(Routes.translationMiddleware); - AgentService.setCanWakeUp(true); - Logger.d(_tag, 'liwei-----------打开唤醒功能'); + Get.toNamed(Routes.translationMiddleware); } /// 打开会议模块 @@ -255,31 +250,9 @@ class HomeController extends GetxController { /// 返回值:`Future` 无返回数据 /// 异常:不显式抛出;内部仅记录日志 /// 说明:进入会议页前关闭唤醒;返回首页后根据录音状态决定是否开启唤醒(存在录音任务则不启用) - void openMeet() async { + void openMeet() { AgentService.interruptCurrentResponse(); - AgentService.setCanWakeUp(false); - Logger.d(_tag, 'liwei-----------关闭唤醒功能'); - await Get.toNamed(Routes.meeting); - try { - if (Get.isRegistered()) { - final mrc = Get.find(); - final hasRecording = mrc.isRecording.value || - mrc.timerStatus.value != TimerStatus.stopped; - if (hasRecording) { - AgentService.setCanWakeUp(false); - Logger.d(_tag, 'openMeet返回:检测到录音任务,保持关闭唤醒功能'); - } else { - AgentService.setCanWakeUp(true); - Logger.d(_tag, 'openMeet返回:无录音任务,打开唤醒功能'); - } - } else { - AgentService.setCanWakeUp(true); - Logger.d(_tag, 'openMeet返回:未注册MeetingRecordController,默认打开唤醒功能'); - } - } catch (e) { - AgentService.setCanWakeUp(true); - Logger.e(_tag, 'openMeet返回:检测录音任务异常,默认打开唤醒功能: $e'); - } + Get.toNamed(Routes.meeting); } void exploreAIFeatures() { diff --git a/lib/modules/meeting/views/meeting_record_view.dart b/lib/modules/meeting/views/meeting_record_view.dart index 48ef7e0cd..5950067da 100644 --- a/lib/modules/meeting/views/meeting_record_view.dart +++ b/lib/modules/meeting/views/meeting_record_view.dart @@ -6,6 +6,7 @@ import 'package:get/get.dart'; import '../controllers/meeting_record_controller.dart'; import '../../../core/theme/app_colors.dart'; +/// 会议录音退出操作 enum _MeetingRecordExitAction { saveAndEnd, endWithoutSave, @@ -13,6 +14,7 @@ enum _MeetingRecordExitAction { cancel, } +/// 会议录音页面 class MeetingRecordView extends GetView { const MeetingRecordView({super.key}); @@ -24,6 +26,7 @@ class MeetingRecordView extends GetView { }); } + /// 显示退出确认弹窗 Future<_MeetingRecordExitAction?> _showExitConfirmDialog(bool isDarkMode) { return Get.dialog<_MeetingRecordExitAction>( Dialog( @@ -58,7 +61,7 @@ class MeetingRecordView extends GetView { ), SizedBox(height: 14.h), Text( - 'meetingRecordExitTitle'.tr, // 对应中文:退出录音 + 'meetingRecordExitTitle'.tr, // 退出录音 style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.bold, @@ -67,7 +70,7 @@ class MeetingRecordView extends GetView { ), SizedBox(height: 8.h), Text( - 'meetingRecordExitMessage'.tr, // 对应中文:请选择离开后的操作 + 'meetingRecordExitMessage'.tr, // 请选择离开后的操作 textAlign: TextAlign.center, style: TextStyle( fontSize: 14.sp, @@ -96,7 +99,7 @@ class MeetingRecordView extends GetView { elevation: 0, ), child: Text( - 'meetingRecordEndAndSave'.tr, // 对应中文:结束录音并保存 + 'meetingRecordEndAndSave'.tr, // 结束录音并保存 style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w600, @@ -121,7 +124,7 @@ class MeetingRecordView extends GetView { ), ), child: Text( - 'meetingRecordContinueInBackground'.tr, // 对应中文:后台继续录音 + 'meetingRecordContinueInBackground'.tr, // 后台继续录音 style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w500, @@ -146,7 +149,7 @@ class MeetingRecordView extends GetView { ), ), child: Text( - 'meetingRecordEndWithoutSave'.tr, // 对应中文:结束录音不保存 + 'meetingRecordEndWithoutSave'.tr, // 结束录音不保存 style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w500, @@ -168,7 +171,7 @@ class MeetingRecordView extends GetView { ), ), child: Text( - 'cancel'.tr, // 对应中文:取消 + 'cancel'.tr, // 取消 style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w500, @@ -305,6 +308,7 @@ class MeetingRecordView extends GetView { ); } + /// 构建音频标签页 Widget _buildAudioTab(BuildContext context, bool isDarkMode) { return LayoutBuilder( builder: (context, constraints) { diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift index b22c0e2c5..98c3d4685 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift @@ -80,6 +80,7 @@ class AgentServiceImpl: NSObject { private var isRecognizing: Bool = false private var hasSpeechDetected: Bool = false internal var isAiStreaming: Bool = false + private var isCanWakeUp: Bool = true // 新增:识别"启动中"与"启动中收到停止请求"状态 // 用途:解决 start 异步、stop 先于 onSessionStarted 到来时被忽略的问题 @@ -167,6 +168,10 @@ class AgentServiceImpl: NSObject { private func sendError(_ message: String, code: String = "ERROR") { sendEvent(name: "error", data: ["code": code, "message": message]) } + + func setCanWakeUp(_ canWakeUp: Bool) { + isCanWakeUp = canWakeUp + } private func initSystemPrompt() { systemPrompt = """ @@ -2025,7 +2030,9 @@ extension AgentServiceImpl: BleService.Callback { func onWakeupSignalReceived() { - + if !isCanWakeUp { + return + } startRecognition(useBle: true, mode: "ble_wakeup") } diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServicePlugin.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServicePlugin.swift index 88ed6f736..e8a5d6209 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServicePlugin.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServicePlugin.swift @@ -194,6 +194,14 @@ public class AgentServicePlugin: NSObject, FlutterPlugin { } let success = impl.setTtsRate(rate) result(success) + case "setCanWakeUp": + guard let arguments = call.arguments as? [String: Any], + let canWakeUp = arguments["canWakeUp"] as? Bool else { + result(FlutterError(code: "INVALID_ARGUMENTS", message: "canWakeUp参数不能为空", details: nil)) + return + } + impl.setCanWakeUp(canWakeUp) + result(true) default: result(FlutterMethodNotImplemented) } diff --git a/local_plugins/agent_service/lib/agent_service.dart b/local_plugins/agent_service/lib/agent_service.dart index 97737c671..3ddbed06d 100644 --- a/local_plugins/agent_service/lib/agent_service.dart +++ b/local_plugins/agent_service/lib/agent_service.dart @@ -520,7 +520,10 @@ class AgentService { } } - /// 设置是否可以唤醒 + /// 设置是否可以ble唤醒agent + /// + /// [canWakeUp] 是否可以ble唤醒agent + /// 返回是否设置成功 static Future setCanWakeUp(bool canWakeUp) async { try { final bool result = await _channel.invokeMethod('setCanWakeUp', { @@ -528,7 +531,8 @@ class AgentService { }); return result; } on PlatformException catch (e) { - throw AgentServiceException(e.code, e.message ?? '设置是否可以唤醒失败', e.details); + throw AgentServiceException( + e.code, e.message ?? '设置是否可以ble唤醒agent失败', e.details); } }