import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import '../controllers/realtime_controller.dart'; import 'widgets/shader_mic_flow.dart'; class RealtimeView extends GetView { const RealtimeView({Key? key}) : super(key: key); @override Widget build(BuildContext context) { // 设置状态栏为黑色文字 SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.dark, statusBarBrightness: Brightness.light, ), ); return Scaffold( backgroundColor: Colors.white, body: SafeArea( child: Stack( children: [ // 中间音频可视化 Positioned.fill( child: Obx(() => AnimatedAlign( alignment: controller.isSubtitleMode.value ? const Alignment(0, -0.7) // 字幕模式时移到更上方 : const Alignment(0, -0.25), duration: const Duration(milliseconds: 600), curve: Curves.easeInOutCubic, child: LayoutBuilder( builder: (context, constraints) { final targetSize = controller.isSubtitleMode.value ? MediaQuery.of(context).size.width * 0.55 // 字幕模式时较小 : MediaQuery.of(context).size.width * 0.75; // 初始状态更大 return TweenAnimationBuilder( tween: Tween(end: targetSize), duration: const Duration(milliseconds: 600), curve: Curves.easeInOutCubic, builder: (context, animatedSize, child) { return Stack( alignment: Alignment.center, children: [ // 使用Shader动画 Obx(() => ShaderMicFlow( size: animatedSize, amplitude: controller.audioAmplitude.value, )), ], ); }, ); }, ), )), ), // 字幕显示区域 - 只有在字幕模式开启时才显示,位于圆形下方 if (controller.isSubtitleMode.value) Positioned( left: 20, right: 20, top: MediaQuery.of(context).size.height * 0.35, // 在圆形下方 bottom: 160, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Obx(() => AnimatedOpacity( opacity: controller.isSubtitleMode.value ? 1.0 : 0.0, duration: const Duration(milliseconds: 300), child: TweenAnimationBuilder( tween: Tween( begin: 0.0, end: controller.isSubtitleMode.value ? 1.0 : 0.0, ), duration: const Duration(milliseconds: 900), // 延迟显示,让圆圈先移动 curve: Curves.easeInOut, builder: (context, animationValue, child) { return Transform.translate( offset: Offset(0, 20 * (1 - animationValue)), // 从下方滑入 child: Opacity( opacity: animationValue, child: _buildChatSubtitles(), ), ); }, ), )), ), ), // 底部控制按钮 Positioned( bottom: 80, left: 0, right: 0, child: Center( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ // 麦克风按钮 Obx(() => _buildMicButton()), // 字幕按钮 Obx(() => _buildMainButton( icon: controller.isSubtitleMode.value ? Icons.subtitles : Icons.subtitles_outlined, onTap: controller.toggleSubtitleMode, enabled: true, isActive: controller.isSubtitleMode.value, )), // 清空聊天按钮 _buildMainButton( icon: Icons.clear_all, onTap: controller.clearChat, enabled: true, isActive: false, ), // 关闭按钮 _buildMainButton( icon: Icons.close, onTap: controller.closeAndGoBack, enabled: true, isActive: false, ), ], ), ), ), // Home Indicator Positioned( bottom: 8, left: 0, right: 0, child: Center( child: Container( width: 134, height: 5, decoration: BoxDecoration( color: Colors.black, borderRadius: BorderRadius.circular(2.5), ), ), ), ), ], ), ), ); } /// 构建聊天字幕界面 - 上下两行显示用户输入和AI回复 Widget _buildChatSubtitles() { return Obx(() { // 组合用户消息:优先显示实时部分转录,否则显示最终消息 final String displayUserMessage = controller.partialUserMessage.value.isNotEmpty ? controller.partialUserMessage.value : controller.latestUserMessage.value; // 组合AI消息:优先显示流式响应,否则显示最终消息 final String displayAiMessage = controller.streamingAiMessage.value.isNotEmpty ? controller.streamingAiMessage.value : controller.latestAiMessage.value; final hasUserMessage = displayUserMessage.isNotEmpty; final hasAiMessage = displayAiMessage.isNotEmpty; // 如果没有消息,显示空白 if (!hasUserMessage && !hasAiMessage) { return Container(); } return Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, // 让Column紧凑排列 children: [ // 用户输入行 - 蓝色,动态扩展 if (hasUserMessage) Container( width: double.infinity, constraints: const BoxConstraints( minHeight: 30, // 最小1行高度 maxHeight: 104, // 最大4行高度 (26*4) ), padding: const EdgeInsets.symmetric(vertical: 4), child: SingleChildScrollView( reverse: true, child: Text( displayUserMessage, textAlign: TextAlign.center, style: TextStyle( fontSize: 18, color: Colors.blue[600], height: 1.4, fontWeight: FontWeight.w500, letterSpacing: 0.3, ), ), ), ), // 分隔线 - 只有在两个消息都存在时才显示 if (hasUserMessage && hasAiMessage) Container( width: 60, height: 1, margin: const EdgeInsets.symmetric(vertical: 2), // 进一步减少分隔线间距 decoration: BoxDecoration( color: Colors.grey[300], borderRadius: BorderRadius.circular(0.5), ), ), // AI回复行 - 绿色,动态扩展 if (hasAiMessage) Container( width: double.infinity, constraints: const BoxConstraints( minHeight: 30, // 最小1行高度 maxHeight: 104, // 最大4行高度 (26*4) ), padding: const EdgeInsets.symmetric(vertical: 4), child: SingleChildScrollView( reverse: true, child: Text( displayAiMessage, textAlign: TextAlign.center, style: TextStyle( fontSize: 18, color: Colors.green[600], height: 1.4, fontWeight: FontWeight.w500, letterSpacing: 0.3, ), ), ), ), ], ); }); } /// 构建麦克风按钮 - 根据连接状态和录音状态显示不同的状态 Widget _buildMicButton() { // 获取当前状态 final isConnected = controller.isConnected.value; final isConnecting = controller.isConnecting.value; final isListening = controller.isListening.value; // 确定按钮状态 IconData icon; Color? backgroundColor; Color? iconColor; VoidCallback? onTap; if (!isConnected && !isConnecting) { // 未连接状态 - 禁用 icon = Icons.mic_off; backgroundColor = Colors.grey[300]; iconColor = Colors.grey[600]; onTap = null; } else if (isConnecting) { // 连接中状态 - 禁用但显示连接中 icon = Icons.sync; backgroundColor = Colors.orange[200]; iconColor = Colors.orange[700]; onTap = null; } else if (isListening) { // 录音中状态 - 激活状态 icon = Icons.mic; backgroundColor = Theme.of(Get.context!).primaryColor; iconColor = Colors.white; onTap = controller.toggleListening; } else { // 已连接但未录音状态 - 正常状态 icon = Icons.mic_none; backgroundColor = Colors.grey[200]; iconColor = Colors.black; onTap = controller.toggleListening; } return GestureDetector( onTap: onTap, child: Container( width: 60, height: 60, decoration: BoxDecoration( color: backgroundColor, shape: BoxShape.circle, ), child: Icon( icon, color: iconColor, size: 26, ), ), ); } /// 构建主要控制按钮 Widget _buildMainButton({ required IconData icon, VoidCallback? onTap, required bool enabled, required bool isActive, }) { return GestureDetector( onTap: enabled ? onTap : null, child: Container( width: 60, height: 60, decoration: BoxDecoration( color: enabled ? (isActive ? Theme.of(Get.context!).primaryColor : Colors.grey[200]) : Colors.grey[300], shape: BoxShape.circle, ), child: Icon( icon, color: enabled ? (isActive ? Colors.white : Colors.black) : Colors.grey[600], size: 26, ), ), ); } }