import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'dart:math'; import 'package:audio_waveforms/audio_waveforms.dart'; import '../controllers/chat_controller.dart'; import '../models/message_model.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/widgets/message_bubble.dart'; class ChatView extends StatefulWidget { const ChatView({Key? key}) : super(key: key); @override State createState() => _ChatViewState(); } class _ChatViewState extends State with TickerProviderStateMixin { // 获取控制器 ChatController get controller => Get.find(); // 添加ScrollController用于控制滚动 final ScrollController _scrollController = ScrollController(); // 添加一个动画控制器,用于波形动画 late final AnimationController _waveAnimationController; // 添加录音控制器,用于获取麦克风数据 late final RecorderController _recorderController; @override void initState() { super.initState(); // 初始化动画控制器 _waveAnimationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 1000), )..repeat(); // 初始化录音控制器 _recorderController = RecorderController() ..androidEncoder = AndroidEncoder.aac ..androidOutputFormat = AndroidOutputFormat.mpeg4 ..iosEncoder = IosEncoder.kAudioFormatMPEG4AAC ..sampleRate = 44100 ..bitRate = 128000 ..updateFrequency = const Duration(milliseconds: 50); // 更新频率 // 初始化录音控制器 _initializeRecorder(); // 监听消息列表变化,自动滚动到底部 ever(controller.messages, (_) { // 使用Future.delayed确保在UI更新后滚动 Future.delayed(Duration(milliseconds: 50), () { if (_scrollController.hasClients && _scrollController.position.maxScrollExtent > 0 && (_scrollController.position.maxScrollExtent - _scrollController.offset) < 200) { _scrollController.animateTo( _scrollController.position.maxScrollExtent, duration: Duration(milliseconds: 100), curve: Curves.easeOut, ); } }); }); // 监听语音识别状态变化 ever(controller.isVoiceRecognitionActive, (isActive) { if (isActive) { // 开始录音以获取波形数据 _recorderController.record(); } else { // 停止录音 _recorderController.stop(); } }); } // 初始化录音控制器 Future _initializeRecorder() async { try { // 请求录音权限 await _recorderController.checkPermission(); // 如果当前正在进行语音识别,开始录音 if (controller.isVoiceRecognitionActive.value) { await _recorderController.record(); } } catch (e) { print('初始化录音控制器失败: $e'); } } @override void dispose() { _scrollController.dispose(); _waveAnimationController.dispose(); _recorderController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, // 设置Scaffold背景为白色 appBar: AppBar( leading: IconButton( icon: Icon(Icons.arrow_back_ios, color: Colors.black), onPressed: () => Get.back(), ), title: Row( children: [ CircleAvatar( backgroundImage: AssetImage(controller.agent.avatarUrl), radius: 18.r, ), SizedBox(width: 10.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( controller.agent.name, style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.bold, color: Colors.black, ), ), Text( controller.agent.description.length > 20 ? '${controller.agent.description.substring(0, 20)}...' : controller.agent.description, style: TextStyle( fontSize: 12.sp, color: Colors.grey, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ), ], ), actions: [ // TTS开关 Obx(() => IconButton( icon: Icon( controller.isTtsEnabled.value ? Icons.volume_up : Icons.volume_off, color: controller.isTtsEnabled.value ? AppColors.primary : Colors.grey, ), onPressed: controller.toggleTts, )), // 更多选项 PopupMenuButton( icon: Icon(Icons.more_vert, color: Colors.black), itemBuilder: (context) => [ PopupMenuItem( value: 'clear', child: Row( children: [ Icon(Icons.delete_outline, color: Colors.red), SizedBox(width: 8.w), Text('清空对话'), ], ), ), ], onSelected: (value) { if (value == 'clear') { controller.clearChat(); } }, ), ], elevation: 0, backgroundColor: Colors.white, ), body: Container( color: Colors.white, // 确保body背景也是白色 child: Column( children: [ // 消息列表 Expanded( child: Obx(() => ListView.builder( controller: controller.scrollController, padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h), itemCount: controller.messages.length, reverse: false, physics: const AlwaysScrollableScrollPhysics(), cacheExtent: 1000, // 增加缓存范围,减少重建 itemBuilder: (context, index) { final message = controller.messages[index]; return MessageBubble( key: ObjectKey(message), // 使用ObjectKey而不是ValueKey message: message, isUser: message.role == 'user', ); }, )), ), // 输入区域 - 根据当前模式显示不同的输入面板 Obx(() => controller.isVoiceRecognitionActive.value ? _buildVoiceInputPanel() : _buildTextInputPanel()), ], ), ), ); } // 构建文本输入面板 Widget _buildTextInputPanel() { // 创建一个TextEditingController来控制输入框 final TextEditingController textController = TextEditingController(text: controller.inputText.value); // 监听输入变化 textController.addListener(() { controller.inputText.value = textController.text; }); return Container( padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 5, offset: Offset(0, -1), ), ], ), child: Row( children: [ // 语音输入按钮 IconButton( icon: Icon( Icons.mic_none, color: Colors.grey, ), onPressed: controller.startVoiceRecognition, ), // 文本输入框 Expanded( child: TextField( controller: textController, decoration: InputDecoration( hintText: '发消息...', border: OutlineInputBorder( borderRadius: BorderRadius.circular(20.r), borderSide: BorderSide.none, ), filled: true, fillColor: Colors.grey[200], contentPadding: EdgeInsets.symmetric( horizontal: 16.w, vertical: 8.h, ), ), maxLines: null, textInputAction: TextInputAction.send, onSubmitted: (value) { if (value.trim().isNotEmpty) { controller.sendMessage(value); textController.clear(); } }, ), ), // 发送按钮 Obx(() => IconButton( icon: Icon( Icons.send, color: controller.inputText.value.trim().isEmpty ? Colors.grey : AppColors.primary, ), onPressed: controller.inputText.value.trim().isEmpty ? null : () { controller.sendMessage(controller.inputText.value); textController.clear(); }, )), ], ), ); } // 构建语音输入面板 Widget _buildVoiceInputPanel() { final screenWidth = Get.width; return Container( width: screenWidth, decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Color(0xFFFFFFFF), // 纯白色开始 Color(0xFFE8D5F5), // 更深的紫色结束 ], stops: [0.3, 1.0], // 调整颜色分布,让白色占据更多空间 ), ), child: SafeArea( child: SizedBox( height: 100.h, child: Center( child: Padding( padding: EdgeInsets.symmetric(horizontal: 20.w), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ // 左侧麦克风按钮 _buildMicrophoneButton(), // 中间音频波形和状态文本 Expanded( child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ // 音频波形 _buildAudioWaveform(), SizedBox(height: 10.h), // 状态文本 Obx(() => Text( controller.isSpeechDetected.value ? '正在聆听...' : '你可以开始说话', style: TextStyle( fontSize: 16.sp, color: Colors.grey.shade700, fontWeight: FontWeight.w500, ), )), ], ), ), // 右侧关闭按钮 _buildCloseButton(), ], ), ), ), ), ), ); } // 麦克风按钮 Widget _buildMicrophoneButton() { return Container( width: 60.w, height: 60.w, decoration: BoxDecoration( color: Colors.white, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, spreadRadius: 1, ), ], ), child: Center( child: Obx(() => IconButton( icon: Icon( controller.isListening.value ? Icons.mic : Icons.mic_none, color: controller.isListening.value ? AppColors.primary : Colors.grey.shade400, size: 28.sp, ), onPressed: controller.stopVoiceRecognition, padding: EdgeInsets.zero, )), ), ); } // 关闭按钮 Widget _buildCloseButton() { return Container( width: 60.w, height: 60.w, decoration: BoxDecoration( color: Colors.white, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, spreadRadius: 1, ), ], ), child: Center( child: IconButton( icon: Icon( Icons.close, color: Colors.grey.shade600, size: 28.sp, ), onPressed: controller.stopVoiceRecognition, padding: EdgeInsets.zero, ), ), ); } // 音频波形 Widget _buildAudioWaveform() { return SizedBox( width: 220.w, height: 50.h, child: Obx(() { final isSpeaking = controller.isSpeechDetected.value; final isRec = controller.isListening.value; if (!isRec) { // 如果没有在录音,显示静态波形 return _buildStaticWaveform(); } // 使用AudioWaveforms组件显示实时波形 return AudioWaveforms( enableGesture: false, size: Size(220.w, 50.h), recorderController: _recorderController, waveStyle: WaveStyle( waveColor: isSpeaking ? AppColors.primary : AppColors.primary.withOpacity(0.3), extendWaveform: true, showMiddleLine: false, spacing: 4.0, waveThickness: 3.0, showDurationLabel: false, scaleFactor: 80, // 调整波形高度 ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12.r), color: Colors.transparent, ), padding: EdgeInsets.symmetric(horizontal: 8.w), margin: EdgeInsets.only(bottom: 5.h), ); }), ); } // 静态波形(当没有录音时显示) Widget _buildStaticWaveform() { final barCount = 23; // 条形数量 return Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: List.generate( barCount, (index) { // 静态高度 final height = 2.h; return SizedBox( width: 5.5.w, child: Center( child: Container( width: 2.5.w, height: height, margin: EdgeInsets.symmetric(horizontal: 1.2.w), decoration: BoxDecoration( color: Colors.grey.shade400, borderRadius: BorderRadius.circular(2.r), ), ), ), ); }, ), ); } }