import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'dart:math'; import 'dart:async'; import '../controllers/voice_input_controller.dart'; class VoiceInputPanel extends GetView { const VoiceInputPanel({ Key? key, required this.onRecordingResult, required this.onClose, required this.onRecognizing, }) : super(key: key); final Function(String) onRecordingResult; final VoidCallback onClose; final Function(String) onRecognizing; @override Widget build(BuildContext context) { return Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Color(0xFFFFFFFF), // 纯白色开始 Color(0xFFE8D5F5), // 更深的紫色结束 ], stops: [0.3, 1.0], // 调整颜色分布,让白色占据更多空间 ), ), child: SafeArea( child: Container( height: 120.h, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // 主要内容:麦克风图标、音频波形、关闭按钮 Padding( padding: EdgeInsets.symmetric(horizontal: 20.w), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ // 左侧麦克风图标 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(() => Icon( controller.isRecording.value ? Icons.mic : Icons.mic_none, color: controller.isRecording.value ? Colors.purple : Colors.purple.shade300, size: 28.sp, )), ), ), // 中间音频波形和状态文本 Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ _buildAudioWaveform(), SizedBox(height: 10.h), // 文字提示 - 根据状态显示不同文本 Obx(() => Text( _getStatusText(), style: TextStyle( fontSize: 16.sp, color: Colors.grey.shade700, fontWeight: FontWeight.w500, ), )), ], ), // 右侧关闭按钮 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: onClose, padding: EdgeInsets.zero, ), ), ), ], ), ), // 底部进度条和识别文本 Padding( padding: EdgeInsets.only(top: 15.h), child: Column( children: [ // 移除显示当前识别的文本,因为现在直接显示在聊天窗口中 // 进度条 Container( width: 250.w, height: 4.h, decoration: BoxDecoration( color: Colors.purple.withOpacity(0.2), borderRadius: BorderRadius.circular(2.r), ), child: Obx(() => controller.isRecording.value ? _buildProgressIndicator() : Container() ), ), ], ), ), ], ), ), ), ), ); } // 根据状态返回不同的提示文本 String _getStatusText() { if (controller.isConnecting.value) { return '正在连接语音服务...'; } else if (controller.isMuted.value) { return '语音已静音'; } else if (controller.isRecording.value) { return '正在聆听...'; } else { return '你可以开始说话'; } } // 构建进度指示器 Widget _buildProgressIndicator() { return Container( width: 250.w, height: 4.h, decoration: BoxDecoration( gradient: LinearGradient( colors: [Colors.purple.shade300, Colors.purple.shade100], begin: Alignment.centerLeft, end: Alignment.centerRight, ), borderRadius: BorderRadius.circular(2.r), ), ); } // 检查用户是否正在说话 bool _isUserSpeaking() { // 使用控制器的isUserSpeaking属性 return controller.isUserSpeaking.value; } Widget _buildAudioWaveform() { return Obx(() { final isSpeaking = _isUserSpeaking(); final barCount = 15; // 增加条形数量,使波形更丰富 // 根据控制器状态动态生成波形高度 return Container( width: 180.w, // 增加宽度以容纳更多条形 height: 40.h, // 增加高度以允许更大的波动 child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: List.generate( barCount, (index) { // 创建动态高度效果,模拟声波 double height = controller.isRecording.value ? _getWaveHeight(index, isSpeaking, barCount) // 当正在录音时显示动态高度 : 4.h; // 默认高度 // 根据位置计算不同的动画持续时间,使波形更自然 final duration = Duration( milliseconds: 100 + (index % 3) * 50 ); // 根据是否说话和位置计算颜色 Color barColor; if (controller.isRecording.value) { if (isSpeaking) { // 创建渐变色效果,从中间向两边渐变 final middleIndex = barCount ~/ 2; final distance = (index - middleIndex).abs(); final maxDistance = middleIndex; final colorIntensity = 1.0 - (distance / maxDistance) * 0.5; barColor = Colors.purple.withOpacity(colorIntensity); } else { barColor = Colors.purple.shade200; } } else { barColor = Colors.grey.shade400; } return AnimatedContainer( duration: duration, curve: Curves.easeInOut, // 使用更平滑的动画曲线 width: 3.w, // 减小宽度,使波形更精细 height: height, margin: EdgeInsets.symmetric(horizontal: 1.5.w), // 减小间距 decoration: BoxDecoration( color: barColor, borderRadius: BorderRadius.circular(2.r), // 添加轻微的阴影效果,增强视觉效果 boxShadow: isSpeaking ? [ BoxShadow( color: Colors.purple.withOpacity(0.2), blurRadius: 3, spreadRadius: 0.5, ) ] : null, ), ); }, ), ), ); }); } // 生成动态波形高度 double _getWaveHeight(int index, bool isSpeaking, int barCount) { // 基础高度 double baseHeight = 6.h; // 获取当前时间毫秒数 final DateTime now = DateTime.now(); final int milliseconds = now.millisecondsSinceEpoch; // 计算中心位置 final middleIndex = barCount ~/ 2; final distanceFromMiddle = (index - middleIndex).abs(); // 使用多个正弦波叠加,创建更自然的波形 // 每个条形使用不同的频率和相位,创建不规则波形 double wave1 = sin((milliseconds / 200) + (index * 0.7)) * 3.h; double wave2 = sin((milliseconds / 350) + (index * 0.5)) * 2.h; double wave3 = sin((milliseconds / 500) + (index * 0.3)) * 1.5.h; // 基于距离中心的位置计算高度衰减 double positionFactor = 1.0 - (distanceFromMiddle / middleIndex) * 0.5; if (isSpeaking) { // 用户说话时的波形 // 创建一个随时间变化的振幅因子,使波形看起来更有活力 double timeFactor = 0.7 + sin(milliseconds / 1000) * 0.3; // 中心位置的条形高度最高 double centerBoost = pow(positionFactor, 1.5) * 10.h; // 添加随机因素,使波形更自然 double randomFactor = (Random().nextDouble() * 2 - 1) * 2.h; // 根据时间添加脉动效果 double pulseEffect = sin(milliseconds / 600) * 3.h * positionFactor; // 组合所有效果 return baseHeight + (wave1 + wave2 + wave3) * timeFactor + centerBoost + randomFactor + pulseEffect; } else { // 用户不说话时的波形 - 更平静,但仍有微小变化 double idleWave = (wave1 + wave2) * 0.5 * positionFactor; return baseHeight + idleWave; } } }