themes
This commit is contained in:
@@ -31,7 +31,8 @@ class VocabScreen extends StatefulWidget {
|
||||
State<VocabScreen> createState() => _VocabScreenState();
|
||||
}
|
||||
|
||||
class _VocabScreenState extends State<VocabScreen> with SingleTickerProviderStateMixin {
|
||||
class _VocabScreenState extends State<VocabScreen>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late TabController _tabController;
|
||||
List<VocabularyItem> _deck = [];
|
||||
bool _loading = false;
|
||||
@@ -150,7 +151,9 @@ class _VocabScreenState extends State<VocabScreen> with SingleTickerProviderStat
|
||||
|
||||
List<VocabularyItem> currentDeckForMode = _deck;
|
||||
if (mode == VocabQuizMode.audioToEnglish) {
|
||||
currentDeckForMode = _deck.where((item) => item.pronunciationAudios.isNotEmpty).toList();
|
||||
currentDeckForMode = _deck
|
||||
.where((item) => item.pronunciationAudios.isNotEmpty)
|
||||
.toList();
|
||||
if (currentDeckForMode.isEmpty) {
|
||||
setState(() {
|
||||
_status = 'No vocabulary with audio found.';
|
||||
@@ -160,13 +163,16 @@ class _VocabScreenState extends State<VocabScreen> with SingleTickerProviderStat
|
||||
}
|
||||
}
|
||||
|
||||
// If it's a new session or we've gone through all shuffled items, re-shuffle
|
||||
if (quizState.shuffledDeck.isEmpty || quizState.currentIndex >= quizState.shuffledDeck.length) {
|
||||
quizState.shuffledDeck = currentDeckForMode.toList(); // Start with a fresh copy
|
||||
// Apply sorting based on SRS stages here, but only once per shuffle
|
||||
if (quizState.shuffledDeck.isEmpty ||
|
||||
quizState.currentIndex >= quizState.shuffledDeck.length) {
|
||||
quizState.shuffledDeck = currentDeckForMode.toList();
|
||||
quizState.shuffledDeck.sort((a, b) {
|
||||
final aSrsItem = a.srsItems[mode.toString()] ?? VocabSrsItem(vocabId: a.id, quizMode: mode);
|
||||
final bSrsItem = b.srsItems[mode.toString()] ?? VocabSrsItem(vocabId: b.id, quizMode: mode);
|
||||
final aSrsItem =
|
||||
a.srsItems[mode.toString()] ??
|
||||
VocabSrsItem(vocabId: a.id, quizMode: mode);
|
||||
final bSrsItem =
|
||||
b.srsItems[mode.toString()] ??
|
||||
VocabSrsItem(vocabId: b.id, quizMode: mode);
|
||||
final stageComparison = aSrsItem.srsStage.compareTo(bSrsItem.srsStage);
|
||||
if (stageComparison != 0) {
|
||||
return stageComparison;
|
||||
@@ -176,8 +182,8 @@ class _VocabScreenState extends State<VocabScreen> with SingleTickerProviderStat
|
||||
quizState.currentIndex = 0;
|
||||
}
|
||||
|
||||
quizState.current = quizState.shuffledDeck[quizState.currentIndex]; // Pick from shuffled deck
|
||||
quizState.currentIndex++; // Advance index
|
||||
quizState.current = quizState.shuffledDeck[quizState.currentIndex];
|
||||
quizState.currentIndex++;
|
||||
|
||||
quizState.key = UniqueKey();
|
||||
if (mode == VocabQuizMode.audioToEnglish) {
|
||||
@@ -195,16 +201,15 @@ class _VocabScreenState extends State<VocabScreen> with SingleTickerProviderStat
|
||||
quizState.correctAnswers = [quizState.current!.meanings.first];
|
||||
quizState.options = [
|
||||
quizState.correctAnswers.first,
|
||||
..._dg.generateVocabMeanings(quizState.current!, _deck, 3)
|
||||
].map(_toTitleCase).toList()
|
||||
..shuffle();
|
||||
..._dg.generateVocabMeanings(quizState.current!, _deck, 3),
|
||||
].map(_toTitleCase).toList()..shuffle();
|
||||
break;
|
||||
|
||||
case VocabQuizMode.englishToVocab:
|
||||
quizState.correctAnswers = [quizState.current!.characters];
|
||||
quizState.options = [
|
||||
quizState.correctAnswers.first,
|
||||
..._dg.generateVocab(quizState.current!, _deck, 3)
|
||||
..._dg.generateVocab(quizState.current!, _deck, 3),
|
||||
]..shuffle();
|
||||
break;
|
||||
}
|
||||
@@ -218,14 +223,16 @@ class _VocabScreenState extends State<VocabScreen> with SingleTickerProviderStat
|
||||
final current = _currentQuizState.current;
|
||||
if (current == null || current.pronunciationAudios.isEmpty) return;
|
||||
|
||||
final maleAudios = current.pronunciationAudios.where((a) => a.gender == 'male');
|
||||
final audioUrl = (maleAudios.isNotEmpty ? maleAudios.first.url : current.pronunciationAudios.first.url);
|
||||
final maleAudios = current.pronunciationAudios.where(
|
||||
(a) => a.gender == 'male',
|
||||
);
|
||||
final audioUrl = (maleAudios.isNotEmpty
|
||||
? maleAudios.first.url
|
||||
: current.pronunciationAudios.first.url);
|
||||
|
||||
try {
|
||||
await _audioPlayer.play(UrlSource(audioUrl));
|
||||
} catch (e) {
|
||||
// Ignore player errors
|
||||
}
|
||||
} finally {}
|
||||
}
|
||||
|
||||
void _answer(String option) async {
|
||||
@@ -248,7 +255,7 @@ class _VocabScreenState extends State<VocabScreen> with SingleTickerProviderStat
|
||||
quizState.asked += 1;
|
||||
quizState.selectedOption = option;
|
||||
quizState.showResult = true;
|
||||
setState(() {}); // Trigger UI rebuild to show selected/correct colors
|
||||
setState(() {});
|
||||
|
||||
if (isCorrect) {
|
||||
quizState.score += 1;
|
||||
@@ -269,28 +276,28 @@ class _VocabScreenState extends State<VocabScreen> with SingleTickerProviderStat
|
||||
? _toTitleCase(quizState.correctAnswers.first)
|
||||
: quizState.correctAnswers.first;
|
||||
|
||||
if (!mounted) return;
|
||||
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);
|
||||
}
|
||||
ScaffoldMessenger.of(context).showSnackBar(snack);
|
||||
|
||||
if (isCorrect) {
|
||||
if (_playCorrectSound) {
|
||||
await _audioPlayer.play(AssetSource('sfx/confirm.mp3'));
|
||||
}
|
||||
if (_playAudio && mode != VocabQuizMode.audioToEnglish) {
|
||||
final maleAudios =
|
||||
current.pronunciationAudios.where((a) => a.gender == 'male');
|
||||
final maleAudios = current.pronunciationAudios.where(
|
||||
(a) => a.gender == 'male',
|
||||
);
|
||||
if (maleAudios.isNotEmpty) {
|
||||
final completer = Completer<void>();
|
||||
final sub = _audioPlayer.onPlayerComplete.listen((event) {
|
||||
@@ -300,19 +307,15 @@ class _VocabScreenState extends State<VocabScreen> with SingleTickerProviderStat
|
||||
try {
|
||||
await _audioPlayer.play(UrlSource(maleAudios.first.url));
|
||||
await completer.future.timeout(const Duration(seconds: 5));
|
||||
} catch (e) {
|
||||
// Ignore player errors
|
||||
} finally {
|
||||
await sub.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No fixed delay for incorrect answers
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isAnswering = true; // Disable input after showing result
|
||||
_isAnswering = true;
|
||||
});
|
||||
|
||||
_nextQuestion();
|
||||
@@ -327,13 +330,14 @@ class _VocabScreenState extends State<VocabScreen> with SingleTickerProviderStat
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text('WaniKani API key is not set.'),
|
||||
Text('WaniKani API key is not set.', style: TextStyle(color: Theme.of(context).colorScheme.onSurface)),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (_) => const SettingsScreen()),
|
||||
);
|
||||
if (!mounted) return;
|
||||
_loadDeck();
|
||||
},
|
||||
child: const Text('Go to Settings'),
|
||||
@@ -358,11 +362,7 @@ class _VocabScreenState extends State<VocabScreen> with SingleTickerProviderStat
|
||||
),
|
||||
body: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
_buildQuizPage(0),
|
||||
_buildQuizPage(1),
|
||||
_buildQuizPage(2),
|
||||
],
|
||||
children: [_buildQuizPage(0), _buildQuizPage(1), _buildQuizPage(2)],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -377,7 +377,7 @@ class _VocabScreenState extends State<VocabScreen> with SingleTickerProviderStat
|
||||
promptWidget = const SizedBox.shrink();
|
||||
} else if (mode == VocabQuizMode.audioToEnglish) {
|
||||
promptWidget = IconButton(
|
||||
icon: const Icon(Icons.volume_up, color: Colors.white, size: 64),
|
||||
icon: Icon(Icons.volume_up, color: Theme.of(context).colorScheme.onSurface, size: 64),
|
||||
onPressed: _playCurrentAudio,
|
||||
);
|
||||
} else {
|
||||
@@ -390,10 +390,12 @@ class _VocabScreenState extends State<VocabScreen> with SingleTickerProviderStat
|
||||
promptText = _toTitleCase(quizState.current!.meanings.first);
|
||||
break;
|
||||
case VocabQuizMode.audioToEnglish:
|
||||
// Handled above
|
||||
break;
|
||||
}
|
||||
promptWidget = Text(promptText, style: const TextStyle(fontSize: 48, color: Colors.white));
|
||||
promptWidget = Text(
|
||||
promptText,
|
||||
style: TextStyle(fontSize: 48, color: Theme.of(context).colorScheme.onSurface),
|
||||
);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
@@ -403,13 +405,9 @@ class _VocabScreenState extends State<VocabScreen> with SingleTickerProviderStat
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
_status,
|
||||
),
|
||||
),
|
||||
Expanded(child: Text(_status, style: TextStyle(color: Theme.of(context).colorScheme.onSurface))),
|
||||
if (_loading)
|
||||
const CircularProgressIndicator(color: Colors.blueAccent),
|
||||
CircularProgressIndicator(color: Theme.of(context).colorScheme.primary),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
@@ -422,10 +420,7 @@ class _VocabScreenState extends State<VocabScreen> with SingleTickerProviderStat
|
||||
maxWidth: 500,
|
||||
minHeight: 150,
|
||||
),
|
||||
child: KanjiCard(
|
||||
characterWidget: promptWidget,
|
||||
subtitle: '',
|
||||
),
|
||||
child: KanjiCard(characterWidget: promptWidget, subtitle: ''),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -445,7 +440,7 @@ class _VocabScreenState extends State<VocabScreen> with SingleTickerProviderStat
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Score: ${quizState.score} / ${quizState.asked}',
|
||||
style: const TextStyle(color: Colors.white),
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -454,4 +449,4 @@ class _VocabScreenState extends State<VocabScreen> with SingleTickerProviderStat
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user