import 'package:flutter/material.dart'; import 'dart:math'; import 'package:flutter_tts/flutter_tts.dart'; import '../models/custom_kanji_item.dart'; import '../widgets/options_grid.dart'; import '../widgets/kanji_card.dart'; enum CustomQuizMode { japaneseToEnglish, englishToJapanese, listeningComprehension } class CustomQuizScreen extends StatefulWidget { final List deck; final CustomQuizMode quizMode; final Function(CustomKanjiItem) onCardReviewed; final bool useKanji; final bool isActive; const CustomQuizScreen({ super.key, required this.deck, required this.quizMode, required this.onCardReviewed, required this.useKanji, required this.isActive, }); @override State createState() => CustomQuizScreenState(); } class CustomQuizScreenState extends State with TickerProviderStateMixin { int _currentIndex = 0; List _shuffledDeck = []; List _options = []; bool _answered = false; bool? _correct; late FlutterTts _flutterTts; late AnimationController _shakeController; late Animation _shakeAnimation; final List _incorrectlyAnsweredItems = []; @override void initState() { super.initState(); _shuffledDeck = widget.deck.toList()..shuffle(); _initTts(); if (_shuffledDeck.isNotEmpty) { _generateOptions(); } _shakeController = AnimationController( duration: const Duration(milliseconds: 500), vsync: this, ); _shakeAnimation = Tween(begin: 0, end: 1).animate( CurvedAnimation( parent: _shakeController, curve: Curves.elasticIn, ), ); } @override void didUpdateWidget(CustomQuizScreen oldWidget) { super.didUpdateWidget(oldWidget); if (widget.deck != oldWidget.deck && !widget.isActive) { setState(() { _shuffledDeck = widget.deck.toList()..shuffle(); _currentIndex = 0; _answered = false; _correct = null; if (_shuffledDeck.isNotEmpty) { _generateOptions(); } }); } if (widget.useKanji != oldWidget.useKanji) { setState(() { _generateOptions(); }); } } void playAudio() { if (widget.quizMode == CustomQuizMode.listeningComprehension && _currentIndex < _shuffledDeck.length) { _speak(_shuffledDeck[_currentIndex].characters); } } void _initTts() async { _flutterTts = FlutterTts(); await _flutterTts.setLanguage("ja-JP"); } @override void dispose() { _flutterTts.stop(); _shakeController.dispose(); super.dispose(); } void _generateOptions() { final currentItem = _shuffledDeck[_currentIndex]; if (widget.quizMode == CustomQuizMode.listeningComprehension || widget.quizMode == CustomQuizMode.japaneseToEnglish) { _options = [currentItem.meaning]; } else { _options = [widget.useKanji && currentItem.kanji != null ? currentItem.kanji! : currentItem.characters]; } final otherItems = widget.deck .where((item) => item.characters != currentItem.characters) .toList(); otherItems.shuffle(); for (var i = 0; i < min(3, otherItems.length); i++) { if (widget.quizMode == CustomQuizMode.listeningComprehension || widget.quizMode == CustomQuizMode.japaneseToEnglish) { _options.add(otherItems[i].meaning); } else { _options.add(widget.useKanji && otherItems[i].kanji != null ? otherItems[i].kanji! : otherItems[i].characters); } } _options.shuffle(); } void _checkAnswer(String answer) async { final currentItem = _shuffledDeck[_currentIndex]; final correctAnswer = (widget.quizMode == CustomQuizMode.englishToJapanese) ? (widget.useKanji && currentItem.kanji != null ? currentItem.kanji! : currentItem.characters) : currentItem.meaning; final isCorrect = answer == correctAnswer; if (currentItem.useInterval) { int currentSrsLevel; switch (widget.quizMode) { case CustomQuizMode.japaneseToEnglish: currentSrsLevel = currentItem.srsData.japaneseToEnglish; break; case CustomQuizMode.englishToJapanese: currentSrsLevel = currentItem.srsData.englishToJapanese; break; case CustomQuizMode.listeningComprehension: currentSrsLevel = currentItem.srsData.listeningComprehension; break; } if (isCorrect) { if (_incorrectlyAnsweredItems.contains(currentItem.characters)) { _incorrectlyAnsweredItems.remove(currentItem.characters); } else { currentSrsLevel++; } final interval = pow(2, currentSrsLevel).toInt(); final newNextReview = DateTime.now().add(Duration(hours: interval)); switch (widget.quizMode) { case CustomQuizMode.japaneseToEnglish: currentItem.srsData.japaneseToEnglishNextReview = newNextReview; break; case CustomQuizMode.englishToJapanese: currentItem.srsData.englishToJapaneseNextReview = newNextReview; break; case CustomQuizMode.listeningComprehension: currentItem.srsData.listeningComprehensionNextReview = newNextReview; break; } } else { if (!_incorrectlyAnsweredItems.contains(currentItem.characters)) { _incorrectlyAnsweredItems.add(currentItem.characters); } currentSrsLevel = max(0, currentSrsLevel - 1); final newNextReview = DateTime.now().add(const Duration(hours: 1)); switch (widget.quizMode) { case CustomQuizMode.japaneseToEnglish: currentItem.srsData.japaneseToEnglishNextReview = newNextReview; break; case CustomQuizMode.englishToJapanese: currentItem.srsData.englishToJapaneseNextReview = newNextReview; break; case CustomQuizMode.listeningComprehension: currentItem.srsData.listeningComprehensionNextReview = newNextReview; break; } } switch (widget.quizMode) { case CustomQuizMode.japaneseToEnglish: currentItem.srsData.japaneseToEnglish = currentSrsLevel; break; case CustomQuizMode.englishToJapanese: currentItem.srsData.englishToJapanese = currentSrsLevel; break; case CustomQuizMode.listeningComprehension: currentItem.srsData.listeningComprehension = currentSrsLevel; break; } widget.onCardReviewed(currentItem); } // --- SnackBar Logic (new) --- final correctDisplay = (widget.quizMode == CustomQuizMode.englishToJapanese) ? (widget.useKanji && currentItem.kanji != null ? currentItem.kanji! : currentItem.characters) : currentItem.meaning; final snack = SnackBar( content: Text( isCorrect ? 'Correct!' : 'Wrong — correct: $correctDisplay', style: TextStyle( color: isCorrect ? Colors.greenAccent : Colors.redAccent, fontWeight: FontWeight.bold, ), ), backgroundColor: const Color(0xFF222222), duration: const Duration(milliseconds: 900), ); if (mounted) { ScaffoldMessenger.of(context).showSnackBar(snack); } // --- End SnackBar Logic --- if (isCorrect) { if (widget.quizMode == CustomQuizMode.japaneseToEnglish) { await _speak(currentItem.characters); } await Future.delayed(const Duration(milliseconds: 500)); // Small delay after correct answer } else { _shakeController.forward(from: 0); await Future.delayed(const Duration(milliseconds: 900)); // Delay for shake animation } _nextQuestion(); } void _nextQuestion() { setState(() { _currentIndex++; _answered = false; _correct = null; if (_currentIndex < _shuffledDeck.length) { _generateOptions(); if (widget.quizMode == CustomQuizMode.listeningComprehension) { _speak(_shuffledDeck[_currentIndex].characters); } } }); } Future _speak(String text) async { await _flutterTts.speak(text); } void _onOptionSelected(String option) { if (!(_answered && _correct!)) { _checkAnswer(option); } } @override Widget build(BuildContext context) { if (_shuffledDeck.isEmpty || _currentIndex >= _shuffledDeck.length) { return const Center( child: Text('Review session complete!'), ); } final currentItem = _shuffledDeck[_currentIndex]; final question = (widget.quizMode == CustomQuizMode.englishToJapanese) ? currentItem.meaning : (widget.useKanji && currentItem.kanji != null ? currentItem.kanji! : currentItem.characters); Widget promptWidget; if (widget.quizMode == CustomQuizMode.listeningComprehension) { promptWidget = IconButton( icon: const Icon(Icons.volume_up, size: 64), onPressed: () => _speak(currentItem.characters), ); } else { promptWidget = GestureDetector( onTap: () => _speak(question), child: Text( question, style: const TextStyle(fontSize: 48), textAlign: TextAlign.center, ), ); } return Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ const SizedBox(height: 18), Expanded( flex: 3, child: Center( child: ConstrainedBox( constraints: const BoxConstraints( minWidth: 0, maxWidth: 500, minHeight: 150, ), child: KanjiCard( characterWidget: promptWidget, subtitle: '', ), ), ), ), const SizedBox(height: 12), SafeArea( top: false, child: Column( children: [ AnimatedBuilder( animation: _shakeAnimation, builder: (context, child) { return Transform.translate( offset: Offset(_shakeAnimation.value * 10, 0), child: child, ); }, child: OptionsGrid( options: _options, onSelected: _onOptionSelected, correctAnswers: [], showResult: false, isDisabled: false, ), ), ], ), ), ], ), ); } }