import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../models/vocabulary_book_model.dart'; import '../models/word_model.dart'; import '../providers/vocabulary_provider.dart'; import 'dart:math'; enum TestType { vocabularyLevel, listening, reading, } class VocabularyTestScreen extends ConsumerStatefulWidget { final VocabularyBook? vocabularyBook; final TestType testType; final int questionCount; const VocabularyTestScreen({ super.key, this.vocabularyBook, this.testType = TestType.vocabularyLevel, this.questionCount = 20, }); @override ConsumerState createState() => _VocabularyTestScreenState(); } class _VocabularyTestScreenState extends ConsumerState { List _testWords = []; int _currentIndex = 0; Map _userAnswers = {}; bool _isLoading = true; bool _isTestComplete = false; @override void initState() { super.initState(); _initTest(); } Future _initTest() async { setState(() => _isLoading = true); try { final notifier = ref.read(vocabularyProvider.notifier); await notifier.loadTodayStudyWords(); final state = ref.read(vocabularyProvider); final allWords = state.todayWords; if (allWords.isEmpty) { // 如果没有今日单词,生成示例数据 _testWords = _generateSampleWords(); } else { // 随机选取指定数量的单词 final random = Random(); final selectedWords = []; final wordsCopy = List.from(allWords); final count = widget.questionCount.clamp(1, wordsCopy.length); for (var i = 0; i < count; i++) { if (wordsCopy.isEmpty) break; final index = random.nextInt(wordsCopy.length); selectedWords.add(wordsCopy.removeAt(index)); } _testWords = selectedWords; } } catch (e) { _testWords = _generateSampleWords(); } setState(() => _isLoading = false); } List _generateSampleWords() { // 生成示例测试数据 final sampleWords = [ 'abandon', 'ability', 'abroad', 'absence', 'absolute', 'absorb', 'abstract', 'abundant', 'academic', 'accept', ]; return List.generate( widget.questionCount.clamp(1, sampleWords.length), (index) => Word( id: '${index + 1}', word: sampleWords[index % sampleWords.length], phonetic: '/ˈsæmpl/', difficulty: WordDifficulty.intermediate, frequency: 1000, definitions: [ WordDefinition( type: WordType.noun, definition: 'Example definition', translation: '示例释义 ${index + 1}', ), ], createdAt: DateTime.now(), updatedAt: DateTime.now(), ), ); } @override Widget build(BuildContext context) { if (_isLoading) { return Scaffold( appBar: AppBar( title: const Text('词汇测试'), ), body: const Center( child: CircularProgressIndicator(), ), ); } if (_isTestComplete) { return _buildResultScreen(); } return Scaffold( appBar: AppBar( title: Text('词汇测试 (${_currentIndex + 1}/${_testWords.length})'), actions: [ TextButton( onPressed: _showExitConfirmDialog, child: const Text( '退出', style: TextStyle(color: Colors.white), ), ), ], ), body: _buildTestQuestion(), ); } Widget _buildTestQuestion() { if (_currentIndex >= _testWords.length) { return const Center(child: Text('测试已完成')); } final word = _testWords[_currentIndex]; final correctAnswer = word.definitions.isNotEmpty ? word.definitions.first.translation : '示例释义'; // 生成选项(一个正确答案 + 三个干扰项) final options = _generateOptions(correctAnswer, _currentIndex); return Column( children: [ // 进度条 LinearProgressIndicator( value: (_currentIndex + 1) / _testWords.length, backgroundColor: Colors.grey[200], valueColor: const AlwaysStoppedAnimation(Color(0xFF2196F3)), ), Expanded( child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // 问题区域 Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: const Color(0xFF2196F3).withOpacity(0.1), borderRadius: BorderRadius.circular(16), ), child: Column( children: [ const Text( '请选择下列单词的正确释义', style: TextStyle( fontSize: 14, color: Colors.grey, ), ), const SizedBox(height: 16), Text( word.word, style: const TextStyle( fontSize: 36, fontWeight: FontWeight.bold, color: Color(0xFF2196F3), ), ), if (word.phonetic != null) ...[ const SizedBox(height: 8), Text( word.phonetic!, style: const TextStyle( fontSize: 18, color: Colors.grey, fontStyle: FontStyle.italic, ), ), ], ], ), ), const SizedBox(height: 32), // 选项 ...options.asMap().entries.map((entry) { final index = entry.key; final option = entry.value; final optionLabel = String.fromCharCode(65 + index); // A, B, C, D return Padding( padding: const EdgeInsets.only(bottom: 12), child: _buildOptionCard( label: optionLabel, text: option, isSelected: _userAnswers[_currentIndex] == option, onTap: () => _selectAnswer(option), ), ); }), ], ), ), ), // 底部按钮 Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, -2), ), ], ), child: SafeArea( child: ElevatedButton( onPressed: _userAnswers.containsKey(_currentIndex) ? _nextQuestion : null, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF2196F3), padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: Text( _currentIndex < _testWords.length - 1 ? '下一题' : '完成测试', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), ), ), ], ); } Widget _buildOptionCard({ required String label, required String text, required bool isSelected, required VoidCallback onTap, }) { return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isSelected ? const Color(0xFF2196F3).withOpacity(0.1) : Colors.white, border: Border.all( color: isSelected ? const Color(0xFF2196F3) : Colors.grey[300]!, width: isSelected ? 2 : 1, ), borderRadius: BorderRadius.circular(12), ), child: Row( children: [ Container( width: 32, height: 32, decoration: BoxDecoration( color: isSelected ? const Color(0xFF2196F3) : Colors.grey[200], shape: BoxShape.circle, ), child: Center( child: Text( label, style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: isSelected ? Colors.white : Colors.grey[600], ), ), ), ), const SizedBox(width: 16), Expanded( child: Text( text, style: TextStyle( fontSize: 16, color: isSelected ? const Color(0xFF2196F3) : Colors.black87, fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, ), ), ), ], ), ), ); } List _generateOptions(String correctAnswer, int questionIndex) { final distractors = [ '放弃;遗弃', '能力;才能', '在国外;到国外', '缺席;缺乏', '绝对的;完全的', '吸收;吸引', '抽象的;抽象概念', '丰富的;充裕的', '学术的;学院的', '接受;承认', ]; final options = [correctAnswer]; final random = Random(questionIndex); // 使用问题索引作为种子以保证一致性 while (options.length < 4 && distractors.isNotEmpty) { final index = random.nextInt(distractors.length); final distractor = distractors[index]; if (distractor != correctAnswer && !options.contains(distractor)) { options.add(distractor); } distractors.removeAt(index); } // 打乱选项顺序 options.shuffle(random); return options; } void _selectAnswer(String answer) { setState(() { _userAnswers[_currentIndex] = answer; }); } void _nextQuestion() { if (_currentIndex < _testWords.length - 1) { setState(() { _currentIndex++; }); } else { setState(() { _isTestComplete = true; }); } } Widget _buildResultScreen() { int correctCount = 0; for (var i = 0; i < _testWords.length; i++) { final word = _testWords[i]; final correctAnswer = word.definitions.isNotEmpty ? word.definitions.first.translation : '示例释义'; if (_userAnswers[i] == correctAnswer) { correctCount++; } } final score = (correctCount / _testWords.length * 100).round(); return Scaffold( appBar: AppBar( title: const Text('测试结果'), automaticallyImplyLeading: false, ), body: Center( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: 120, height: 120, decoration: BoxDecoration( color: score >= 60 ? Colors.green.withOpacity(0.1) : Colors.orange.withOpacity(0.1), shape: BoxShape.circle, ), child: Center( child: Text( '$score', style: TextStyle( fontSize: 48, fontWeight: FontWeight.bold, color: score >= 60 ? Colors.green : Colors.orange, ), ), ), ), const SizedBox(height: 24), Text( score >= 80 ? '太棒了!' : score >= 60 ? '不错哦!' : '加油!', style: const TextStyle( fontSize: 28, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), Text( '你的分数:$score 分', style: const TextStyle( fontSize: 18, color: Colors.grey, ), ), const SizedBox(height: 8), Text( '正确率:$correctCount/${_testWords.length}', style: const TextStyle( fontSize: 16, color: Colors.grey, ), ), const SizedBox(height: 48), ElevatedButton( onPressed: () => Navigator.of(context).pop(), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF2196F3), padding: const EdgeInsets.symmetric( horizontal: 48, vertical: 16, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: const Text( '完成', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), const SizedBox(height: 16), TextButton( onPressed: () { setState(() { _currentIndex = 0; _userAnswers.clear(); _isTestComplete = false; }); _initTest(); }, child: const Text('重新测试'), ), ], ), ), ), ); } void _showExitConfirmDialog() { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('退出测试'), content: const Text('确定要退出吗?当前进度将不会保存。'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('取消'), ), TextButton( onPressed: () { Navigator.of(context).pop(); Navigator.of(context).pop(); }, child: const Text('确定'), ), ], ), ); } }