Merge pull request 'add new listening comprehension mode' (#4) from listening_comprehension_mode into master
Reviewed-on: Crylia/wanikani-kanji-srs#4
This commit was merged in pull request #4.
This commit is contained in:
@@ -84,7 +84,7 @@ String _katakanaToHiragana(String input) {
|
|||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
enum VocabQuizMode { vocabToEnglish, englishToVocab }
|
enum VocabQuizMode { vocabToEnglish, englishToVocab, audioToEnglish }
|
||||||
|
|
||||||
class VocabSrsItem {
|
class VocabSrsItem {
|
||||||
final int vocabId;
|
final int vocabId;
|
||||||
|
|||||||
@@ -104,7 +104,19 @@ class _VocabScreenState extends State<VocabScreen> {
|
|||||||
void _nextQuestion() {
|
void _nextQuestion() {
|
||||||
if (_deck.isEmpty) return;
|
if (_deck.isEmpty) return;
|
||||||
|
|
||||||
_deck.sort((a, b) {
|
List<VocabularyItem> deck = _deck;
|
||||||
|
if (_mode == VocabQuizMode.audioToEnglish) {
|
||||||
|
deck = _deck.where((item) => item.pronunciationAudios.isNotEmpty).toList();
|
||||||
|
if (deck.isEmpty) {
|
||||||
|
setState(() {
|
||||||
|
_status = 'No vocabulary with audio found.';
|
||||||
|
_current = null;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deck.sort((a, b) {
|
||||||
final aSrsItem = a.srsItems[_mode.toString()] ??
|
final aSrsItem = a.srsItems[_mode.toString()] ??
|
||||||
VocabSrsItem(vocabId: a.id, quizMode: _mode);
|
VocabSrsItem(vocabId: a.id, quizMode: _mode);
|
||||||
final bSrsItem = b.srsItems[_mode.toString()] ??
|
final bSrsItem = b.srsItems[_mode.toString()] ??
|
||||||
@@ -117,13 +129,17 @@ class _VocabScreenState extends State<VocabScreen> {
|
|||||||
return aSrsItem.lastAsked.compareTo(bSrsItem.lastAsked);
|
return aSrsItem.lastAsked.compareTo(bSrsItem.lastAsked);
|
||||||
});
|
});
|
||||||
|
|
||||||
_current = _deck.first;
|
_current = deck.first;
|
||||||
|
if (_mode == VocabQuizMode.audioToEnglish) {
|
||||||
|
_playCurrentAudio();
|
||||||
|
}
|
||||||
|
|
||||||
_correctAnswers = [];
|
_correctAnswers = [];
|
||||||
_options = [];
|
_options = [];
|
||||||
|
|
||||||
switch (_mode) {
|
switch (_mode) {
|
||||||
case VocabQuizMode.vocabToEnglish:
|
case VocabQuizMode.vocabToEnglish:
|
||||||
|
case VocabQuizMode.audioToEnglish:
|
||||||
_correctAnswers = [_current!.meanings.first];
|
_correctAnswers = [_current!.meanings.first];
|
||||||
_options = [
|
_options = [
|
||||||
_correctAnswers.first,
|
_correctAnswers.first,
|
||||||
@@ -144,6 +160,19 @@ class _VocabScreenState extends State<VocabScreen> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _playCurrentAudio() async {
|
||||||
|
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);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await _audioPlayer.play(UrlSource(audioUrl));
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore player errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _answer(String option) async {
|
void _answer(String option) async {
|
||||||
final isCorrect = _correctAnswers
|
final isCorrect = _correctAnswers
|
||||||
.map((a) => a.toLowerCase().trim())
|
.map((a) => a.toLowerCase().trim())
|
||||||
@@ -200,7 +229,7 @@ class _VocabScreenState extends State<VocabScreen> {
|
|||||||
if (_playCorrectSound) {
|
if (_playCorrectSound) {
|
||||||
await _audioPlayer.play(AssetSource('sfx/confirm.mp3'));
|
await _audioPlayer.play(AssetSource('sfx/confirm.mp3'));
|
||||||
}
|
}
|
||||||
if (_playAudio) {
|
if (_playAudio && _mode != VocabQuizMode.audioToEnglish) {
|
||||||
final maleAudios =
|
final maleAudios =
|
||||||
current.pronunciationAudios.where((a) => a.gender == 'male');
|
current.pronunciationAudios.where((a) => a.gender == 'male');
|
||||||
if (maleAudios.isNotEmpty) {
|
if (maleAudios.isNotEmpty) {
|
||||||
@@ -228,15 +257,29 @@ class _VocabScreenState extends State<VocabScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
String prompt = '';
|
Widget promptWidget;
|
||||||
|
|
||||||
|
if (_current == null) {
|
||||||
|
promptWidget = const SizedBox.shrink();
|
||||||
|
} else if (_mode == VocabQuizMode.audioToEnglish) {
|
||||||
|
promptWidget = IconButton(
|
||||||
|
icon: const Icon(Icons.volume_up, color: Colors.white, size: 64),
|
||||||
|
onPressed: _playCurrentAudio,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
String promptText = '';
|
||||||
switch (_mode) {
|
switch (_mode) {
|
||||||
case VocabQuizMode.vocabToEnglish:
|
case VocabQuizMode.vocabToEnglish:
|
||||||
prompt = _current?.characters ?? '';
|
promptText = _current?.characters ?? '';
|
||||||
break;
|
break;
|
||||||
case VocabQuizMode.englishToVocab:
|
case VocabQuizMode.englishToVocab:
|
||||||
prompt = _current != null ? _toTitleCase(_current!.meanings.first) : '';
|
promptText = _current != null ? _toTitleCase(_current!.meanings.first) : '';
|
||||||
break;
|
break;
|
||||||
|
case VocabQuizMode.audioToEnglish:
|
||||||
|
// Handled above
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
promptWidget = Text(promptText, style: const TextStyle(fontSize: 48, color: Colors.white));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -282,6 +325,7 @@ class _VocabScreenState extends State<VocabScreen> {
|
|||||||
children: [
|
children: [
|
||||||
_buildChoiceChip('Vocab→English', VocabQuizMode.vocabToEnglish),
|
_buildChoiceChip('Vocab→English', VocabQuizMode.vocabToEnglish),
|
||||||
_buildChoiceChip('English→Vocab', VocabQuizMode.englishToVocab),
|
_buildChoiceChip('English→Vocab', VocabQuizMode.englishToVocab),
|
||||||
|
_buildChoiceChip('Listening', VocabQuizMode.audioToEnglish),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 18),
|
const SizedBox(height: 18),
|
||||||
@@ -295,7 +339,7 @@ class _VocabScreenState extends State<VocabScreen> {
|
|||||||
minHeight: 150,
|
minHeight: 150,
|
||||||
),
|
),
|
||||||
child: KanjiCard(
|
child: KanjiCard(
|
||||||
characters: prompt,
|
characterWidget: promptWidget,
|
||||||
subtitle: '',
|
subtitle: '',
|
||||||
backgroundColor: const Color(0xFF1E1E1E),
|
backgroundColor: const Color(0xFF1E1E1E),
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
@@ -326,6 +370,7 @@ class _VocabScreenState extends State<VocabScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ChoiceChip _buildChoiceChip(String label, VocabQuizMode mode) {
|
ChoiceChip _buildChoiceChip(String label, VocabQuizMode mode) {
|
||||||
|
|||||||
@@ -2,13 +2,15 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
class KanjiCard extends StatelessWidget {
|
class KanjiCard extends StatelessWidget {
|
||||||
final String characters;
|
final String characters;
|
||||||
|
final Widget? characterWidget;
|
||||||
final String subtitle;
|
final String subtitle;
|
||||||
final Color? backgroundColor;
|
final Color? backgroundColor;
|
||||||
final Color? textColor;
|
final Color? textColor;
|
||||||
|
|
||||||
const KanjiCard({
|
const KanjiCard({
|
||||||
super.key,
|
super.key,
|
||||||
required this.characters,
|
this.characters = '',
|
||||||
|
this.characterWidget,
|
||||||
this.subtitle = '',
|
this.subtitle = '',
|
||||||
this.backgroundColor,
|
this.backgroundColor,
|
||||||
this.textColor,
|
this.textColor,
|
||||||
@@ -32,6 +34,7 @@ class KanjiCard extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
|
characterWidget ??
|
||||||
Text(
|
Text(
|
||||||
characters,
|
characters,
|
||||||
style: theme.textTheme.headlineMedium?.copyWith(
|
style: theme.textTheme.headlineMedium?.copyWith(
|
||||||
@@ -44,7 +47,7 @@ class KanjiCard extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
subtitle,
|
subtitle,
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
color: fgColor.withValues(alpha: 0.7),
|
color: fgColor.withAlpha((255 * 0.7).round()),
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user