From c1aac0ece5d1ab06e12e1b2a677f2fc46b22e88b Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Sat, 28 Jun 2025 15:21:46 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E9=80=9A=E8=AF=9D):=20=E6=B7=BB=E5=8A=A0A?= =?UTF-8?q?I=E8=AF=AD=E9=9F=B3=E9=80=9A=E8=AF=9D=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在AgentController中添加电话模式状态管理 - 实现开始/结束电话模式逻辑,包括权限检查和语音模式切换 - 在AgentView中添加电话模式UI,包括覆盖层、状态显示和控制按钮 - 调整"+选项"高度以容纳电话按钮 --- .../agent/controllers/agent_controller.dart | 51 ++++ lib/modules/agent/views/agent_view.dart | 260 +++++++++++++++++- 2 files changed, 304 insertions(+), 7 deletions(-) diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 7de28787d..03bce6c62 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -78,6 +78,9 @@ class AgentController extends GetxController { // "+"号选项展开状态 final isPlusOptionsExpanded = false.obs; // 是否展开"+"号选项 + // 打电话模式状态 + final isPhoneCallMode = false.obs; // 是否处于打电话模式 + // 键盘状态 final isKeyboardVisible = false.obs; // 键盘是否可见 @@ -1024,6 +1027,54 @@ class AgentController extends GetxController { useImagePrompt('请翻译这张图片中的文字'); } + // 开始打电话模式 + void startPhoneCall() async { + Logger.i(TAG, '🔥 开始打电话模式'); + + // 收缩"+"选项 + collapsePlusOptions(); + + // 进入打电话模式 + isPhoneCallMode.value = true; + + // 如果当前是文本模式,切换到语音模式 + if (isTextInputMode.value) { + // 检查麦克风权限 + final hasPermission = await PermissionUtil.instance.requestPermission( + permissionType: Permission.microphone, + permissionName: '麦克风', + explanationText: '打电话功能需要访问麦克风进行语音通话。', + permanentDenialText: '您已永久拒绝麦克风权限,请前往系统设置开启权限。', + settingsButtonText: '去设置', + cancelButtonText: '取消', + ); + + if (!hasPermission) { + // 权限未获取,退出打电话模式 + isPhoneCallMode.value = false; + return; + } + + // 切换到语音模式并开始语音输入 + isTextInputMode.value = false; + await startVoiceInput(); + } + } + + // 结束打电话模式 + void endPhoneCall() async { + Logger.i(TAG, '🔥 结束打电话模式'); + + // 退出打电话模式 + isPhoneCallMode.value = false; + + // 停止语音输入 + await stopVoiceInput(); + + // 切换回文本模式 + isTextInputMode.value = true; + } + // 获取音乐播放状态 Future musicStatus() async { try { diff --git a/lib/modules/agent/views/agent_view.dart b/lib/modules/agent/views/agent_view.dart index da6ba9952..9e24b7ef6 100644 --- a/lib/modules/agent/views/agent_view.dart +++ b/lib/modules/agent/views/agent_view.dart @@ -183,10 +183,13 @@ class _AgentViewState extends State with WidgetsBindingObserver { ), ], ), - body: Column( + body: Stack( children: [ - // 聊天内容 - Expanded( + // 主要聊天界面 + Column( + children: [ + // 聊天内容 + Expanded( child: Obx( () => Align( alignment: Alignment.topCenter, @@ -644,7 +647,7 @@ class _AgentViewState extends State with WidgetsBindingObserver { return AnimatedContainer( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, - height: isExpanded ? 100.h : 0, // 调整高度以适应按钮尺寸 + height: isExpanded ? 120.h : 0, // 调整高度以适应按钮尺寸 child: isExpanded ? Container( margin: EdgeInsets.only(top: 8.h), @@ -685,14 +688,14 @@ class _AgentViewState extends State with WidgetsBindingObserver { // }, // isDarkMode: isDarkMode, // ), - // 通话按钮(预留位置) + // 通话按钮 _buildPlusOptionButton( context: context, icon: Icons.phone, label: '打电话', onTap: () { - //controller.collapsePlusOptions(); - // TODO: 实现通话功能 + // 启动打电话模式 + controller.startPhoneCall(); }, isDarkMode: isDarkMode, ), @@ -707,6 +710,16 @@ class _AgentViewState extends State with WidgetsBindingObserver { ], ), ), + ], + ), + + // 打电话模式覆盖层 + Obx(() { + if (controller.isPhoneCallMode.value) { + return _buildPhoneCallOverlay(context, isDarkMode); + } + return const SizedBox.shrink(); + }), ], ), ); @@ -808,6 +821,239 @@ class _AgentViewState extends State with WidgetsBindingObserver { ), ); } + + // 构建打电话模式覆盖层 + Widget _buildPhoneCallOverlay(BuildContext context, bool isDarkMode) { + return Container( + color: isDarkMode ? Colors.black : Colors.white, + child: SafeArea( + child: Column( + children: [ + // 顶部标题栏 + _buildPhoneCallHeader(isDarkMode), + + // 中间音频可视化区域 + Expanded( + child: _buildPhoneCallVisualization(isDarkMode), + ), + + // 底部控制按钮 + _buildPhoneCallControls(isDarkMode), + ], + ), + ), + ); + } + + // 构建打电话模式标题栏 + 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( + 'AI语音通话', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: isDarkMode ? Colors.white : Colors.black, + ), + ), + Obx(() { + final status = controller.isListening.value + ? '正在通话中...' + : controller.isSpeaking.value + ? 'AI正在回复...' + : '等待开始...'; + 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 = '点击开始通话'; + if (controller.isListening.value) { + statusText = '正在听您说话...'; + } else if (controller.isSpeaking.value) { + statusText = 'AI正在回复...'; + } else if (controller.isProcessing.value) { + statusText = '正在处理...'; + } + + 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: const EdgeInsets.symmetric(horizontal: 32, vertical: 24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + // 静音按钮 + Obx(() => _buildPhoneControlButton( + icon: controller.isTtsEnabled.value ? Icons.volume_up : Icons.volume_off, + onTap: () => controller.toggleTts(), + isDarkMode: isDarkMode, + isActive: controller.isTtsEnabled.value, + )), + + // 主要通话按钮 + Obx(() { + final isActive = controller.isListening.value; + return GestureDetector( + onTap: () { + if (isActive) { + controller.stopVoiceInput(); + } else { + controller.startVoiceInput(); + } + }, + child: Container( + width: 80, + height: 80, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: isActive ? Colors.red : Colors.green, + boxShadow: [ + BoxShadow( + color: (isActive ? Colors.red : Colors.green).withOpacity(0.3), + blurRadius: 12, + spreadRadius: 2, + ), + ], + ), + child: Icon( + isActive ? Icons.call_end : Icons.call, + color: Colors.white, + size: 36, + ), + ), + ); + }), + + // 结束通话按钮 + _buildPhoneControlButton( + icon: Icons.call_end, + onTap: () => controller.endPhoneCall(), + isDarkMode: isDarkMode, + isActive: false, + backgroundColor: Colors.red, + ), + ], + ), + ); + } + + // 构建打电话控制按钮 + Widget _buildPhoneControlButton({ + required IconData icon, + required VoidCallback onTap, + required bool isDarkMode, + required bool isActive, + Color? backgroundColor, + }) { + return GestureDetector( + onTap: onTap, + child: Container( + width: 56, + height: 56, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: backgroundColor ?? (isDarkMode ? Colors.grey[800] : Colors.grey[200]), + border: isActive ? Border.all(color: Colors.blue, width: 2) : null, + ), + child: Icon( + icon, + color: backgroundColor != null + ? Colors.white + : (isDarkMode ? Colors.white : Colors.black), + size: 24, + ), + ), + ); + } }