import 'package:flutter/material.dart'; import '../models/reading_exercise_model.dart'; /// 阅读练习详情页面 class ReadingExerciseScreen extends StatefulWidget { final ReadingExercise exercise; const ReadingExerciseScreen({ super.key, required this.exercise, }); @override State createState() => _ReadingExerciseScreenState(); } class _ReadingExerciseScreenState extends State with TickerProviderStateMixin { late TabController _tabController; Map userAnswers = {}; bool showResults = false; int score = 0; @override void initState() { super.initState(); _tabController = TabController(length: 2, vsync: this); } @override void dispose() { _tabController.dispose(); super.dispose(); } @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: Text( widget.exercise.title, style: const TextStyle( color: Colors.black87, fontSize: 16, fontWeight: FontWeight.w600, ), ), centerTitle: true, bottom: TabBar( controller: _tabController, labelColor: const Color(0xFF2196F3), unselectedLabelColor: Colors.grey, indicatorColor: const Color(0xFF2196F3), tabs: const [ Tab(text: '阅读文章'), Tab(text: '练习题'), ], ), ), body: TabBarView( controller: _tabController, children: [ _buildArticleTab(), _buildQuestionsTab(), ], ), ); } Widget _buildArticleTab() { return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildArticleHeader(), const SizedBox(height: 20), _buildArticleContent(), const SizedBox(height: 20), _buildArticleFooter(), ], ), ); } Widget _buildArticleHeader() { 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: [ Row( children: [ Expanded( child: Text( widget.exercise.title, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: _getDifficultyColor(widget.exercise.difficulty), borderRadius: BorderRadius.circular(12), ), child: Text( _getDifficultyLabel(widget.exercise.difficulty), style: const TextStyle( fontSize: 12, color: Colors.white, fontWeight: FontWeight.bold, ), ), ), ], ), const SizedBox(height: 12), Text( widget.exercise.summary, style: const TextStyle( fontSize: 14, color: Colors.grey, ), ), const SizedBox(height: 16), Row( children: [ _buildInfoChip(Icons.access_time, '${widget.exercise.estimatedTime}分钟'), const SizedBox(width: 12), _buildInfoChip(Icons.text_fields, '${widget.exercise.wordCount}词'), const SizedBox(width: 12), _buildInfoChip(Icons.quiz, '${widget.exercise.questions.length}题'), ], ), if (widget.exercise.tags.isNotEmpty) ...[ const SizedBox(height: 12), Wrap( spacing: 6, runSpacing: 6, children: widget.exercise.tags.map((tag) => Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(12), ), child: Text( tag, style: const TextStyle( fontSize: 12, color: Colors.grey, ), ), )).toList(), ), ], ], ), ); } Widget _buildInfoChip(IconData icon, String text) { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( icon, size: 14, color: Colors.grey[600], ), const SizedBox(width: 4), Text( text, style: TextStyle( fontSize: 12, color: Colors.grey[600], ), ), ], ), ); } Widget _buildArticleContent() { 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), Text( widget.exercise.content, style: const TextStyle( fontSize: 16, height: 1.6, color: Colors.black87, ), ), ], ), ); } Widget _buildArticleFooter() { 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: 12), Row( children: [ const Text( '来源:', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, ), ), Text( widget.exercise.source, style: const TextStyle( fontSize: 14, color: Colors.grey, ), ), ], ), const SizedBox(height: 8), Row( children: [ const Text( '发布时间:', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, ), ), Text( '${widget.exercise.publishDate.year}年${widget.exercise.publishDate.month}月${widget.exercise.publishDate.day}日', style: const TextStyle( fontSize: 14, color: Colors.grey, ), ), ], ), ], ), ); } Widget _buildQuestionsTab() { return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( children: [ if (showResults) _buildResultsHeader(), ...widget.exercise.questions.asMap().entries.map((entry) { final index = entry.key; final question = entry.value; return Padding( padding: const EdgeInsets.only(bottom: 16), child: _buildQuestionCard(question, index), ); }).toList(), const SizedBox(height: 20), if (!showResults) SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _submitAnswers, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF2196F3), padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: const Text( '提交答案', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), if (showResults) SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _resetQuiz, style: ElevatedButton.styleFrom( backgroundColor: Colors.grey, padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: const Text( '重新练习', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), ], ), ); } Widget _buildResultsHeader() { final percentage = (score / widget.exercise.questions.length * 100).round(); return Container( margin: const EdgeInsets.only(bottom: 20), 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( children: [ const Text( '练习结果', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Column( children: [ Text( '$score', style: const TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: Color(0xFF2196F3), ), ), const Text( '正确题数', style: TextStyle( fontSize: 12, color: Colors.grey, ), ), ], ), Column( children: [ Text( '${widget.exercise.questions.length}', style: const TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: Colors.grey, ), ), const Text( '总题数', style: TextStyle( fontSize: 12, color: Colors.grey, ), ), ], ), Column( children: [ Text( '$percentage%', style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: percentage >= 80 ? Colors.green : percentage >= 60 ? Colors.orange : Colors.red, ), ), const Text( '正确率', style: TextStyle( fontSize: 12, color: Colors.grey, ), ), ], ), ], ), ], ), ); } Widget _buildQuestionCard(ReadingQuestion question, int index) { final userAnswer = userAnswers[question.id]; final isCorrect = userAnswer == question.correctAnswer; final showAnswer = showResults; return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), border: showAnswer ? Border.all( color: isCorrect ? Colors.green : Colors.red, width: 2, ) : null, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 24, height: 24, decoration: BoxDecoration( color: const Color(0xFF2196F3), borderRadius: BorderRadius.circular(12), ), child: Center( child: Text( '${index + 1}', style: const TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold, ), ), ), ), const SizedBox(width: 12), Expanded( child: Text( question.question, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), if (showAnswer) Icon( isCorrect ? Icons.check_circle : Icons.cancel, color: isCorrect ? Colors.green : Colors.red, ), ], ), const SizedBox(height: 16), if (question.type == 'multiple_choice') ...question.options.asMap().entries.map((entry) { final optionIndex = entry.key; final option = entry.value; final isSelected = userAnswer == optionIndex; final isCorrectOption = optionIndex == question.correctAnswer; Color? backgroundColor; Color? textColor; if (showAnswer) { if (isCorrectOption) { backgroundColor = Colors.green.withOpacity(0.1); textColor = Colors.green; } else if (isSelected && !isCorrectOption) { backgroundColor = Colors.red.withOpacity(0.1); textColor = Colors.red; } } else if (isSelected) { backgroundColor = const Color(0xFF2196F3).withOpacity(0.1); textColor = const Color(0xFF2196F3); } return GestureDetector( onTap: showAnswer ? null : () { setState(() { userAnswers[question.id] = optionIndex; }); }, child: Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: backgroundColor ?? Colors.grey[50], borderRadius: BorderRadius.circular(8), border: Border.all( color: backgroundColor != null ? (textColor ?? Colors.grey) : Colors.grey.withOpacity(0.3), ), ), child: Row( children: [ Container( width: 20, height: 20, decoration: BoxDecoration( shape: BoxShape.circle, color: isSelected ? (textColor ?? const Color(0xFF2196F3)) : Colors.transparent, border: Border.all( color: textColor ?? Colors.grey, ), ), child: isSelected ? const Icon( Icons.check, size: 12, color: Colors.white, ) : null, ), const SizedBox(width: 12), Expanded( child: Text( option, style: TextStyle( fontSize: 14, color: textColor ?? Colors.black87, ), ), ), ], ), ), ); }).toList(), if (question.type == 'true_false') Row( children: [ Expanded( child: _buildTrueFalseOption(question, true, 'True'), ), const SizedBox(width: 12), Expanded( child: _buildTrueFalseOption(question, false, 'False'), ), ], ), if (showAnswer && question.explanation.isNotEmpty) ...[ const SizedBox(height: 16), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.blue.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '解析:', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Color(0xFF2196F3), ), ), const SizedBox(height: 4), Text( question.explanation, style: const TextStyle( fontSize: 14, color: Colors.black87, ), ), ], ), ), ], ], ), ); } Widget _buildTrueFalseOption(ReadingQuestion question, bool value, String label) { final userAnswer = userAnswers[question.id]; final isSelected = userAnswer == (value ? 0 : 1); final isCorrect = (value ? 0 : 1) == question.correctAnswer; final showAnswer = showResults; Color? backgroundColor; Color? textColor; if (showAnswer) { if (isCorrect) { backgroundColor = Colors.green.withOpacity(0.1); textColor = Colors.green; } else if (isSelected && !isCorrect) { backgroundColor = Colors.red.withOpacity(0.1); textColor = Colors.red; } } else if (isSelected) { backgroundColor = const Color(0xFF2196F3).withOpacity(0.1); textColor = const Color(0xFF2196F3); } return GestureDetector( onTap: showAnswer ? null : () { setState(() { userAnswers[question.id] = value ? 0 : 1; }); }, child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: backgroundColor ?? Colors.grey[50], borderRadius: BorderRadius.circular(8), border: Border.all( color: backgroundColor != null ? (textColor ?? Colors.grey) : Colors.grey.withOpacity(0.3), ), ), child: Center( child: Text( label, style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: textColor ?? Colors.black87, ), ), ), ), ); } void _submitAnswers() { if (userAnswers.length < widget.exercise.questions.length) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('请完成所有题目后再提交'), backgroundColor: Colors.orange, ), ); return; } int correctCount = 0; for (final question in widget.exercise.questions) { if (userAnswers[question.id] == question.correctAnswer) { correctCount++; } } setState(() { score = correctCount; showResults = true; }); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('练习完成!正确率:${(correctCount / widget.exercise.questions.length * 100).round()}%'), backgroundColor: Colors.green, ), ); } void _resetQuiz() { setState(() { userAnswers.clear(); showResults = false; score = 0; }); } String _getDifficultyLabel(ReadingDifficulty difficulty) { switch (difficulty) { case ReadingDifficulty.elementary: return 'A1'; case ReadingDifficulty.intermediate: return 'B1'; case ReadingDifficulty.upperIntermediate: return 'B2'; case ReadingDifficulty.advanced: return 'C1'; case ReadingDifficulty.proficient: return 'C2'; } } Color _getDifficultyColor(ReadingDifficulty difficulty) { switch (difficulty) { case ReadingDifficulty.elementary: return Colors.green; case ReadingDifficulty.intermediate: return Colors.orange; case ReadingDifficulty.upperIntermediate: return Colors.deepOrange; case ReadingDifficulty.advanced: return Colors.red; case ReadingDifficulty.proficient: return Colors.purple; } } }