import 'package:flutter/material.dart'; import '../models/ai_tutor.dart'; import '../models/conversation.dart'; /// AI对话页面 class AIConversationScreen extends StatefulWidget { final AITutor tutor; const AIConversationScreen({ super.key, required this.tutor, }); @override State createState() => _AIConversationScreenState(); } class _AIConversationScreenState extends State { final TextEditingController _messageController = TextEditingController(); final ScrollController _scrollController = ScrollController(); final List _messages = []; bool _isTyping = false; bool _isRecording = false; @override void initState() { super.initState(); _initializeConversation(); } void _initializeConversation() { // 添加导师的欢迎消息 final welcomeMessage = ConversationMessage( id: 'welcome_${DateTime.now().millisecondsSinceEpoch}', content: _getWelcomeMessage(), type: MessageType.ai, timestamp: DateTime.now(), ); setState(() { _messages.add(welcomeMessage); }); } String _getWelcomeMessage() { switch (widget.tutor.type) { case TutorType.business: return '${widget.tutor.introduction}\n\n让我们开始商务英语对话练习吧!你可以告诉我你的工作背景,或者我们可以模拟一个商务场景。'; case TutorType.daily: return '${widget.tutor.introduction}\n\n今天想聊什么呢?我们可以谈论天气、兴趣爱好,或者你今天做了什么有趣的事情!'; case TutorType.travel: return '${widget.tutor.introduction}\n\n准备好开始我们的旅行英语之旅了吗?告诉我你想去哪里旅行,或者我们可以模拟在机场、酒店的场景!'; case TutorType.academic: return '${widget.tutor.introduction}\n\n欢迎来到学术英语课堂!我们可以讨论你的研究领域,或者练习学术演讲技巧。'; } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF5F5F5), appBar: AppBar( backgroundColor: widget.tutor.type.color, elevation: 0, leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () => Navigator.of(context).pop(), ), title: Row( children: [ Text( widget.tutor.avatar, style: const TextStyle(fontSize: 24), ), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.tutor.name, style: const TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold, ), ), Text( widget.tutor.type.displayName, style: const TextStyle( color: Colors.white70, fontSize: 12, ), ), ], ), ), ], ), actions: [ IconButton( icon: const Icon(Icons.info_outline, color: Colors.white), onPressed: _showTutorInfo, ), ], ), body: Column( children: [ Expanded( child: ListView.builder( controller: _scrollController, padding: const EdgeInsets.all(16), itemCount: _messages.length + (_isTyping ? 1 : 0), itemBuilder: (context, index) { if (index == _messages.length && _isTyping) { return _buildTypingIndicator(); } return _buildMessageBubble(_messages[index]); }, ), ), _buildInputArea(), ], ), ); } Widget _buildMessageBubble(ConversationMessage message) { final isUser = message.type == MessageType.user; return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( mainAxisAlignment: isUser ? MainAxisAlignment.end : MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ if (!isUser) ...[ CircleAvatar( radius: 16, backgroundColor: widget.tutor.type.color.withOpacity(0.1), child: Text( widget.tutor.avatar, style: const TextStyle(fontSize: 16), ), ), const SizedBox(width: 8), ], Flexible( child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: isUser ? widget.tutor.type.color : Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 5, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( message.content, style: TextStyle( color: isUser ? Colors.white : Colors.black87, fontSize: 14, ), ), const SizedBox(height: 4), Text( _formatTime(message.timestamp), style: TextStyle( color: isUser ? Colors.white70 : Colors.grey, fontSize: 10, ), ), ], ), ), ), if (isUser) ...[ const SizedBox(width: 8), CircleAvatar( radius: 16, backgroundColor: Colors.blue.withOpacity(0.1), child: const Icon( Icons.person, size: 16, color: Colors.blue, ), ), ], ], ), ); } Widget _buildTypingIndicator() { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( children: [ CircleAvatar( radius: 16, backgroundColor: widget.tutor.type.color.withOpacity(0.1), child: Text( widget.tutor.avatar, style: const TextStyle(fontSize: 16), ), ), const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 5, offset: const Offset(0, 2), ), ], ), child: Row( mainAxisSize: MainAxisSize.min, children: [ _buildDot(0), const SizedBox(width: 4), _buildDot(1), const SizedBox(width: 4), _buildDot(2), ], ), ), ], ), ); } Widget _buildDot(int index) { return AnimatedContainer( duration: Duration(milliseconds: 600 + (index * 200)), width: 6, height: 6, decoration: BoxDecoration( color: widget.tutor.type.color.withOpacity(0.6), shape: BoxShape.circle, ), ); } Widget _buildInputArea() { return Container( padding: const EdgeInsets.all(16), decoration: const BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black12, blurRadius: 5, offset: Offset(0, -2), ), ], ), child: Row( children: [ GestureDetector( onTapDown: (_) => _startRecording(), onTapUp: (_) => _stopRecording(), onTapCancel: () => _stopRecording(), child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: _isRecording ? Colors.red : widget.tutor.type.color, shape: BoxShape.circle, ), child: Icon( _isRecording ? Icons.stop : Icons.mic, color: Colors.white, size: 20, ), ), ), const SizedBox(width: 12), Expanded( child: TextField( controller: _messageController, decoration: InputDecoration( hintText: '输入消息...', border: OutlineInputBorder( borderRadius: BorderRadius.circular(25), borderSide: BorderSide.none, ), filled: true, fillColor: Colors.grey[100], contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), ), onSubmitted: (_) => _sendMessage(), ), ), const SizedBox(width: 12), GestureDetector( onTap: _sendMessage, child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: widget.tutor.type.color, shape: BoxShape.circle, ), child: const Icon( Icons.send, color: Colors.white, size: 20, ), ), ), ], ), ); } void _sendMessage() { final text = _messageController.text.trim(); if (text.isEmpty) return; // 添加用户消息 final userMessage = ConversationMessage( id: 'user_${DateTime.now().millisecondsSinceEpoch}', content: text, type: MessageType.user, timestamp: DateTime.now(), ); setState(() { _messages.add(userMessage); _isTyping = true; }); _messageController.clear(); _scrollToBottom(); // 生成AI回复 _generateAIResponse(text); } void _generateAIResponse(String userMessage) { // 模拟AI思考时间 Future.delayed(const Duration(milliseconds: 1500), () { final aiResponse = _getAIResponse(userMessage); final aiMessage = ConversationMessage( id: 'ai_${DateTime.now().millisecondsSinceEpoch}', content: aiResponse, type: MessageType.ai, timestamp: DateTime.now(), ); setState(() { _isTyping = false; _messages.add(aiMessage); }); _scrollToBottom(); }); } String _getAIResponse(String userMessage) { final message = userMessage.toLowerCase(); // 根据导师类型和用户消息生成相应回复 switch (widget.tutor.type) { case TutorType.business: return _getBusinessResponse(message); case TutorType.daily: return _getDailyResponse(message); case TutorType.travel: return _getTravelResponse(message); case TutorType.academic: return _getAcademicResponse(message); } } String _getBusinessResponse(String message) { if (message.contains('meeting') || message.contains('会议')) { return "Great! Let's discuss meeting preparation. What type of meeting are you attending? Is it a client presentation, team meeting, or board meeting?"; } else if (message.contains('presentation') || message.contains('演讲')) { return "Presentations are crucial in business. What's your presentation topic? I can help you structure your content and practice key phrases."; } else if (message.contains('email') || message.contains('邮件')) { return "Business emails require professional tone. Are you writing to a client, colleague, or supervisor? What's the main purpose of your email?"; } else if (message.contains('hello') || message.contains('hi') || message.contains('你好')) { return "Hello! I'm your business English tutor. I can help you with presentations, meetings, negotiations, and professional communication. What would you like to practice today?"; } else { return "That's an interesting point. In business context, we should consider the professional implications. Could you elaborate on your specific business scenario?"; } } String _getDailyResponse(String message) { if (message.contains('weather') || message.contains('天气')) { return "The weather is a great conversation starter! How's the weather where you are? You can say 'It's sunny/rainy/cloudy today' or 'What a beautiful day!'"; } else if (message.contains('food') || message.contains('吃') || message.contains('饭')) { return "Food is always a fun topic! What's your favorite cuisine? You can practice ordering food or describing flavors. Try saying 'I'd like to order...' or 'This tastes delicious!'"; } else if (message.contains('hobby') || message.contains('爱好')) { return "Hobbies are personal and interesting! What do you enjoy doing in your free time? You can say 'I enjoy...' or 'My hobby is...' or 'In my spare time, I like to...'"; } else if (message.contains('hello') || message.contains('hi') || message.contains('你好')) { return "Hi there! I'm here to help you with everyday English conversations. We can talk about weather, food, hobbies, shopping, or any daily activities. What interests you?"; } else { return "That's interesting! In daily conversations, we often share personal experiences. Can you tell me more about that? It's great practice for natural English!"; } } String _getTravelResponse(String message) { if (message.contains('airport') || message.contains('flight') || message.contains('机场')) { return "Airport conversations are essential for travelers! Are you checking in, going through security, or asking for directions? Try phrases like 'Where is gate B12?' or 'Is this flight delayed?'"; } else if (message.contains('hotel') || message.contains('酒店')) { return "Hotel interactions are important! Are you checking in, asking about amenities, or reporting an issue? Practice saying 'I have a reservation under...' or 'Could you help me with...'"; } else if (message.contains('restaurant') || message.contains('餐厅')) { return "Dining out while traveling is fun! Are you making a reservation, ordering food, or asking about local specialties? Try 'Table for two, please' or 'What do you recommend?'"; } else if (message.contains('direction') || message.contains('路')) { return "Getting directions is crucial when traveling! Practice asking 'How do I get to...?' or 'Is it walking distance?' You can also say 'Could you show me on the map?'"; } else if (message.contains('hello') || message.contains('hi') || message.contains('你好')) { return "Welcome, fellow traveler! I'm your travel English companion. I can help you with airport conversations, hotel bookings, restaurant orders, and asking for directions. Where shall we start?"; } else { return "That sounds like a great travel experience! When traveling, it's important to communicate clearly. Can you describe the situation in more detail? I'll help you with the right phrases!"; } } String _getAcademicResponse(String message) { if (message.contains('research') || message.contains('研究')) { return "Research is fundamental in academics! What's your research area? We can practice presenting findings, discussing methodology, or explaining complex concepts clearly."; } else if (message.contains('presentation') || message.contains('论文')) { return "Academic presentations require clear structure and precise language. Are you presenting research results, defending a thesis, or giving a conference talk? Let's work on your key points!"; } else if (message.contains('discussion') || message.contains('讨论')) { return "Academic discussions involve critical thinking and evidence-based arguments. What topic are you discussing? Practice phrases like 'According to the research...' or 'The evidence suggests...'"; } else if (message.contains('writing') || message.contains('写作')) { return "Academic writing has specific conventions. Are you working on an essay, research paper, or thesis? I can help with structure, citations, and formal language."; } else if (message.contains('hello') || message.contains('hi') || message.contains('你好')) { return "Greetings! I'm your academic English tutor. I specialize in research discussions, academic presentations, scholarly writing, and conference communications. What academic skill would you like to develop?"; } else { return "That's a thoughtful academic point. In scholarly discourse, we need to support our arguments with evidence. Could you provide more context or examples to strengthen your position?"; } } void _startRecording() { setState(() { _isRecording = true; }); // TODO: 实现语音录制功能 } void _stopRecording() { setState(() { _isRecording = false; }); // TODO: 处理录制的语音 } void _scrollToBottom() { WidgetsBinding.instance.addPostFrameCallback((_) { if (_scrollController.hasClients) { _scrollController.animateTo( _scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); } }); } void _showTutorInfo() { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) => Container( height: MediaQuery.of(context).size.height * 0.7, decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: Column( children: [ Container( width: 40, height: 4, margin: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( color: Colors.grey[300], borderRadius: BorderRadius.circular(2), ), ), Expanded( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( widget.tutor.avatar, style: const TextStyle(fontSize: 40), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.tutor.name, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), Text( widget.tutor.type.displayName, style: TextStyle( fontSize: 14, color: widget.tutor.type.color, ), ), ], ), ), ], ), const SizedBox(height: 20), Text( '个性特点', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: widget.tutor.type.color, ), ), const SizedBox(height: 8), Text( widget.tutor.personality, style: const TextStyle(fontSize: 14), ), const SizedBox(height: 20), Text( '专业领域', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: widget.tutor.type.color, ), ), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 8, children: widget.tutor.specialties.map((specialty) { return Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), decoration: BoxDecoration( color: widget.tutor.type.color.withOpacity(0.1), borderRadius: BorderRadius.circular(16), border: Border.all( color: widget.tutor.type.color.withOpacity(0.3), ), ), child: Text( specialty, style: TextStyle( fontSize: 12, color: widget.tutor.type.color, ), ), ); }).toList(), ), ], ), ), ), ], ), ), ); } String _formatTime(DateTime time) { return '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}'; } @override void dispose() { _messageController.dispose(); _scrollController.dispose(); super.dispose(); } }