From 1f28db2401a5627e5f25e918ab83b0b22a418cb9 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Mon, 21 Apr 2025 19:29:47 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8C=89=E4=BD=8F=E8=AF=B4?= =?UTF-8?q?=E8=AF=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/controllers/chat_controller.dart | 99 +++++++-- lib/modules/chat/views/chat_view.dart | 209 +++++++++--------- 2 files changed, 194 insertions(+), 114 deletions(-) diff --git a/lib/modules/chat/controllers/chat_controller.dart b/lib/modules/chat/controllers/chat_controller.dart index 74ea42005..50b3e89ba 100644 --- a/lib/modules/chat/controllers/chat_controller.dart +++ b/lib/modules/chat/controllers/chat_controller.dart @@ -86,10 +86,16 @@ class ChatController extends GetxController { // 记录最后一次活动时间 DateTime _lastActivityTime = DateTime.now(); + // 音乐播放 final player = AudioPlayer(); final RxString audiourl = ''.obs; final RxBool isPlay = false.obs; + // 按住说话、文字输入切换 + final RxBool isTextInput = false.obs; + final RxBool isHoldSpeak = false.obs; + String finalResult = ''; + @override Future onInit() async { super.onInit(); @@ -570,6 +576,53 @@ class ChatController extends GetxController { } } + // 按下开始说话 + void talkLongPress() async { + if (isHoldSpeak.value) return; + + _voiceInteractionService.pauseVoiceInteraction(); + try { + final recognitionStream = await _asrService.startContinuousRecognition(); + + isHoldSpeak.value = true; + recognizedText.value = ''; + finalResult = ''; + + // 监听识别事件 + _recognitionSubscription = recognitionStream.listen( + _handleRecognitionEvent, + onError: _handleRecognitionError, + ); + } catch (e) { + Logger.error('启动语音识别失败', e); + isHoldSpeak.value = false; + } + } + + // 松开停止说话 + void talkLongPressUp() async { + if (!isHoldSpeak.value) return; + try { + await _asrService.stopContinuousRecognition(); + _recognitionSubscription?.cancel(); + _recognitionSubscription = null; + + final intermediateIndex = + messages.indexWhere((m) => m.isIntermediate && m.role == 'user'); + if (intermediateIndex >= 0) { + messages.removeAt(intermediateIndex); + } + + sendMessage(finalResult); + + recognizedText.value = ''; + finalResult = ''; + isHoldSpeak.value = false; + } catch (e) { + Logger.error('停止语音识别失败', e); + } + } + // 开始语音识别 Future startVoiceRecognition() async { if (isVoiceRecognitionActive.value) return; @@ -682,7 +735,7 @@ class ChatController extends GetxController { if (intermediateIndex >= 0) { // 更新已有的中间状态消息 messages[intermediateIndex] = messages[intermediateIndex].copyWith( - content: text, + content: isVoiceRecognitionActive.value ? text : finalResult + text, ); messages.refresh(); } else { @@ -703,26 +756,44 @@ class ChatController extends GetxController { if (text.isEmpty) return; Logger.info('处理语音识别最终结果: $text'); - recognizedText.value = text; // 更新活动时间 _updateActivityTime(); - // 查找并移除用户的中间状态消息 - final intermediateIndex = - messages.indexWhere((m) => m.isIntermediate && m.role == 'user'); - if (intermediateIndex >= 0) { - messages.removeAt(intermediateIndex); - } + if (isVoiceRecognitionActive.value) { + // 一直语音说话 - // 发送识别到的文本 - sendMessage(text); + recognizedText.value = text; + // 查找并移除用户的中间状态消息 + final intermediateIndex = + messages.indexWhere((m) => m.isIntermediate && m.role == 'user'); + if (intermediateIndex >= 0) { + messages.removeAt(intermediateIndex); + } + + // 发送识别到的文本 + sendMessage(text); + // 重置识别文本,但不关闭语音识别框 + recognizedText.value = ''; + // 重置语音检测状态 + isSpeechDetected.value = false; + } else { + // 按住说话 - // 重置识别文本,但不关闭语音识别框 - recognizedText.value = ''; + finalResult += text; + recognizedText.value = finalResult; - // 重置语音检测状态 - isSpeechDetected.value = false; + final intermediateIndex = + messages.indexWhere((m) => m.isIntermediate && m.role == 'user'); + + if (intermediateIndex >= 0) { + // 更新已有的中间状态消息 + messages[intermediateIndex] = messages[intermediateIndex].copyWith( + content: finalResult, + ); + messages.refresh(); + } + } } // 处理语音识别事件错误 diff --git a/lib/modules/chat/views/chat_view.dart b/lib/modules/chat/views/chat_view.dart index 0e8f53e56..39020c0b0 100644 --- a/lib/modules/chat/views/chat_view.dart +++ b/lib/modules/chat/views/chat_view.dart @@ -113,6 +113,13 @@ class ChatView extends GetView { ], ), actions: [ + IconButton( + icon: Icon( + Icons.phone, + color: isDarkMode ? Colors.white : Colors.black87, + ), + onPressed: controller.startVoiceRecognition, + ), // TTS开关 Obx(() => IconButton( icon: Icon( @@ -389,120 +396,122 @@ class ChatView extends GetView { Widget _buildTextInputPanel( bool isDarkMode, TextEditingController textController) { return Container( - padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), + height: 48.w, + margin: EdgeInsets.only(left: 16.w, right: 16.w, top: 10.w, bottom: 20.w), + clipBehavior: Clip.hardEdge, decoration: BoxDecoration( - color: isDarkMode ? AppColors.cardBackground : Colors.white, + color: controller.isHoldSpeak.value + ? AppColors.primary + : isDarkMode + ? AppColors.cardBackground + : Colors.white, + borderRadius: BorderRadius.circular(12.w), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 3, - offset: Offset(0, -1), + offset: const Offset(0, -1), ), ], ), - child: SafeArea( - child: Row( - children: [ - // 语音识别按钮 - Container( - width: 40.w, - height: 40.w, - decoration: BoxDecoration( - color: isDarkMode - ? Colors.white.withOpacity(0.1) - : Colors.grey[100], - shape: BoxShape.circle, - ), - child: IconButton( - icon: Icon( - Icons.mic_none, - color: isDarkMode ? Colors.white : Colors.grey[700], - size: 20.r, - ), - padding: EdgeInsets.zero, - onPressed: controller.startVoiceRecognition, - ), + child: Row( + children: [ + Expanded( + child: Obx( + () => controller.isTextInput.value + ? Center( + child: TextField( + controller: textController, + style: TextStyle( + fontSize: 15.sp, + color: isDarkMode ? Colors.white : Colors.black87, + ), + textInputAction: TextInputAction.send, + decoration: InputDecoration( + hintText: 'inputMessage'.tr, // 输入消息... + hintStyle: TextStyle(color: Colors.grey[500]), + filled: false, + border: InputBorder.none, + focusedBorder: InputBorder.none, + contentPadding: EdgeInsets.only(left: 10.w), + ), + onSubmitted: (value) { + if (value.trim().isNotEmpty) { + controller.sendMessage(value); + textController.clear(); + controller.inputText.value = ''; // 清空输入框 + } + }, + ), + ) + : Listener( + onPointerDown: (event) { + controller.talkLongPress(); + }, + onPointerUp: (event) { + controller.talkLongPressUp(); + }, + child: Container( + padding: EdgeInsets.only( + left: controller.isHoldSpeak.value ? 0 : 46.w), + color: Colors.transparent, + child: controller.isHoldSpeak.value + ? Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(40, (int index) { + return Container( + width: 3.w, + height: 10.w, + margin: + EdgeInsets.symmetric(horizontal: 2.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(3.r), + ), + ); + }), + ) + : Center( + child: Text( + '按住说话', + style: TextStyle( + fontSize: 16.sp, + color: isDarkMode + ? Colors.white + : Colors.black87, + ), + ), + ), + ), + ), ), - - SizedBox(width: 8.w), - - // 输入框 - Expanded( - child: TextField( - controller: textController, - decoration: InputDecoration( - hintText: 'inputMessage'.tr, // 输入消息... - hintStyle: TextStyle( - color: isDarkMode ? Colors.grey[500] : Colors.grey[500]), - filled: true, - fillColor: isDarkMode - ? Colors.white.withOpacity(0.1) - : Colors.grey[100], - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(20.r), - borderSide: BorderSide.none, - ), - contentPadding: EdgeInsets.symmetric( - horizontal: 16.w, - vertical: 10.h, + ), + if (!controller.isHoldSpeak.value) + GestureDetector( + onTap: () { + controller.isTextInput.value = !controller.isTextInput.value; + }, + child: Container( + width: 30.w, + height: 30.w, + margin: EdgeInsets.all(8.w), + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30.r), + border: Border.all( + color: isDarkMode ? Colors.white : Colors.black87, + width: 2, ), ), - maxLines: null, - textInputAction: TextInputAction.newline, - style: TextStyle( - fontSize: 15.sp, - color: isDarkMode ? Colors.white : Colors.black87, + child: Obx( + () => Icon( + controller.isTextInput.value ? Icons.mic : Icons.keyboard, + size: 20.w, + ), ), - onChanged: (value) => controller.inputText.value = value, - onSubmitted: (value) { - if (value.trim().isNotEmpty) { - controller.sendMessage(value); - textController.clear(); - controller.inputText.value = ''; // 清空输入框 - } - }, ), ), - - SizedBox(width: 8.w), - - // 发送按钮 - Obx(() { - final hasText = controller.inputText.value.isNotEmpty; - return Container( - width: 40.w, - height: 40.w, - decoration: BoxDecoration( - color: hasText - ? AppColors.primary - : isDarkMode - ? Colors.white.withOpacity(0.1) - : Colors.grey[200], - shape: BoxShape.circle, - ), - child: IconButton( - icon: Icon( - Icons.send, - color: hasText - ? Colors.white - : isDarkMode - ? Colors.white.withOpacity(0.5) - : Colors.grey[400], - size: 18.r, - ), - padding: EdgeInsets.zero, - onPressed: hasText - ? () { - controller.sendMessage(controller.inputText.value); - textController.clear(); - controller.inputText.value = ''; // 清空输入框 - } - : null, - ), - ); - }), - ], - ), + ], ), ); }