import 'dart:io'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../controllers/agent_controller.dart'; import 'message_bubble.dart'; class AgentView extends StatelessWidget { const AgentView({Key? key}) : super(key: key); @override Widget build(BuildContext context) { final controller = Get.find(); return Scaffold( appBar: AppBar( title: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(20), ), child: const Icon( Icons.smart_toy_outlined, color: Colors.white, ), ), const SizedBox(width: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'DeepVoice AI', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), Row( children: [ Container( width: 8, height: 8, decoration: BoxDecoration( color: Colors.green, borderRadius: BorderRadius.circular(4), ), ), const SizedBox(width: 4), Text( '在线', style: TextStyle( fontSize: 12, color: Colors.green, ), ), ], ), ], ), ], ), actions: [ PopupMenuButton( icon: const Icon(Icons.more_vert), itemBuilder: (context) => [ PopupMenuItem( child: const Text('清除聊天记录'), onTap: () => controller.clearChat(), ), ], ), ], ), body: Column( children: [ // 聊天内容 Expanded( child: Obx( () => ListView.builder( controller: controller.scrollController, reverse: false, padding: const EdgeInsets.all(16), itemCount: controller.messages.length, itemBuilder: (context, index) { final message = controller.messages[index]; return MessageBubble( isUser: message.isUser, message: message.text, timestamp: message.timestamp, isRecognizing: message.isRecognizing, hasImage: message.hasImage, imagePath: message.imagePath, mate: message.meta, ); }, ), ), ), // 输入区域 Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Theme.of(context).scaffoldBackgroundColor, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 5, offset: const Offset(0, -1), ), ], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // 图片预览区域 - 仅在图片输入模式下显示 Obx(() { if (controller.isImageInputActive.value && controller.selectedImagePath.value != null) { return Column( mainAxisSize: MainAxisSize.min, children: [ // 图片预览和关闭按钮 Stack( children: [ ClipRRect( borderRadius: BorderRadius.circular(12), child: Image.file( File(controller.selectedImagePath.value!), height: 150, width: double.infinity, fit: BoxFit.cover, ), ), Positioned( top: 8, right: 8, child: GestureDetector( onTap: () => controller.clearImageInput(), child: Container( padding: EdgeInsets.all(4), decoration: BoxDecoration( color: Colors.black.withOpacity(0.6), shape: BoxShape.circle, ), child: Icon( Icons.close, color: Colors.white, size: 16, ), ), ), ), ], ), // 预设提示选项 SizedBox(height: 8), SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ _buildPromptChip( context: context, label: '总结图片内容', onTap: () => controller.summarizeImage(), ), SizedBox(width: 8), _buildPromptChip( context: context, label: '抽取图像文字', onTap: () => controller.extractTextFromImage(), ), SizedBox(width: 8), _buildPromptChip( context: context, label: '翻译图像文字', onTap: () => controller.translateImageText(), ), ], ), ), SizedBox(height: 12), ], ); } else { return SizedBox.shrink(); } }), // 输入控件行 Row( children: [ // 键盘/语音切换按钮 Obx(() { final isTextMode = controller.isTextInputMode.value; return GestureDetector( onTap: () { controller.toggleInputMode(); }, child: Container( width: 48, height: 48, decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(24), ), child: Icon( isTextMode ? Icons.mic_none : Icons.keyboard, color: Colors.grey[600], ), ), ); }), const SizedBox(width: 8), // 文本输入框或语音波形图 Expanded( child: Obx(() { final isTextMode = controller.isTextInputMode.value; if (isTextMode) { // 文本输入模式 - 注意观察图片模式时的高度变化 return Container( padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(24), ), child: Row( children: [ Expanded( child: Obx(() { // 图片模式下使用更高的输入框 final isImageActive = controller.isImageInputActive.value; return TextField( controller: controller.textController, maxLines: isImageActive ? 2 : 1, minLines: isImageActive ? 2 : 1, decoration: InputDecoration( hintText: isImageActive ? '添加对图片的问题或描述...' : '输入消息...', border: InputBorder.none, contentPadding: EdgeInsets.symmetric( vertical: isImageActive ? 12 : 0), ), onSubmitted: (_) => controller.sendTextMessage(), ); }), ), GestureDetector( onTap: () => controller.sendTextMessage(), child: Icon( Icons.send, color: Theme.of(context).primaryColor, ), ), ], ), ); } else { // 语音输入模式 - 始终显示波形图 return GestureDetector( onTap: () { if (controller.isListening.value) { controller.stopVoiceInput(); } else { controller.startVoiceInput(); } }, child: Container( height: 48, padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(24), ), child: Row( children: [ // 语音波形图 - 始终显示 Expanded( child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ ...List.generate(10, (index) { return _buildSoundBar(index); }), const Spacer(), // "正在聆听"文字 Obx(() { final isListening = controller.isListening.value; return Row( children: [ Icon( Icons.mic, size: 14, color: isListening ? Theme.of(context) .primaryColor : Colors.grey, ), const SizedBox(width: 4), Text( '正在聆听', style: TextStyle( fontSize: 12, color: isListening ? Theme.of(context) .primaryColor : Colors.grey, ), ), ], ); }), ], ), ), ], ), ), ); } }), ), const SizedBox(width: 8), // 图片选择按钮 Container( width: 48, height: 48, decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(24), ), child: PopupMenuButton( icon: Icon( Icons.add_photo_alternate_outlined, color: Colors.grey[600], ), padding: EdgeInsets.zero, onSelected: (value) async { if (value == 'camera') { await controller.takePhoto(); } else if (value == 'gallery') { await controller.pickImage(); } }, itemBuilder: (context) => [ PopupMenuItem( value: 'camera', child: Row( children: [ Icon(Icons.camera_alt, color: Theme.of(context).primaryColor), SizedBox(width: 8), Text('拍照'), ], ), ), PopupMenuItem( value: 'gallery', child: Row( children: [ Icon(Icons.photo_library, color: Theme.of(context).primaryColor), SizedBox(width: 8), Text('从相册选择'), ], ), ), ], ), ), ], ), ], ), ), ], ), ); } // 构建提示选项芯片 Widget _buildPromptChip({ required BuildContext context, required String label, required VoidCallback onTap, }) { return GestureDetector( onTap: onTap, child: Container( padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: Theme.of(context).primaryColor.withOpacity(0.1), borderRadius: BorderRadius.circular(16), border: Border.all( color: Theme.of(context).primaryColor.withOpacity(0.3), ), ), child: Text( label, style: TextStyle( fontSize: 13, color: Theme.of(context).primaryColor, fontWeight: FontWeight.w500, ), ), ), ); } // 构建声音柱 Widget _buildSoundBar(int index) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 1), child: Obx(() => _SoundBar( delay: index * 0.1, isActive: Get.find().isListening.value, )), ); } } // 声音柱动画组件 class _SoundBar extends StatefulWidget { final double delay; final bool isActive; const _SoundBar({ required this.delay, required this.isActive, }); @override _SoundBarState createState() => _SoundBarState(); } class _SoundBarState extends State<_SoundBar> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _animation; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 500), ); _animation = Tween(begin: 3, end: 15).animate( CurvedAnimation( parent: _controller, curve: Curves.easeInOut, ), )..addListener(() { setState(() {}); }); _startOrStopAnimation(); } @override void didUpdateWidget(_SoundBar oldWidget) { super.didUpdateWidget(oldWidget); if (widget.isActive != oldWidget.isActive) { _startOrStopAnimation(); } } void _startOrStopAnimation() { if (widget.isActive) { Future.delayed(Duration(milliseconds: (widget.delay * 1000).toInt()), () { if (mounted && widget.isActive) { _controller.repeat(reverse: true); } }); } else { _controller.reset(); // 设置为小高度的静态状态 _controller.animateTo(0.1, duration: Duration(milliseconds: 300)); } } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Container( width: 2, height: widget.isActive ? _animation.value : 3, decoration: BoxDecoration( color: widget.isActive ? Theme.of(context).primaryColor : Colors.grey[400], borderRadius: BorderRadius.circular(1), ), ); } }