import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import '../controllers/chat_controller.dart'; import '../models/message_model.dart'; import '../../../data/services/volcano_tts_service.dart'; import '../widgets/voice_input_panel.dart'; class ChatView extends GetView { const ChatView({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return WillPopScope( onWillPop: () async { final tts = Get.find(); await tts.stop(); return true; }, child: Scaffold( backgroundColor: Colors.white, appBar: _buildAppBar(), body: SafeArea( child: Column( children: [ Expanded( child: _buildMessageList(), ), _buildInputSection(), ], ), ), ), ); } PreferredSizeWidget _buildAppBar() { return AppBar( backgroundColor: Colors.white, elevation: 0, leading: IconButton( icon: Icon( Icons.arrow_back_ios, color: Colors.black, size: 20.sp, ), onPressed: () async { final tts = Get.find(); await tts.stop(); Get.back(); }, ), title: Column( children: [ Text( controller.agentName, style: TextStyle( fontSize: 17.sp, fontWeight: FontWeight.w600, color: Colors.black, ), ), if (controller.agentSubtitle != null) Text( controller.agentSubtitle!, style: TextStyle( fontSize: 12.sp, color: Colors.grey[600], ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), centerTitle: true, actions: [ IconButton( icon: Obx(() => Icon( Get.find().isEnabled.value ? Icons.volume_up : Icons.volume_off, color: Colors.black, size: 24.sp, )), onPressed: controller.toggleTTS, ), IconButton( icon: Icon( Icons.more_horiz, color: Colors.black, size: 24.sp, ), onPressed: () => _showMoreOptions(), ), ], ); } Widget _buildMessageList() { return Stack( children: [ Obx(() => ListView.builder( controller: controller.scrollController, padding: EdgeInsets.only( top: 16.h, bottom: controller.isVoiceInputVisible.value ? 140.h : 80.h, ), itemCount: controller.messages.length, itemBuilder: (context, index) => _buildMessage(controller.messages[index]), physics: const AlwaysScrollableScrollPhysics(), clipBehavior: Clip.none, keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, )), Obx(() { if (!controller.isLoading.value) return const SizedBox.shrink(); return Positioned( bottom: controller.isVoiceInputVisible.value ? 150.h : 90.h, left: 0, right: 0, child: Container( padding: EdgeInsets.symmetric(vertical: 8.h), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: 16.w, height: 16.w, child: const CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Color(0xFF4A6FE5)), ), ), SizedBox(width: 8.w), Text( '正在思考...', style: TextStyle( fontSize: 14.sp, color: Colors.grey[600], ), ), ], ), ), ); }), ], ); } Widget _buildInputSection() { return Obx(() { if (controller.isVoiceInputVisible.value) { return VoiceInputPanel( onRecordingResult: controller.handleVoiceResult, onClose: controller.stopVoiceInput, onRecognizing: controller.handleRecognizing, ); } return _buildTextInput(); }); } Widget _buildTextInput() { return Container( padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), decoration: BoxDecoration( color: Colors.white, border: Border( top: BorderSide( color: Colors.grey[200]!, width: 1, ), ), ), child: Row( children: [ Expanded( child: Container( padding: EdgeInsets.symmetric(horizontal: 16.w), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(24.r), ), child: Row( children: [ IconButton( icon: Icon( Icons.mic, color: Colors.grey[600], size: 24.sp, ), onPressed: controller.startVoiceInput, ), Expanded( child: TextField( controller: controller.messageController, decoration: const InputDecoration( hintText: '输入消息...', border: InputBorder.none, ), maxLines: null, keyboardType: TextInputType.multiline, textInputAction: TextInputAction.send, onSubmitted: (_) => controller.sendMessage(), ), ), IconButton( icon: const Icon(Icons.send), color: const Color(0xFF4A6FE5), onPressed: controller.sendMessage, ), ], ), ), ), ], ), ); } void _showMoreOptions() { Get.bottomSheet( Container( padding: EdgeInsets.all(20.w), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only( topLeft: Radius.circular(20.r), topRight: Radius.circular(20.r), ), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: const Icon(Icons.delete_outline, color: Colors.red), title: const Text('清空对话'), onTap: () { Get.back(); controller.clearChat(); }, ), ], ), ), ); } Widget _buildMessage(Message message) { final isUser = message.role == 'user'; return Padding( padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), child: Row( mainAxisAlignment: isUser ? MainAxisAlignment.end : MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ if (!isUser) ...[ CircleAvatar( radius: 20.r, backgroundImage: AssetImage(controller.agentAvatar), backgroundColor: Colors.transparent, ), SizedBox(width: 12.w), ], Flexible( child: Container( padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), decoration: BoxDecoration( color: isUser ? const Color(0xFF4A6FE5) : const Color(0xFFF5F5F5), borderRadius: BorderRadius.only( topLeft: Radius.circular(isUser ? 18.r : 4.r), topRight: Radius.circular(isUser ? 4.r : 18.r), bottomLeft: Radius.circular(18.r), bottomRight: Radius.circular(18.r), ), ), child: Text( message.content, style: TextStyle( fontSize: 16.sp, color: isUser ? Colors.white : Colors.black87, height: 1.5, ), ), ), ), if (isUser) ...[ SizedBox(width: 12.w), CircleAvatar( radius: 20.r, backgroundColor: const Color(0xFFEEF4FF), child: Icon( Icons.person, color: const Color(0xFF4A6FE5), size: 24.sp, ), ), ], ], ), ); } }