You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

302 lines
11 KiB

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<VoiceInputController> {
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;
}
}
}