themes and some refractoring

This commit is contained in:
Rene Kievits
2025-10-31 17:18:33 +01:00
parent 4eb488e28c
commit de3501c3e4
15 changed files with 443 additions and 414 deletions

View File

@@ -3,7 +3,8 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/kanji_item.dart';
import '../models/vocabulary_item.dart';
import '../models/srs_item.dart';
import 'package:hirameki_srs/src/services/vocab_deck_repository.dart';
import '../services/distractor_generator.dart';
import '../widgets/kanji_card.dart';
@@ -53,9 +54,6 @@ class _VocabScreenState extends State<VocabScreen>
super.initState();
_tabController = TabController(length: 3, vsync: this);
_tabController.addListener(() {
if (_tabController.indexIsChanging) {
_nextQuestion();
}
setState(() {});
});
_loadSettings();
@@ -130,16 +128,16 @@ class _VocabScreenState extends State<VocabScreen>
.join(' ');
}
VocabQuizMode _modeForIndex(int index) {
QuizMode _modeForIndex(int index) {
switch (index) {
case 0:
return VocabQuizMode.vocabToEnglish;
return QuizMode.vocabToEnglish;
case 1:
return VocabQuizMode.englishToVocab;
return QuizMode.englishToVocab;
case 2:
return VocabQuizMode.audioToEnglish;
return QuizMode.audioToEnglish;
default:
return VocabQuizMode.vocabToEnglish;
return QuizMode.vocabToEnglish;
}
}
@@ -150,7 +148,7 @@ class _VocabScreenState extends State<VocabScreen>
final mode = _modeForIndex(index ?? _tabController.index);
List<VocabularyItem> currentDeckForMode = _deck;
if (mode == VocabQuizMode.audioToEnglish) {
if (mode == QuizMode.audioToEnglish) {
currentDeckForMode = _deck
.where((item) => item.pronunciationAudios.isNotEmpty)
.toList();
@@ -169,10 +167,10 @@ class _VocabScreenState extends State<VocabScreen>
quizState.shuffledDeck.sort((a, b) {
final aSrsItem =
a.srsItems[mode.toString()] ??
VocabSrsItem(vocabId: a.id, quizMode: mode);
SrsItem(subjectId: a.id, quizMode: mode);
final bSrsItem =
b.srsItems[mode.toString()] ??
VocabSrsItem(vocabId: b.id, quizMode: mode);
SrsItem(subjectId: b.id, quizMode: mode);
final stageComparison = aSrsItem.srsStage.compareTo(bSrsItem.srsStage);
if (stageComparison != 0) {
return stageComparison;
@@ -186,18 +184,14 @@ class _VocabScreenState extends State<VocabScreen>
quizState.currentIndex++;
quizState.key = UniqueKey();
if (mode == VocabQuizMode.audioToEnglish) {
_playCurrentAudio();
}
quizState.correctAnswers = [];
quizState.options = [];
quizState.selectedOption = null;
quizState.showResult = false;
switch (mode) {
case VocabQuizMode.vocabToEnglish:
case VocabQuizMode.audioToEnglish:
case QuizMode.vocabToEnglish:
case QuizMode.audioToEnglish:
quizState.correctAnswers = [quizState.current!.meanings.first];
quizState.options = [
quizState.correctAnswers.first,
@@ -205,13 +199,15 @@ class _VocabScreenState extends State<VocabScreen>
].map(_toTitleCase).toList()..shuffle();
break;
case VocabQuizMode.englishToVocab:
case QuizMode.englishToVocab:
quizState.correctAnswers = [quizState.current!.characters];
quizState.options = [
quizState.correctAnswers.first,
..._dg.generateVocab(quizState.current!, _deck, 3),
]..shuffle();
break;
default:
break;
}
setState(() {
@@ -219,10 +215,12 @@ class _VocabScreenState extends State<VocabScreen>
});
}
Future<void> _playCurrentAudio() async {
Future<void> _playCurrentAudio({bool playOnLoad = false}) async {
final current = _currentQuizState.current;
if (current == null || current.pronunciationAudios.isEmpty) return;
if (playOnLoad && !_playAudio) return;
final maleAudios = current.pronunciationAudios.where(
(a) => a.gender == 'male',
);
@@ -250,7 +248,7 @@ class _VocabScreenState extends State<VocabScreen>
var srsItemNullable = current.srsItems[srsKey];
final isNew = srsItemNullable == null;
final srsItem =
srsItemNullable ?? VocabSrsItem(vocabId: current.id, quizMode: mode);
srsItemNullable ?? SrsItem(subjectId: current.id, quizMode: mode);
quizState.asked += 1;
quizState.selectedOption = option;
@@ -272,7 +270,7 @@ class _VocabScreenState extends State<VocabScreen>
await repo.updateVocabSrsItem(srsItem);
}
final correctDisplay = (mode == VocabQuizMode.vocabToEnglish)
final correctDisplay = (mode == QuizMode.vocabToEnglish)
? _toTitleCase(quizState.correctAnswers.first)
: quizState.correctAnswers.first;
@@ -281,7 +279,9 @@ class _VocabScreenState extends State<VocabScreen>
content: Text(
isCorrect ? 'Correct!' : 'Wrong — correct: $correctDisplay',
style: TextStyle(
color: isCorrect ? Theme.of(context).colorScheme.tertiary : Theme.of(context).colorScheme.error,
color: isCorrect
? Theme.of(context).colorScheme.tertiary
: Theme.of(context).colorScheme.error,
fontWeight: FontWeight.bold,
),
),
@@ -294,7 +294,7 @@ class _VocabScreenState extends State<VocabScreen>
if (_playCorrectSound) {
await _audioPlayer.play(AssetSource('sfx/confirm.mp3'));
}
if (_playAudio && mode != VocabQuizMode.audioToEnglish) {
if (_playAudio) {
final maleAudios = current.pronunciationAudios.where(
(a) => a.gender == 'male',
);
@@ -330,7 +330,12 @@ class _VocabScreenState extends State<VocabScreen>
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('WaniKani API key is not set.', style: TextStyle(color: Theme.of(context).colorScheme.onSurface)),
Text(
'WaniKani API key is not set.',
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () async {
@@ -375,26 +380,35 @@ class _VocabScreenState extends State<VocabScreen>
if (quizState.current == null) {
promptWidget = const SizedBox.shrink();
} else if (mode == VocabQuizMode.audioToEnglish) {
} else if (mode == QuizMode.audioToEnglish) {
promptWidget = IconButton(
icon: Icon(Icons.volume_up, color: Theme.of(context).colorScheme.onSurface, size: 64),
icon: Icon(
Icons.volume_up,
color: Theme.of(context).colorScheme.onSurface,
size: 64,
),
onPressed: _playCurrentAudio,
);
} else {
String promptText = '';
switch (mode) {
case VocabQuizMode.vocabToEnglish:
case QuizMode.vocabToEnglish:
promptText = quizState.current!.characters;
break;
case VocabQuizMode.englishToVocab:
case QuizMode.englishToVocab:
promptText = _toTitleCase(quizState.current!.meanings.first);
break;
case VocabQuizMode.audioToEnglish:
case QuizMode.audioToEnglish:
break;
default:
break;
}
promptWidget = Text(
promptText,
style: TextStyle(fontSize: 48, color: Theme.of(context).colorScheme.onSurface),
style: TextStyle(
fontSize: 48,
color: Theme.of(context).colorScheme.onSurface,
),
);
}
@@ -405,9 +419,18 @@ class _VocabScreenState extends State<VocabScreen>
children: [
Row(
children: [
Expanded(child: Text(_status, style: TextStyle(color: Theme.of(context).colorScheme.onSurface))),
Expanded(
child: Text(
_status,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
),
),
),
if (_loading)
CircularProgressIndicator(color: Theme.of(context).colorScheme.primary),
CircularProgressIndicator(
color: Theme.of(context).colorScheme.primary,
),
],
),
const SizedBox(height: 18),
@@ -440,7 +463,9 @@ class _VocabScreenState extends State<VocabScreen>
const SizedBox(height: 8),
Text(
'Score: ${quizState.score} / ${quizState.asked}',
style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
),
),
],
),