From b59ac61cf7201ebdc091a1a1144c1704f7258304 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Fri, 18 Apr 2025 12:06:51 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=9C=B0=E5=9B=BE?= =?UTF-8?q?=E5=8D=A1=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/views/chat_navigation_card.dart | 44 +++++++------------ 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/lib/modules/chat/views/chat_navigation_card.dart b/lib/modules/chat/views/chat_navigation_card.dart index 72ac80077..ee9085dcd 100644 --- a/lib/modules/chat/views/chat_navigation_card.dart +++ b/lib/modules/chat/views/chat_navigation_card.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'dart:math'; import 'dart:math' as math; -import 'package:amap_flutter/amap_flutter.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:http/http.dart' as http; @@ -25,10 +24,7 @@ class _ChatNavigationCardState extends State { LatLng startPoint = LatLng(116.397428, 39.90923); // 起点坐标 LatLng endPoint = LatLng(116.397026, 39.903303); // 终点坐标 - final _amapFlutter = AmapFlutter(); - StreamSubscription? _locationStream; - - String _mapUrl = ''; + late Future _mapUrl; @override void initState() { @@ -36,30 +32,18 @@ class _ChatNavigationCardState extends State { _getLocation(); } - @override - void dispose() { - _amapFlutter.dispose(); - _locationStream?.cancel(); - super.dispose(); - } - - void _getLocation() async { + void _getLocation() { + List startPointList = widget.data?['location'].split(','); List endPointList = widget.data?['destination'].split(','); + startPoint = LatLng( + double.parse(startPointList[0]), + double.parse(startPointList[1]), + ); endPoint = LatLng( double.parse(endPointList[0]), double.parse(endPointList[1]), ); - await _amapFlutter.initLocation(); - _locationStream = _amapFlutter.locationStream().listen((data) async { - startPoint = LatLng( - data['longitude'], - data['latitude'], - ); - _mapUrl = await _getMapUrl(); - _amapFlutter.stopLocation(); - setState(() {}); - }); - _amapFlutter.startLocation(); + _mapUrl = _getMapUrl(); } Future _getMapUrl() async { @@ -193,9 +177,15 @@ class _ChatNavigationCardState extends State { Widget build(BuildContext context) { return Column( children: [ - _mapUrl.isNotEmpty - ? Image.network(_mapUrl) - : const CircularProgressIndicator(), + FutureBuilder( + future: _mapUrl, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasData) { + return Image.network(snapshot.data); + } + return const CircularProgressIndicator(); + }, + ), 10.verticalSpace, Text( widget.content, 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 2/3] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8C=89=E4=BD=8F?= =?UTF-8?q?=E8=AF=B4=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, - ), - ); - }), - ], - ), + ], ), ); } From e3bdaa3e564c9dd353ef9e40199d0d967bda7348 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Wed, 23 Apr 2025 14:16:36 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=83=8C=E6=99=AF?= =?UTF-8?q?=E5=8A=A8=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/controllers/chat_controller.dart | 44 ++- .../views/animated_gradient_background.dart | 65 ++++ lib/modules/chat/views/chat_view.dart | 329 +++++++++--------- 3 files changed, 258 insertions(+), 180 deletions(-) create mode 100644 lib/modules/chat/views/animated_gradient_background.dart diff --git a/lib/modules/chat/controllers/chat_controller.dart b/lib/modules/chat/controllers/chat_controller.dart index 50b3e89ba..4ce3bf80b 100644 --- a/lib/modules/chat/controllers/chat_controller.dart +++ b/lib/modules/chat/controllers/chat_controller.dart @@ -96,6 +96,9 @@ class ChatController extends GetxController { final RxBool isHoldSpeak = false.obs; String finalResult = ''; + // 按住说话防抖 + Timer? _talkDebounceTimer; + @override Future onInit() async { super.onInit(); @@ -289,6 +292,7 @@ class ChatController extends GetxController { _voiceInteractionSubscription?.cancel(); _autoStopTimer?.cancel(); _scrollDebounceTimer?.cancel(); // 清理防抖计时器 + _talkDebounceTimer?.cancel(); _amapFlutter.dispose(); _locationStream?.cancel(); @@ -578,12 +582,15 @@ class ChatController extends GetxController { // 按下开始说话 void talkLongPress() async { + _talkDebounceTimer?.cancel(); if (isHoldSpeak.value) return; _voiceInteractionService.pauseVoiceInteraction(); try { final recognitionStream = await _asrService.startContinuousRecognition(); + await Future.delayed(const Duration(milliseconds: 150)); + isHoldSpeak.value = true; recognizedText.value = ''; finalResult = ''; @@ -601,26 +608,29 @@ class ChatController extends GetxController { // 松开停止说话 void talkLongPressUp() async { - if (!isHoldSpeak.value) return; - try { - await _asrService.stopContinuousRecognition(); - _recognitionSubscription?.cancel(); - _recognitionSubscription = null; + _talkDebounceTimer?.cancel(); + _talkDebounceTimer = Timer(const Duration(milliseconds: 200), () 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); - } + final intermediateIndex = + messages.indexWhere((m) => m.isIntermediate && m.role == 'user'); + if (intermediateIndex >= 0) { + messages.removeAt(intermediateIndex); + } - sendMessage(finalResult); + sendMessage(finalResult); - recognizedText.value = ''; - finalResult = ''; - isHoldSpeak.value = false; - } catch (e) { - Logger.error('停止语音识别失败', e); - } + recognizedText.value = ''; + finalResult = ''; + isHoldSpeak.value = false; + } catch (e) { + Logger.error('停止语音识别失败', e); + } + }); } // 开始语音识别 diff --git a/lib/modules/chat/views/animated_gradient_background.dart b/lib/modules/chat/views/animated_gradient_background.dart new file mode 100644 index 000000000..181295cea --- /dev/null +++ b/lib/modules/chat/views/animated_gradient_background.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; + +class AnimatedGradientBackground extends StatefulWidget { + const AnimatedGradientBackground({super.key}); + + @override + State createState() => + _AnimatedGradientBackgroundState(); +} + +class _AnimatedGradientBackgroundState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _beginAnimation; + late Animation _endAnimation; + + final Color color1 = const Color(0xFFfbcfdd); + final Color color2 = const Color(0xFFe2e9fb); + + @override + void initState() { + super.initState(); + + _controller = AnimationController( + vsync: this, + duration: const Duration(seconds: 10), + )..repeat(reverse: true); + + // 创建起始点动画 + _beginAnimation = AlignmentTween( + begin: Alignment.topCenter, + end: Alignment.bottomLeft, + ).animate(_controller); + + // 创建结束点动画 + _endAnimation = AlignmentTween( + begin: Alignment.bottomCenter, + end: Alignment.topRight, + ).animate(_controller); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [color1, color2], + begin: _beginAnimation.value!, + end: _endAnimation.value!, + ), + ), + ); + }, + ); + } +} diff --git a/lib/modules/chat/views/chat_view.dart b/lib/modules/chat/views/chat_view.dart index 39020c0b0..ed904d4e6 100644 --- a/lib/modules/chat/views/chat_view.dart +++ b/lib/modules/chat/views/chat_view.dart @@ -5,6 +5,7 @@ import 'dart:math'; import '../controllers/chat_controller.dart'; import '../models/message_model.dart'; import '../../../core/theme/app_colors.dart'; +import 'animated_gradient_background.dart'; import 'chat_music_card.dart'; import 'chat_navigation_card.dart'; import 'weather_day_card.dart'; @@ -16,10 +17,17 @@ class ChatView extends GetView { Widget build(BuildContext context) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; - return Scaffold( - backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.grey[50], - appBar: _buildAppBar(isDarkMode), - body: _buildBody(isDarkMode), + return Obx( + () => Scaffold( + backgroundColor: + isDarkMode ? AppColors.darkBackground : Colors.grey[50], + appBar: controller.isVoiceRecognitionActive.value + ? null + : _buildAppBar(isDarkMode), + body: controller.isVoiceRecognitionActive.value + ? _buildVoiceInputPanel(isDarkMode) + : _buildBody(isDarkMode), + ), ); } @@ -47,7 +55,7 @@ class ChatView extends GetView { BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 3, - offset: Offset(0, 1), + offset: const Offset(0, 1), ), ], ), @@ -92,7 +100,7 @@ class ChatView extends GetView { Container( width: 6.r, height: 6.r, - decoration: BoxDecoration( + decoration: const BoxDecoration( color: Colors.green, shape: BoxShape.circle, ), @@ -164,7 +172,7 @@ class ChatView extends GetView { value: 'clear', child: Row( children: [ - Icon(Icons.delete_outline, color: Colors.red), + const Icon(Icons.delete_outline, color: Colors.red), SizedBox(width: 8.w), Text( 'clearChatHistory'.tr, // 清空聊天记录 @@ -205,7 +213,7 @@ class ChatView extends GetView { ), // 底部输入区域 - _buildInputArea(isDarkMode), + _buildTextInputPanel(isDarkMode), ], ); } @@ -377,24 +385,11 @@ class ChatView extends GetView { return box; } - // 构建输入区域 - Widget _buildInputArea(bool isDarkMode) { + // 构建文本输入面板 + Widget _buildTextInputPanel(bool isDarkMode) { final textController = TextEditingController(text: controller.inputText.value); - // 检查是否处于语音识别模式 - return Obx(() { - if (controller.isVoiceRecognitionActive.value) { - return _buildVoiceInputPanel(isDarkMode); - } else { - return _buildTextInputPanel(isDarkMode, textController); - } - }); - } - - // 构建文本输入面板 - Widget _buildTextInputPanel( - bool isDarkMode, TextEditingController textController) { return Container( height: 48.w, margin: EdgeInsets.only(left: 16.w, right: 16.w, top: 10.w, bottom: 20.w), @@ -521,158 +516,166 @@ class ChatView extends GetView { return Container( decoration: BoxDecoration( color: isDarkMode ? AppColors.cardBackground : Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 3, - offset: Offset(0, -1), - ), - ], ), child: SafeArea( - child: SizedBox( - height: 80.h, - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 20.w), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - // 语音录制按钮 - Container( - width: 60.r, - height: 60.r, - decoration: BoxDecoration( - color: isDarkMode - ? Colors.white.withOpacity(0.1) - : Colors.white, - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 8, - spreadRadius: 1, + child: Column( + children: [ + Row( + children: [], + ), + Expanded( + child: AnimatedGradientBackground(), + ), + SizedBox( + height: 80.h, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 20.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // 语音录制按钮 + Container( + width: 60.r, + height: 60.r, + decoration: BoxDecoration( + color: isDarkMode + ? Colors.white.withOpacity(0.1) + : Colors.white, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 8, + spreadRadius: 1, + ), + ], ), - ], - ), - child: Center( - child: Obx(() => Icon( - controller.isListening.value - ? Icons.mic - : Icons.mic_none, - color: controller.isListening.value - ? AppColors.primary - : Colors.grey.shade400, - size: 28.r, - )), - ), - ), + child: Center( + child: Obx(() => Icon( + controller.isListening.value + ? Icons.mic + : Icons.mic_none, + color: controller.isListening.value + ? AppColors.primary + : Colors.grey.shade400, + size: 28.r, + )), + ), + ), - // 中间音频波形和状态文本 - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // 音频波形区域 - Container( - height: 50.h, - child: Obx(() { - final isSpeaking = controller.isSpeechDetected.value; - return Center( - child: Container( - width: 220.w, - height: 30.h, - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: List.generate( - 23, - (index) { - // 随机高度模拟波形 - double height = isSpeaking - ? (Random().nextDouble() * 20.0 + 5.0).h - : 2.h; + // 中间音频波形和状态文本 + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // 音频波形区域 + Container( + height: 50.h, + child: Obx(() { + final isSpeaking = + controller.isSpeechDetected.value; + return Center( + child: Container( + width: 220.w, + height: 30.h, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + 23, + (index) { + // 随机高度模拟波形 + double height = isSpeaking + ? (Random().nextDouble() * 20.0 + + 5.0) + .h + : 2.h; - return SizedBox( - width: 6.w, - child: Center( - child: AnimatedContainer( - duration: Duration(milliseconds: 500), - width: 2.5.w, - height: height, - margin: EdgeInsets.symmetric( - horizontal: 1.5.w), - decoration: BoxDecoration( - color: isSpeaking - ? AppColors.primary - : isDarkMode - ? Colors.grey.shade600 - : Colors.grey.shade400, - borderRadius: - BorderRadius.circular(2.r), + return SizedBox( + width: 6.w, + child: Center( + child: AnimatedContainer( + duration: const Duration( + milliseconds: 500), + width: 2.5.w, + height: height, + margin: EdgeInsets.symmetric( + horizontal: 1.5.w), + decoration: BoxDecoration( + color: isSpeaking + ? AppColors.primary + : isDarkMode + ? Colors.grey.shade600 + : Colors.grey.shade400, + borderRadius: + BorderRadius.circular(2.r), + ), + ), ), - ), - ), - ); - }, + ); + }, + ), + ), ), - ), - ), - ); - }), - ), - SizedBox(height: 8.h), + ); + }), + ), - // 语音状态文本 - Obx(() => Text( - controller.isSpeechDetected.value - ? 'imListening'.tr // 正在聆听... - : 'canStartTalking'.tr, // 你可以开始说话 - style: TextStyle( - fontSize: 14.sp, - color: isDarkMode - ? Colors.grey[400] - : Colors.grey[700], - fontWeight: FontWeight.w500, - ), - )), - ], - ), - ), + // 语音状态文本 + Obx(() => Text( + controller.isSpeechDetected.value + ? 'imListening'.tr // 正在聆听... + : 'canStartTalking'.tr, // 你可以开始说话 + style: TextStyle( + fontSize: 14.sp, + color: isDarkMode + ? Colors.grey[400] + : Colors.grey[700], + fontWeight: FontWeight.w500, + ), + )), + ], + ), + ), - // 关闭按钮 - Container( - width: 60.r, - height: 60.r, - decoration: BoxDecoration( - color: isDarkMode - ? Colors.white.withOpacity(0.1) - : Colors.white, - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 8, - spreadRadius: 1, + // 关闭按钮 + Container( + width: 60.r, + height: 60.r, + decoration: BoxDecoration( + color: isDarkMode + ? Colors.white.withOpacity(0.1) + : Colors.white, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 8, + spreadRadius: 1, + ), + ], ), - ], - ), - child: Center( - child: IconButton( - icon: Icon( - Icons.close, - color: isDarkMode ? Colors.white : Colors.grey.shade600, - size: 28.r, + child: Center( + child: IconButton( + icon: Icon( + Icons.close, + color: isDarkMode + ? Colors.white + : Colors.grey.shade600, + size: 28.r, + ), + padding: EdgeInsets.zero, + onPressed: controller.stopVoiceRecognition, + ), ), - padding: EdgeInsets.zero, - onPressed: controller.stopVoiceRecognition, ), - ), + ], ), - ], + ), ), - ), + ], ), ), ); @@ -692,7 +695,7 @@ class ChatView extends GetView { BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 2, - offset: Offset(0, 1), + offset: const Offset(0, 1), ), ], ), @@ -730,7 +733,7 @@ class ChatView extends GetView { // 构建加载动画点 Widget _buildLoadingDot(int index, bool isDarkMode) { return AnimatedBuilder( - animation: AlwaysStoppedAnimation(0), + animation: const AlwaysStoppedAnimation(0), builder: (context, child) { return Container( margin: EdgeInsets.symmetric(horizontal: 2.w),