import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'dart:math'; 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 GetView { ChatView({Key? key}) : super(key: key); // 添加ScrollController用于控制滚动 final ScrollController _scrollController = ScrollController(); @override Widget build(BuildContext context) { // 监听消息列表变化,自动滚动到底部 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, ); } }); }); 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.recognizedText.isEmpty ? '你可以开始说话' : '正在聆听...', 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() { final barCount = 23; // 条形数量 return SizedBox( width: 220.w, height: 50.h, child: Obx(() { final isSpeaking = controller.recognizedText.isNotEmpty; final isRec = controller.isListening.value; return Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: List.generate( barCount, (index) { // 创建动态高度效果 double height = isRec ? _getWaveHeight(index, isSpeaking, barCount).clamp(1.h, 22.5.h) : 2.h; // 动画持续时间 final duration = Duration( milliseconds: isSpeaking ? 60 + (index % 4) * 20 : 150 + (index % 3) * 50 ); // 颜色计算 Color barColor; if (isRec) { if (isSpeaking) { final middleIndex = barCount ~/ 2; final distance = (index - middleIndex).abs(); final maxDistance = middleIndex; final colorIntensity = 1.0 - (distance / maxDistance) * 0.2; barColor = index % 3 == 0 ? AppColors.primary : AppColors.primary.withOpacity(colorIntensity); } else { barColor = AppColors.primary.withOpacity(0.3); } } else { barColor = Colors.grey.shade400; } return SizedBox( width: 5.5.w, child: Center( child: AnimatedContainer( duration: duration, curve: isSpeaking ? Curves.elasticOut : Curves.easeInOut, width: isSpeaking ? 2.8.w : 2.5.w, height: height, margin: EdgeInsets.symmetric(horizontal: 1.2.w), decoration: BoxDecoration( color: barColor, borderRadius: BorderRadius.circular(2.r), boxShadow: isSpeaking ? [ BoxShadow( color: AppColors.primary.withOpacity(0.4), blurRadius: 5, spreadRadius: 1.2, ) ] : null, ), ), ), ); }, ), ); }), ); } // 生成动态波形高度 double _getWaveHeight(int index, bool isSpeaking, int barCount) { try { // 基础高度 double baseHeight = isSpeaking ? 5.h : 3.h; // 获取当前时间毫秒数 final DateTime now = DateTime.now(); final int milliseconds = now.millisecondsSinceEpoch; // 计算中心位置 final middleIndex = barCount ~/ 2; final distanceFromMiddle = (index - middleIndex).abs(); // 使用多个正弦波叠加 double freqMultiplier = isSpeaking ? 2.2 : 1.0; double ampMultiplier = isSpeaking ? 1.8 : 1.0; double wave1 = sin((milliseconds / 150) * freqMultiplier + (index * 0.8)) * 2.h * ampMultiplier; double wave2 = sin((milliseconds / 280) * freqMultiplier + (index * 0.5)) * 1.5.h * ampMultiplier; double wave3 = sin((milliseconds / 340) * freqMultiplier + (index * 0.3)) * 1.25.h * ampMultiplier; double wave4 = cos((milliseconds / 220) * freqMultiplier + (index * 0.7)) * 1.h * ampMultiplier; // 基于距离中心的位置计算高度衰减 double positionFactor = isSpeaking ? 1.0 - (distanceFromMiddle / middleIndex) * 0.2 : 1.0 - (distanceFromMiddle / middleIndex) * 0.5; if (isSpeaking) { // 用户说话时的波形 double timeFactor = 0.9 + sin(milliseconds / 600) * 0.5; // 增强中心突出效果 double centerBoost = pow(positionFactor, 1.2) * 10.h; // 增加随机性 double randomFactor = (Random().nextDouble() * 4 - 2) * 1.75.h; // 增强脉冲效果 double pulseEffect = sin(milliseconds / 300) * 2.5.h * positionFactor; // 添加额外的随机波动 double voiceVariation = sin(milliseconds / 80 + index * 1.2) * 1.5.h * Random().nextDouble(); // 添加爆发效果 double burstEffect = 0; if (Random().nextDouble() > 0.97) { burstEffect = 5.h * positionFactor; } return baseHeight + (wave1 + wave2 + wave3 + wave4) * timeFactor + centerBoost + randomFactor + pulseEffect + voiceVariation + burstEffect; } else { // 用户不说话时的波形 double idleWave = (wave1 + wave2) * 0.35 * positionFactor; double gentlePulse = sin(milliseconds / 800) * 1.h * positionFactor; return baseHeight + idleWave + gentlePulse; } } catch (e) { // 发生错误时返回默认高度 return 2.h; } } }