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

@@ -39,7 +39,8 @@ class HomeScreen extends StatefulWidget {
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateMixin {
class _HomeScreenState extends State<HomeScreen>
with SingleTickerProviderStateMixin {
late TabController _tabController;
List<KanjiItem> _deck = [];
bool _loading = false;
@@ -171,8 +172,10 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
_deck.sort((a, b) {
int getSrsStage(KanjiItem item) {
if (mode == QuizMode.reading) {
final onyomiStage = item.srsItems['${QuizMode.reading}onyomi']?.srsStage;
final kunyomiStage = item.srsItems['${QuizMode.reading}kunyomi']?.srsStage;
final onyomiStage =
item.srsItems['${QuizMode.reading}onyomi']?.srsStage;
final kunyomiStage =
item.srsItems['${QuizMode.reading}kunyomi']?.srsStage;
if (onyomiStage != null && kunyomiStage != null) {
return min(onyomiStage, kunyomiStage);
@@ -184,8 +187,10 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
DateTime getLastAsked(KanjiItem item) {
if (mode == QuizMode.reading) {
final onyomiLastAsked = item.srsItems['${QuizMode.reading}onyomi']?.lastAsked;
final kunyomiLastAsked = item.srsItems['${QuizMode.reading}kunyomi']?.lastAsked;
final onyomiLastAsked =
item.srsItems['${QuizMode.reading}onyomi']?.lastAsked;
final kunyomiLastAsked =
item.srsItems['${QuizMode.reading}kunyomi']?.lastAsked;
if (onyomiLastAsked != null && kunyomiLastAsked != null) {
return onyomiLastAsked.isBefore(kunyomiLastAsked)
@@ -227,16 +232,15 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
quizState.correctAnswers = [quizState.current!.meanings.first];
quizState.options = [
quizState.correctAnswers.first,
..._dg.generateMeanings(quizState.current!, _deck, 3)
].map(_toTitleCase).toList()
..shuffle();
..._dg.generateMeanings(quizState.current!, _deck, 3),
].map(_toTitleCase).toList()..shuffle();
break;
case QuizMode.englishToKanji:
quizState.correctAnswers = [quizState.current!.characters];
quizState.options = [
quizState.correctAnswers.first,
..._dg.generateKanji(quizState.current!, _deck, 3)
..._dg.generateKanji(quizState.current!, _deck, 3),
]..shuffle();
break;
@@ -249,16 +253,18 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
? _deck.expand((k) => k.onyomi)
: _deck.expand((k) => k.kunyomi);
final distractors = readingsSource
.where((r) => !quizState.correctAnswers.contains(r))
.toSet()
.toList()
final distractors =
readingsSource
.where((r) => !quizState.correctAnswers.contains(r))
.toSet()
.toList()
..shuffle();
quizState.options = ([
quizState.correctAnswers[_random.nextInt(quizState.correctAnswers.length)],
...distractors.take(3)
])
..shuffle();
quizState.correctAnswers[_random.nextInt(
quizState.correctAnswers.length,
)],
...distractors.take(3),
])..shuffle();
break;
}
@@ -279,26 +285,29 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
String readingType = '';
if (mode == QuizMode.reading) {
readingType = quizState.readingHint.contains("on'yomi") ? 'onyomi' : 'kunyomi';
readingType = quizState.readingHint.contains("on'yomi")
? 'onyomi'
: 'kunyomi';
}
final srsKey = mode.toString() + readingType;
var srsItem = current.srsItems[srsKey];
final isNew = srsItem == null;
final srsItemForUpdate = srsItem ??=
SrsItem(kanjiId: current.id, quizMode: mode, readingType: readingType);
quizState.asked += 1;
quizState.selectedOption = option;
quizState.showResult = true;
setState(() {}); // Trigger UI rebuild to show selected/correct colors
if (isCorrect) {
final srsItemForUpdate = srsItem ??= SrsItem(
kanjiId: current.id,
quizMode: mode,
readingType: readingType,
);
quizState.asked += 1;
quizState.selectedOption = option;
quizState.showResult = true;
setState(() {});
if (isCorrect) {
quizState.score += 1;
srsItemForUpdate.srsStage += 1;
if (_playCorrectSound) {
@@ -310,6 +319,9 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
srsItemForUpdate.lastAsked = DateTime.now();
current.srsItems[srsKey] = srsItemForUpdate;
final scaffoldMessenger = ScaffoldMessenger.of(context);
final theme = Theme.of(context);
if (isNew) {
await repo.insertSrsItem(srsItemForUpdate);
} else {
@@ -319,26 +331,28 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
final correctDisplay = (mode == QuizMode.kanjiToEnglish)
? _toTitleCase(quizState.correctAnswers.first)
: (mode == QuizMode.reading
? quizState.correctAnswers.join(', ')
: quizState.correctAnswers.first);
? quizState.correctAnswers.join(', ')
: quizState.correctAnswers.first);
final snack = SnackBar(
content: Text(
isCorrect ? 'Correct!' : 'Wrong — correct: $correctDisplay',
style: TextStyle(
color: isCorrect ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.error,
color: isCorrect
? theme.colorScheme.primary
: theme.colorScheme.error,
fontWeight: FontWeight.bold,
),
),
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
backgroundColor: theme.colorScheme.surfaceContainerHighest,
duration: const Duration(milliseconds: 900),
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(snack);
scaffoldMessenger.showSnackBar(snack);
}
setState(() {
_isAnswering = true; // Disable input after showing result
_isAnswering = true;
});
Future.delayed(const Duration(milliseconds: 900), () {
@@ -357,7 +371,12 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
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 {
@@ -389,11 +408,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
backgroundColor: Theme.of(context).colorScheme.surface,
body: TabBarView(
controller: _tabController,
children: [
_buildQuizPage(0),
_buildQuizPage(1),
_buildQuizPage(2),
],
children: [_buildQuizPage(0), _buildQuizPage(1), _buildQuizPage(2)],
),
);
}
@@ -430,11 +445,15 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
Expanded(
child: Text(
_status,
style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
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),
@@ -472,7 +491,9 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
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,
),
),
],
),
@@ -481,4 +502,4 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
),
);
}
}
}