import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../models/ai_tutor.dart'; import '../data/ai_tutor_data.dart'; import '../screens/ai_conversation_screen.dart'; import '../models/conversation_scenario.dart'; import '../data/scenario_data.dart'; import '../providers/speaking_provider.dart'; import '../screens/scenario_practice_screen.dart'; import '../models/pronunciation_item.dart'; import '../screens/pronunciation_list_screen.dart'; import '../models/pronunciation_assessment.dart'; /// 口语练习主页面 class SpeakingHomeScreen extends ConsumerStatefulWidget { const SpeakingHomeScreen({super.key}); @override ConsumerState createState() => _SpeakingHomeScreenState(); } class _SpeakingHomeScreenState extends ConsumerState { @override void initState() { super.initState(); // 加载推荐场景 WidgetsBinding.instance.addPostFrameCallback((_) { ref.read(speakingTasksProvider.notifier).loadRecommendedTasks(); }); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF5F5F5), appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0, leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.black87), onPressed: () => Navigator.of(context).pop(), ), title: const Text( '口语练习', style: TextStyle( color: Colors.black87, fontSize: 18, fontWeight: FontWeight.w600, ), ), centerTitle: true, ), body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildAITutors(), const SizedBox(height: 20), _buildScenarios(), const SizedBox(height: 20), _buildPronunciationPractice(), const SizedBox(height: 20), _buildSpeakingProgress(), const SizedBox(height: 100), // 底部导航栏空间 ], ), ), ), ); } Widget _buildAITutors() { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'AI对话伙伴', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 12, mainAxisSpacing: 12, childAspectRatio: 1.2, ), itemCount: AITutorData.getAllTutors().length, itemBuilder: (context, index) { final tutor = AITutorData.getAllTutors()[index]; return _buildTutorCard(tutor); }, ), ], ), ); } Widget _buildTutorCard(AITutor tutor) { return GestureDetector( onTap: () { Navigator.of(context).push( MaterialPageRoute( builder: (context) => AIConversationScreen(tutor: tutor), ), ); }, child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: tutor.type.color.withOpacity(0.1), borderRadius: BorderRadius.circular(12), border: Border.all(color: tutor.type.color.withOpacity(0.3)), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( tutor.avatar, style: const TextStyle(fontSize: 32), ), const SizedBox(height: 8), Text( tutor.type.displayName, style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: tutor.type.color, ), ), const SizedBox(height: 4), Text( tutor.type.description, style: const TextStyle( fontSize: 10, color: Colors.grey, ), textAlign: TextAlign.center, ), ], ), ), ); } Widget _buildScenarios() { final tasksState = ref.watch(speakingTasksProvider); return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '对话场景', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), tasksState.isLoading ? const Center( child: Padding( padding: EdgeInsets.all(20.0), child: CircularProgressIndicator(), ), ) : tasksState.error != null ? Center( child: Column( children: [ Text( '加载失败: ${tasksState.error}', style: const TextStyle(color: Colors.red), ), const SizedBox(height: 8), TextButton( onPressed: () { ref.read(speakingTasksProvider.notifier).loadRecommendedTasks(); }, child: const Text('重试'), ), ], ), ) : tasksState.tasks.isEmpty ? Center( child: Column( children: [ Icon( Icons.chat_bubble_outline, size: 48, color: Colors.grey[400], ), const SizedBox(height: 12), Text( '暂无可用场景', style: TextStyle( fontSize: 14, color: Colors.grey[600], ), ), const SizedBox(height: 8), Text( '使用静态数据作为备选', style: TextStyle( fontSize: 12, color: Colors.grey[500], ), ), ], ), ) : Column( children: tasksState.tasks.take(5).map((task) { // 将SpeakingTask转换为ConversationScenario显示 final scenario = ConversationScenario( id: task.id, title: task.title, subtitle: task.description, description: task.description, duration: '${task.estimatedDuration}分钟', level: _mapDifficultyToLevel(task.difficulty.name), type: ScenarioType.business, objectives: task.objectives, keyPhrases: task.keyPhrases, steps: [], createdAt: task.createdAt, ); return Padding( padding: const EdgeInsets.only(bottom: 12), child: _buildScenarioItem(scenario), ); }).toList(), ), ], ), ); } Widget _buildScenarioItem(ConversationScenario scenario) { return GestureDetector( onTap: () { Navigator.of(context).push( MaterialPageRoute( builder: (context) => ScenarioPracticeScreen(scenario: scenario), ), ); }, child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: const Color(0xFF2196F3).withOpacity(0.1), borderRadius: BorderRadius.circular(20), ), child: Center( child: Text( scenario.type.icon, style: const TextStyle(fontSize: 20), ), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( scenario.title, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, ), ), Text( scenario.subtitle, style: const TextStyle( fontSize: 12, color: Colors.grey, ), ), ], ), ), Column( children: [ Text( scenario.duration, style: const TextStyle( fontSize: 12, color: Colors.grey, ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: const Color(0xFF2196F3), borderRadius: BorderRadius.circular(4), ), child: Text( scenario.level, style: const TextStyle( fontSize: 10, color: Colors.white, ), ), ), ], ), ], ), ), ); } Widget _buildPronunciationPractice() { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '发音练习', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), Row( children: [ Expanded( child: _buildPronunciationCard( '单词发音', '音素练习', Icons.hearing, Colors.blue, ), ), const SizedBox(width: 12), Expanded( child: _buildPronunciationCard( '句子朗读', '语调练习', Icons.graphic_eq, Colors.green, ), ), ], ), ], ), ); } Widget _buildPronunciationCard( String title, String subtitle, IconData icon, Color color, ) { return GestureDetector( onTap: () { PronunciationType type; if (title == '单词发音') { type = PronunciationType.word; } else { type = PronunciationType.sentence; } Navigator.push( context, MaterialPageRoute( builder: (context) => PronunciationListScreen(type: type), ), ); }, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(12), border: Border.all(color: color.withOpacity(0.3)), ), child: Column( children: [ Icon( icon, color: color, size: 32, ), const SizedBox(height: 8), Text( title, style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: color, ), ), const SizedBox(height: 4), Text( subtitle, style: const TextStyle( fontSize: 12, color: Colors.grey, ), textAlign: TextAlign.center, ), ], ), ), ); } Widget _buildSpeakingProgress() { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '口语统计', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), Consumer( builder: (context, ref, _) { final service = ref.watch(speakingServiceProvider); return FutureBuilder( future: service.getUserSpeakingStatistics(), builder: (context, snapshot) { if (!snapshot.hasData) { return const Center(child: CircularProgressIndicator()); } final stats = snapshot.data!.data; return Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildProgressItem('${stats?.totalSessions ?? 0}', '对话次数', Icons.chat_bubble), _buildProgressItem('${stats != null ? stats.skillAnalysis.criteriaScores[PronunciationCriteria.accuracy]?.toStringAsFixed(0) ?? '0' : '0'}%', '发音准确度', Icons.mic), _buildProgressItem('${stats?.averageScore.toStringAsFixed(0) ?? '0'}', '平均分', Icons.trending_up), ], ); }, ); }, ), ], ), ); } Widget _buildProgressItem(String value, String label, IconData icon) { return Column( children: [ Icon( icon, color: const Color(0xFF2196F3), size: 24, ), const SizedBox(height: 8), Text( value, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Color(0xFF2196F3), ), ), const SizedBox(height: 4), Text( label, style: const TextStyle( fontSize: 12, color: Colors.grey, ), ), ], ); } String _mapDifficultyToLevel(String? difficulty) { if (difficulty == null) return 'B1'; switch (difficulty.toLowerCase()) { case 'beginner': case 'elementary': return 'A1'; case 'intermediate': return 'B1'; case 'upper-intermediate': case 'upperintermediate': return 'B2'; case 'advanced': return 'C1'; default: return 'B1'; } } }