From 22e97f572c7ed6caa2a38982c8b1f2e2a640b3f5 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Wed, 30 Jul 2025 20:54:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(agent):=20=E4=BC=98=E5=8C=96=E7=94=B5?= =?UTF-8?q?=E8=AF=9D=E6=A8=A1=E5=BC=8FUI=E5=B9=B6=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=BD=95=E9=9F=B3=E5=88=87=E6=8D=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将声音条组件居中显示 - 重构电话模式控制界面,移除冗余代码 - 增加电话模式专用的录音状态切换功能 - 改进错误处理逻辑,确保状态同步 --- .../agent/controllers/agent_controller.dart | 74 ++++-- lib/modules/agent/views/agent_view.dart | 219 +++--------------- .../agent/widgets/sound_bar_widget.dart | 2 +- 3 files changed, 95 insertions(+), 200 deletions(-) diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index dda9ed2c4..149c05b22 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -153,8 +153,6 @@ class AgentController extends GetxController { _setupMediaSessionCallbacks(); //设置媒体会话回调 _setupTextInputListener(); //设置文本输入监听器 - - startVoiceInput(); //开启语音输入 } @@ -1013,8 +1011,6 @@ class AgentController extends GetxController { } } - - // 开始语音输入 Future startVoiceInput() async { if (isListening.value) return; @@ -1309,29 +1305,71 @@ class AgentController extends GetxController { return; } - // 切换到语音模式并开始语音输入 + // 打电话模式 isTextInputMode.value = false; - // 打电话模式不使用按住说话模式,而是持续语音输入 - //isPushToTalkMode.value = false; - // 开始语音识别 - Logger.i(TAG, '开始打电话模式,开始语音输入'); + Logger.i(TAG, '打电话模式已启动,开始语音输入'); await AgentService.startConversation(); - //} } // 结束打电话模式 void endPhoneCall() async { Logger.i(TAG, '🔥 结束打电话模式'); - // 退出打电话模式 - isPhoneCallMode.value = false; + try { + // 如果正在录音,先停止录音 + if (isListening.value) { + final result = await AgentService.stopConversation(); - // 停止语音输入 - //await stopVoiceInput(); - // 开始语音识别 - await AgentService.stopConversation(); - // 恢复之前的输入模式,而不是强制切换到文字模式 - _restorePreviousInputMode(); + if (result) { + isListening.value = false; + } + } + + // 退出打电话模式 + isPhoneCallMode.value = false; + _restorePreviousInputMode(); + } catch (e) { + Logger.e(TAG, '结束打电话模式失败: $e'); + + // 即使出现异常,也要确保退出打电话模式 + isPhoneCallMode.value = false; + isListening.value = false; + _restorePreviousInputMode(); + } + } + + // 打电话模式专用:切换录音状态 + Future togglePhoneCallRecording() async { + Logger.i(TAG, '🎤 打电话模式切换录音状态'); + // 只有在打电话模式下才能使用此方法 + if (!isPhoneCallMode.value) { + Logger.w(TAG, '不在打电话模式,无法切换录音状态'); + return; + } + + try { + if (isListening.value) { + // 当前正在录音,停止录音 + Logger.i(TAG, '停止打电话模式录音'); + final result = await AgentService.stopConversation(); + + // 手动更新状态,确保UI同步 + if (result) { + isListening.value = false; + } + } else { + // 当前未录音,开始录音 + Logger.i(TAG, '开始打电话模式录音'); + final result = await AgentService.startConversation(); + + // 手动更新状态,确保UI同步 + if (result) { + isListening.value = true; + } + } + } catch (e) { + Logger.e(TAG, '打电话模式录音状态切换失败: $e'); + } } // 耳机唤醒通话模式(专用入口) diff --git a/lib/modules/agent/views/agent_view.dart b/lib/modules/agent/views/agent_view.dart index 3703ec3f4..0ef9a63ac 100644 --- a/lib/modules/agent/views/agent_view.dart +++ b/lib/modules/agent/views/agent_view.dart @@ -922,20 +922,12 @@ class _AgentViewState extends State with WidgetsBindingObserver { child: SafeArea( child: Column( children: [ - // 顶部标题栏 - 保持背景 - // Container( - // color: isDarkMode - // ? Colors.black.withOpacity(0.8) - // : Colors.white.withOpacity(0.8), - // child: _buildPhoneCallHeader(isDarkMode), - // ), - - // 中间音频可视化区域 - 完全透明 + // 中间空白区域 - 完全透明 const Expanded( child: SizedBox(), // 空的占位符,完全透明 ), - // 底部控制按钮 - 保持背景 + // 底部控制按钮 - 包含音频波纹 Container( color: isDarkMode ? Colors.black : Colors.white, child: _buildPhoneCallControls(isDarkMode), @@ -946,185 +938,43 @@ class _AgentViewState extends State with WidgetsBindingObserver { ); } - // 构建打电话模式标题栏 - Widget _buildPhoneCallHeader(bool isDarkMode) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: Row( - children: [ - // 返回按钮 - GestureDetector( - onTap: () => controller.endPhoneCall(), - child: Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: isDarkMode ? Colors.grey[800] : Colors.grey[200], - borderRadius: BorderRadius.circular(20), - ), - child: Icon( - Icons.arrow_back, - color: isDarkMode ? Colors.white : Colors.black, - size: 20, - ), - ), - ), - - const SizedBox(width: 16), - - // 标题 - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'aiVoiceCall'.tr, // AI语音通话 - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: isDarkMode ? Colors.white : Colors.black, - ), - ), - Obx(() { - final status = controller.isListening.value - ? 'inCall'.tr // 正在通话中... - : controller.isSpeaking.value - ? 'aiReplying'.tr // AI正在回复... - : 'waitingToStart'.tr; // 等待开始... - return Text( - status, - style: TextStyle( - fontSize: 14, - color: isDarkMode ? Colors.grey[400] : Colors.grey[600], - ), - ); - }), - ], - ), - ), - ], - ), - ); - } - - // 构建打电话模式音频可视化 - Widget _buildPhoneCallVisualization(bool isDarkMode) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // 音频可视化圆圈 - Obx(() { - final isActive = - controller.isListening.value || controller.isSpeaking.value; - return AnimatedContainer( - duration: const Duration(milliseconds: 300), - width: isActive ? 200 : 160, - height: isActive ? 200 : 160, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: isDarkMode - ? Colors.blue[900]?.withOpacity(0.3) - : Colors.blue[100], - border: Border.all( - color: Colors.blue, - width: isActive ? 3 : 2, - ), - ), - child: Icon( - Icons.phone, - size: isActive ? 80 : 60, - color: Colors.blue, - ), - ); - }), - - const SizedBox(height: 32), - - // 状态文字 - Obx(() { - String statusText = 'clickToStartCall'.tr; // 点击开始通话 - if (controller.isListening.value) { - statusText = 'listeningToYou'.tr; // 正在听您说话... - } else if (controller.isSpeaking.value) { - statusText = 'aiReplying'.tr; // AI正在回复... - } else if (controller.isProcessing.value) { - statusText = 'processing'.tr; // 正在处理... - } - - return Text( - statusText, - style: TextStyle( - fontSize: 16, - color: isDarkMode ? Colors.grey[300] : Colors.grey[700], - fontWeight: FontWeight.w500, - ), - ); - }), - ], - ), - ); - } - // 构建打电话模式控制按钮 Widget _buildPhoneCallControls(bool isDarkMode) { return Container( padding: EdgeInsets.symmetric(horizontal: 32.w, vertical: 24.h), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, // 确保垂直居中对齐 children: [ - // 静音按钮 + // 录音控制按钮(左侧) - 控制录音输入功能 Obx(() => _buildPhoneControlButton( - icon: controller.isTtsEnabled.value - ? Icons.volume_up - : Icons.volume_off, - onTap: () => controller.toggleTts(), + icon: controller.isListening.value + ? Icons.mic // 录音中显示麦克风图标 + : Icons.mic_off, // 未录音显示麦克风关闭图标 + onTap: () => controller.togglePhoneCallRecording(), isDarkMode: isDarkMode, - isActive: controller.isTtsEnabled.value, + isActive: controller.isListening.value, + // backgroundColor: controller.isListening.value + // ? Colors.red // 录音中显示红色背景 + // : Theme.of(context).primaryColor, // 未录音显示主题色 )), - // 主要通话按钮 - Obx(() { - final isActive = controller.isListening.value; - return GestureDetector( - // onTap: () { - // if (isActive) { - // controller.stopVoiceInput(); - // } else { - // controller.startVoiceInput(); - // } - // }, - child: Container( - width: 80.w, - height: 80.h, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: isActive ? Colors.red : Colors.green, - boxShadow: [ - BoxShadow( - color: (isActive ? Colors.red : Colors.green) - .withOpacity(0.3), - blurRadius: 12.w, - spreadRadius: 2.w, - ), - ], - ), - child: Icon( - isActive ? Icons.call_end : Icons.call, - color: Colors.white, - size: 36.sp, - ), - ), - ); - }), + // 音频波纹组件(中间) - 仅显示动态波形效果,无其他UI元素 + Container( + width: 120.w, // 设置固定宽度 + height: 64.h, // 与按钮高度一致 + alignment: Alignment.center, // 确保波纹居中 + child: const AgentSoundBarsWidget(barCount: 12), // 适应宽度的波纹条数 + ), - // 结束通话按钮 + // 关闭打电话模式按钮(右侧) - 退出打电话模式 _buildPhoneControlButton( - icon: Icons.call_end, + icon: Icons.close, onTap: () => controller.endPhoneCall(), isDarkMode: isDarkMode, isActive: false, - backgroundColor: Colors.red, + //backgroundColor: Colors.red, + iconColor: Colors.redAccent, ), ], ), @@ -1138,24 +988,31 @@ class _AgentViewState extends State with WidgetsBindingObserver { required bool isDarkMode, required bool isActive, Color? backgroundColor, + Color? iconColor, }) { return GestureDetector( onTap: onTap, child: Container( - width: 56.w, - height: 56.h, + width: 64.w, // 按钮尺寸 + height: 64.h, decoration: BoxDecoration( shape: BoxShape.circle, color: backgroundColor ?? - (isDarkMode ? Colors.grey[800] : Colors.grey[200]), - border: isActive ? Border.all(color: Colors.blue, width: 2.w) : null, + (isDarkMode ? Colors.grey[800] : Colors.grey[250]), + border: Border.all(color: Colors.blue, width: 2.w), + boxShadow: [ + BoxShadow( + color: (backgroundColor ?? Colors.grey).withValues(alpha: 0.3), + blurRadius: 8.w, + spreadRadius: 1.w, + offset: Offset(0, 2.h), + ), + ], ), child: Icon( icon, - color: backgroundColor != null - ? Colors.white - : (isDarkMode ? Colors.white : Colors.black), - size: 24.sp, + color: iconColor ?? (isDarkMode ? Colors.white : Colors.black), + size: 28.sp, // 图标尺寸 ), ), ); diff --git a/lib/modules/agent/widgets/sound_bar_widget.dart b/lib/modules/agent/widgets/sound_bar_widget.dart index c108bcca2..11fb9ed86 100644 --- a/lib/modules/agent/widgets/sound_bar_widget.dart +++ b/lib/modules/agent/widgets/sound_bar_widget.dart @@ -106,7 +106,7 @@ class SoundBarsWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Row( - mainAxisAlignment: MainAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, children: List.generate(barCount, (index) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 1),