This commit is contained in:
Rene Kievits
2025-10-31 13:40:14 +01:00
parent ad61292263
commit 4eb488e28c
16 changed files with 691 additions and 395 deletions

View File

@@ -5,7 +5,11 @@ import '../models/custom_kanji_item.dart';
import '../widgets/options_grid.dart';
import '../widgets/kanji_card.dart';
enum CustomQuizMode { japaneseToEnglish, englishToJapanese, listeningComprehension }
enum CustomQuizMode {
japaneseToEnglish,
englishToJapanese,
listeningComprehension,
}
class CustomQuizScreen extends StatefulWidget {
final List<CustomKanjiItem> deck;
@@ -53,10 +57,7 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
vsync: this,
);
_shakeAnimation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(
parent: _shakeController,
curve: Curves.elasticIn,
),
CurvedAnimation(parent: _shakeController, curve: Curves.elasticIn),
);
}
@@ -82,7 +83,8 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
}
void playAudio() {
if (widget.quizMode == CustomQuizMode.listeningComprehension && _currentIndex < _shuffledDeck.length) {
if (widget.quizMode == CustomQuizMode.listeningComprehension &&
_currentIndex < _shuffledDeck.length) {
_speak(_shuffledDeck[_currentIndex].characters);
}
}
@@ -101,20 +103,30 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
void _generateOptions() {
final currentItem = _shuffledDeck[_currentIndex];
if (widget.quizMode == CustomQuizMode.listeningComprehension || widget.quizMode == CustomQuizMode.japaneseToEnglish) {
if (widget.quizMode == CustomQuizMode.listeningComprehension ||
widget.quizMode == CustomQuizMode.japaneseToEnglish) {
_options = [currentItem.meaning];
} else {
_options = [widget.useKanji && currentItem.kanji != null ? currentItem.kanji! : currentItem.characters];
_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) {
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.add(
widget.useKanji && otherItems[i].kanji != null
? otherItems[i].kanji!
: otherItems[i].characters,
);
}
}
_options.shuffle();
@@ -123,7 +135,9 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
void _checkAnswer(String answer) async {
final currentItem = _shuffledDeck[_currentIndex];
final correctAnswer = (widget.quizMode == CustomQuizMode.englishToJapanese)
? (widget.useKanji && currentItem.kanji != null ? currentItem.kanji! : currentItem.characters)
? (widget.useKanji && currentItem.kanji != null
? currentItem.kanji!
: currentItem.characters)
: currentItem.meaning;
final isCorrect = answer == correctAnswer;
@@ -157,7 +171,8 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
currentItem.srsData.englishToJapaneseNextReview = newNextReview;
break;
case CustomQuizMode.listeningComprehension:
currentItem.srsData.listeningComprehensionNextReview = newNextReview;
currentItem.srsData.listeningComprehensionNextReview =
newNextReview;
break;
}
} else {
@@ -174,7 +189,8 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
currentItem.srsData.englishToJapaneseNextReview = newNextReview;
break;
case CustomQuizMode.listeningComprehension:
currentItem.srsData.listeningComprehensionNextReview = newNextReview;
currentItem.srsData.listeningComprehensionNextReview =
newNextReview;
break;
}
}
@@ -194,35 +210,35 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
widget.onCardReviewed(currentItem);
}
// --- SnackBar Logic (new) ---
final correctDisplay = (widget.quizMode == CustomQuizMode.englishToJapanese)
? (widget.useKanji && currentItem.kanji != null ? currentItem.kanji! : currentItem.characters)
? (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,
color: isCorrect ? Theme.of(context).colorScheme.tertiary : Theme.of(context).colorScheme.error,
fontWeight: FontWeight.bold,
),
),
backgroundColor: const Color(0xFF222222),
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
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
await Future.delayed(const Duration(milliseconds: 500));
} else {
_shakeController.forward(from: 0);
await Future.delayed(const Duration(milliseconds: 900)); // Delay for shake animation
await Future.delayed(const Duration(milliseconds: 900));
}
_nextQuestion();
@@ -255,15 +271,15 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
@override
Widget build(BuildContext context) {
if (_shuffledDeck.isEmpty || _currentIndex >= _shuffledDeck.length) {
return const Center(
child: Text('Review session complete!'),
);
return Center(child: Text('Review session complete!', style: TextStyle(color: Theme.of(context).colorScheme.onSurface)));
}
final currentItem = _shuffledDeck[_currentIndex];
final question = (widget.quizMode == CustomQuizMode.englishToJapanese)
? currentItem.meaning
: (widget.useKanji && currentItem.kanji != null ? currentItem.kanji! : currentItem.characters);
: (widget.useKanji && currentItem.kanji != null
? currentItem.kanji!
: currentItem.characters);
Widget promptWidget;
if (widget.quizMode == CustomQuizMode.listeningComprehension) {
@@ -276,7 +292,7 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
onTap: () => _speak(question),
child: Text(
question,
style: const TextStyle(fontSize: 48),
style: TextStyle(fontSize: 48, color: Theme.of(context).colorScheme.onSurface),
textAlign: TextAlign.center,
),
);
@@ -296,10 +312,7 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
maxWidth: 500,
minHeight: 150,
),
child: KanjiCard(
characterWidget: promptWidget,
subtitle: '',
),
child: KanjiCard(characterWidget: promptWidget, subtitle: ''),
),
),
),
@@ -331,4 +344,4 @@ class CustomQuizScreenState extends State<CustomQuizScreen>
),
);
}
}
}