From 6fbb164940e58367215efda860908c3b13fbc736 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Fri, 31 Oct 2025 15:53:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=AF=AD=E9=9F=B3=E4=BA=A4=E4=BA=92):=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=AF=AD=E8=A8=80=E6=89=93=E6=96=AD=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=B9=B6=E5=90=8C=E6=AD=A5=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加语言打断功能开关,包括: 1. 在三个语言文件中新增翻译键 2. 修复存储键名拼写错误(interrupe->interrupt) 3. 在Home和Agent页面添加开关控件并保持状态同步 4. 通过监听存储实现跨页面状态更新 --- lib/core/translations/language/en_us.dart | 1 + lib/core/translations/language/zh_cn.dart | 3 +- lib/core/translations/language/zh_hk.dart | 2 + lib/data/services/ble_manager.dart | 4 +- .../agent/controllers/agent_controller.dart | 18 ++- lib/modules/agent/views/agent_view.dart | 118 +++++++++--------- .../home/controllers/home_controller.dart | 55 ++++++++ lib/modules/home/views/home_view.dart | 98 ++++++++++++--- 8 files changed, 219 insertions(+), 80 deletions(-) diff --git a/lib/core/translations/language/en_us.dart b/lib/core/translations/language/en_us.dart index a4e154c64..6640f0c88 100644 --- a/lib/core/translations/language/en_us.dart +++ b/lib/core/translations/language/en_us.dart @@ -543,6 +543,7 @@ const Map enUS = { "loadFailedClickRetry": "Load failed, click to retry", "swipeUpForMore": "Swipe up for more", "callingTool": "I'm helping you, please wait!", // 正在帮您,请稍等! + "language_interrupt": "Language Interrupt", // 语言打断 // 预设提示词 "presetMusic": "Play some music", // Play some music diff --git a/lib/core/translations/language/zh_cn.dart b/lib/core/translations/language/zh_cn.dart index f33c8ca52..8da12ffcc 100644 --- a/lib/core/translations/language/zh_cn.dart +++ b/lib/core/translations/language/zh_cn.dart @@ -513,7 +513,8 @@ const Map zhCN = { "loadMoreMessages": "加载更多消息...", "loadFailedClickRetry": "加载失败,点击重试", "swipeUpForMore": "向上滑动查看更多", - "callingTool": "正在帮您,请稍等!", + "callingTool": "正在帮您,请稍等!", // 正在帮您,请稍等! + "language_interrupt": "语言打断", // 语言打断 // 预设提示词 "presetMusic": "来点音乐", // 来点音乐 diff --git a/lib/core/translations/language/zh_hk.dart b/lib/core/translations/language/zh_hk.dart index 52eaff0a7..3e7446dfb 100644 --- a/lib/core/translations/language/zh_hk.dart +++ b/lib/core/translations/language/zh_hk.dart @@ -295,6 +295,8 @@ const Map zhHK = { "loadMoreMessages": "載入更多訊息...", "loadFailedClickRetry": "載入失敗,點擊重試", "swipeUpForMore": "向上滑動查看更多", + "callingTool": "正在帮您,请稍等!", // 正在帮您,请稍等! + "language_interrupt": "语言打断", // 语言打断 // Agent controller 'welcomeMessage': '你好!我是**小言**,我能幫你:\n\n' diff --git a/lib/data/services/ble_manager.dart b/lib/data/services/ble_manager.dart index 970cf6d5c..12f330002 100644 --- a/lib/data/services/ble_manager.dart +++ b/lib/data/services/ble_manager.dart @@ -650,7 +650,7 @@ class BleManager extends GetxService { // 加载TTS偏好设置 Future _loadTtsInterrupe() async { try { - if (!_storage.hasData("agent_tts_interrupe")) { + if (!_storage.hasData("agent_tts_interrupt")) { // 获取设备类型以确定检查策略 String deviceName = getDeviceName(); DBProduct? product = AppConfig.getproduct(deviceName); @@ -662,7 +662,7 @@ class BleManager extends GetxService { return false; } } else { - return _storage.read('agent_tts_interrupe') ?? false; + return _storage.read('agent_tts_interrupt') ?? false; } } catch (e) { return false; diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index ebae04500..e18abbbc7 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -168,6 +168,18 @@ class AgentController extends GetxController with WidgetsBindingObserver { _initAppDir(); _loadTtsPreference(); //加载TTS偏好设置 _loadTtsInterrupe(); //加载TTS播报打断偏好设置 + // 监听存储中的“agent_tts_interrupe”键变化,实现跨页面状态同步 + // 中文注释:当 Home 页或 Agent 页任意一处切换时,另一处可以通过监听存储自动刷新 UI + _storage.listenKey('agent_tts_interrupt', (value) { + try { + final bool newValue = value is bool ? value : (value == 'true'); + if (isTtsInterruptEnabled.value != newValue) { + isTtsInterruptEnabled.value = newValue; + } + } catch (e) { + // 中文注释:忽略异常,避免因类型不一致导致崩溃 + } + }); _setupScrollListener(); //设置滚动监听器 _loadChatHistory(); //加载聊天历史记录 _subscribeToEvents(); //订阅代理服务事件 @@ -266,7 +278,7 @@ class AgentController extends GetxController with WidgetsBindingObserver { // 加载TTS偏好设置 Future _loadTtsInterrupe() async { try { - if (!_storage.hasData("agent_tts_interrupe")) { + if (!_storage.hasData("agent_tts_interrupt")) { // 获取设备类型以确定检查策略 String deviceName = getDeviceName(); DBProduct? product = AppConfig.getproduct(deviceName); @@ -279,7 +291,7 @@ class AgentController extends GetxController with WidgetsBindingObserver { } } else { isTtsInterruptEnabled.value = - _storage.read('agent_tts_interrupe') ?? false; + _storage.read('agent_tts_interrupt') ?? false; } await AgentService.isInterrupt(isTtsInterruptEnabled.value); } catch (e) { @@ -293,7 +305,7 @@ class AgentController extends GetxController with WidgetsBindingObserver { /// 当启用播报打断时,用户说话会自动停止当前的TTS播放 Future toggleTtsInterrupt() async { isTtsInterruptEnabled.toggle(); - await _storage.write('agent_tts_interrupe', isTtsInterruptEnabled.value); + await _storage.write('agent_tts_interrupt', isTtsInterruptEnabled.value); // 保存偏好设置 try { await AgentService.isInterrupt(isTtsInterruptEnabled.value); diff --git a/lib/modules/agent/views/agent_view.dart b/lib/modules/agent/views/agent_view.dart index 90c66e6b8..4151cf6b9 100644 --- a/lib/modules/agent/views/agent_view.dart +++ b/lib/modules/agent/views/agent_view.dart @@ -176,7 +176,7 @@ class _AgentViewState extends State with WidgetsBindingObserver { ), color: isDarkMode ? Colors.grey[800] : Colors.white, itemBuilder: (context) => [ - // 播报打断开关选项 + // 显示工具开关选项 PopupMenuItem( child: Obx(() => Row( children: [ @@ -225,7 +225,7 @@ class _AgentViewState extends State with WidgetsBindingObserver { SizedBox(width: 8.w), Text( //'ttsInterruptToggle'.tr, - "播报打断", + "language_interrupt".tr, // 语言打断 style: TextStyle(fontSize: 14.sp), ), const Spacer(), @@ -1286,52 +1286,26 @@ class _AgentViewState extends State with WidgetsBindingObserver { // 构建加载更多指示器 Widget _buildLoadMoreIndicator() { - return Obx(() { - if (controller.isLoadingMore.value) { - // 正在加载状态 - return Container( - padding: EdgeInsets.symmetric(vertical: 16.h), - child: Center( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox( - width: 16.w, - height: 16.h, - child: CircularProgressIndicator( - strokeWidth: 2.w, - ), - ), - SizedBox(width: 8.w), - Text( - 'loadMoreMessages'.tr, - style: TextStyle( - fontSize: 14.sp, - color: Colors.grey, - ), - ), - ], - ), - ), - ); - } else if (controller.isLoadingMoreError.value) { - // 加载失败状态 - return Container( - padding: EdgeInsets.symmetric(vertical: 16.h), - child: Center( - child: GestureDetector( - onTap: () => controller.loadMoreMessages(), + return Obx( + () { + if (controller.isLoadingMore.value) { + // 正在加载状态 + return Container( + padding: EdgeInsets.symmetric(vertical: 16.h), + child: Center( child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon( - Icons.refresh, - size: 16.sp, - color: Colors.grey, + SizedBox( + width: 16.w, + height: 16.h, + child: CircularProgressIndicator( + strokeWidth: 2.w, + ), ), SizedBox(width: 8.w), Text( - 'loadFailedClickRetry'.tr, + 'loadMoreMessages'.tr, style: TextStyle( fontSize: 14.sp, color: Colors.grey, @@ -1340,23 +1314,51 @@ class _AgentViewState extends State with WidgetsBindingObserver { ], ), ), - ), - ); - } else { - // 默认状态(有更多消息但未加载) - return Container( - padding: EdgeInsets.symmetric(vertical: 8.h), - child: Center( - child: Text( - 'swipeUpForMore'.tr, - style: TextStyle( - fontSize: 12.sp, - color: Colors.grey, + ); + } else if (controller.isLoadingMoreError.value) { + // 加载失败状态 + return Container( + padding: EdgeInsets.symmetric(vertical: 16.h), + child: Center( + child: GestureDetector( + onTap: () => controller.loadMoreMessages(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.refresh, + size: 16.sp, + color: Colors.grey, + ), + SizedBox(width: 8.w), + Text( + 'loadFailedClickRetry'.tr, + style: TextStyle( + fontSize: 14.sp, + color: Colors.grey, + ), + ), + ], + ), ), ), - ), - ); - } - }); + ); + } else { + // 默认状态(有更多消息但未加载) + return Container( + padding: EdgeInsets.symmetric(vertical: 8.h), + child: Center( + child: Text( + 'swipeUpForMore'.tr, + style: TextStyle( + fontSize: 12.sp, + color: Colors.grey, + ), + ), + ), + ); + } + }, + ); } } diff --git a/lib/modules/home/controllers/home_controller.dart b/lib/modules/home/controllers/home_controller.dart index 112974651..466b4792b 100644 --- a/lib/modules/home/controllers/home_controller.dart +++ b/lib/modules/home/controllers/home_controller.dart @@ -1,5 +1,6 @@ import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; +import 'package:agent_service/agent_service.dart'; // 移除了dart:async导入,因为不再使用Timer import '../../../data/services/novice_guide_service.dart'; import '../../../routes/app_routes.dart'; @@ -32,6 +33,9 @@ class HomeController extends GetxController { final _isFullyConnected = false.obs; String? _lastConnectedDeviceName; + // 播报打断开关状态(与 AgentController 对齐) + final isTtsInterruptEnabled = false.obs; // 是否启用播报打断 + // 移除了电池信息定时刷新相关变量 // OTA检查服务 @@ -101,6 +105,19 @@ class HomeController extends GetxController { _otaCheckService = Get.find(); _loadLastConnectedDeviceName(); // 加载持久化的设备名称 _initWithCachedInfo(); + _loadTtsInterrupt(); // 加载播报打断开关初始状态 + // 监听存储中的“agent_tts_interrupe”键变化,实现与 Agent 页同步 + // 中文注释:当 Agent 页或 Home 页任意一处切换时,另一处通过此监听自动刷新开关状态 + _storage.listenKey('agent_tts_interrupt', (value) { + try { + final bool newValue = value is bool ? value : (value == 'true'); + if (isTtsInterruptEnabled.value != newValue) { + isTtsInterruptEnabled.value = newValue; + } + } catch (e) { + // 中文注释:忽略异常,保持现有状态,避免因类型转换失败导致崩溃 + } + }); // 移除了电池信息定时刷新的启动 } @@ -144,6 +161,44 @@ class HomeController extends GetxController { }); } + /// 加载“播报打断”开关状态 + /// 逻辑与 AgentController 保持一致: + /// 1)若本地不存在存储,则根据设备产品的 iscontinuouschat 取反作为默认值; + /// 2)若存在存储则直接读取; + /// 3)同步到原生服务 AgentService.isInterrupt。 + Future _loadTtsInterrupt() async { + try { + if (!_storage.hasData('agent_tts_interrupt')) { + final deviceName = getDeviceName(); + final DBProduct? product = AppConfig.getproduct(deviceName); + if (deviceName.isNotEmpty && product != null) { + isTtsInterruptEnabled.value = !(product.iscontinuouschat ?? true); + } else { + isTtsInterruptEnabled.value = false; + } + } else { + isTtsInterruptEnabled.value = + _storage.read('agent_tts_interrupt') ?? false; + } + await AgentService.isInterrupt(isTtsInterruptEnabled.value); + } catch (e) { + Logger.e(_tag, '加载播报打断开关失败: $e'); + isTtsInterruptEnabled.value = false; + } + } + + /// 切换“播报打断”开关 + /// 同步保存到本地,并通知原生服务。 + Future toggleTtsInterrupt() async { + try { + isTtsInterruptEnabled.toggle(); + await _storage.write('agent_tts_interrupt', isTtsInterruptEnabled.value); + await AgentService.isInterrupt(isTtsInterruptEnabled.value); + } catch (e) { + Logger.e(_tag, '保存播报打断偏好失败: $e'); + } + } + /// 从本地存储加载最后连接的设备名称 void _loadLastConnectedDeviceName() { _lastConnectedDeviceName = _storage.read(_lastDeviceNameKey); diff --git a/lib/modules/home/views/home_view.dart b/lib/modules/home/views/home_view.dart index 486ef3b20..fbf1dbe51 100644 --- a/lib/modules/home/views/home_view.dart +++ b/lib/modules/home/views/home_view.dart @@ -170,23 +170,89 @@ class HomeView extends GetView { decoration: BoxDecoration( color: isDarkMode ? Colors.grey[800] : Colors.grey[200], shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.2), - blurRadius: 4, - offset: const Offset(0, 2), - ), - ], - ), - child: IconButton( - icon: Icon(Icons.settings_outlined, - size: 16.sp, - color: isDarkMode ? Colors.white70 : Colors.grey[600]), - padding: EdgeInsets.zero, - onPressed: () { - Get.toNamed(Routes.settings); - }, + // boxShadow: [ + // BoxShadow( + // color: Colors.grey.withOpacity(0.2), + // blurRadius: 4, + // offset: const Offset(0, 2), + // ), + // ], ), + child: Obx(() => PopupMenuButton( + padding: EdgeInsets.zero, + // 中文注释:缩小弹窗整体宽度,让展开窗口更紧凑 + constraints: BoxConstraints( + minWidth: 120.w, + maxWidth: 140.w, + ), + icon: Icon( + controller.isTtsInterruptEnabled.value + ? Icons.voice_over_off + : Icons.voice_over_off_outlined, + size: 12.sp, + color: controller.isTtsInterruptEnabled.value + ? Theme.of(Get.context!).colorScheme.primary // 使用主题主色 + : (isDarkMode ? Colors.white70 : Colors.grey[600]), + ), + elevation: 0, // 中文注释:去除弹窗阴影,实现扁平化 + // 中文注释:将弹窗位置整体上移,使菜单与“Voitrans”更为平齐 + offset: Offset(0, -10.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.w), // 中文注释:略微减小圆角 + side: BorderSide.none, // 中文注释:移除边框线,边缘不明显 + ), + color: isDarkMode ? Colors.grey[900] : Colors.grey[50], + itemBuilder: (context) => [ + PopupMenuItem( + // 中文注释:降低菜单项高度并收紧默认内边距,减少上下留白 + height: 15.h, + padding: + EdgeInsets.symmetric(horizontal: 2.w, vertical: 0.h), + child: Obx(() => Row( + mainAxisSize: MainAxisSize.max, // 中文注释:收紧行宽度 + mainAxisAlignment: + MainAxisAlignment.spaceBetween, // 让 Switch 靠右 + children: [ + Icon( + controller.isTtsInterruptEnabled.value + ? Icons.voice_over_off + : Icons.voice_over_off_outlined, + color: controller.isTtsInterruptEnabled.value + ? Theme.of(context).colorScheme.primary + : (isDarkMode + ? Colors.grey[400] + : Colors.grey), + size: 14.sp, // 中文注释:进一步缩小图标尺寸 + ), + SizedBox(width: 4.w), + Text( + // 翻译键代表的中文文本内容:语言打断 + 'language_interrupt'.tr, // 语言打断 + style: TextStyle( + fontSize: 11.sp, + height: 1.0, // 中文注释:降低行高,减小垂直占用 + ), + ), + //SizedBox(width: 0.w), // 中文注释:控制文字与开关间距 + Transform.scale( + scale: 0.6, // 中文注释:缩放开关控件,减小视觉尺寸 + child: Switch( + value: controller.isTtsInterruptEnabled.value, + onChanged: (value) => + controller.toggleTtsInterrupt(), + activeColor: + Theme.of(context).colorScheme.primary, + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + // 中文注释:删除不支持的 visualDensity 参数,保留缩放缩小尺寸 + ), + ), + ], + )), + onTap: () => controller.toggleTtsInterrupt(), + ), + ], + )), ), ], );