added vocabulary audio reading
This commit is contained in:
@@ -96,19 +96,27 @@ class VocabSrsItem {
|
||||
}) : lastAsked = lastAsked ?? DateTime.now();
|
||||
}
|
||||
|
||||
class PronunciationAudio {
|
||||
final String url;
|
||||
final String gender;
|
||||
|
||||
PronunciationAudio({required this.url, required this.gender});
|
||||
}
|
||||
|
||||
class VocabularyItem {
|
||||
final int id;
|
||||
final String characters;
|
||||
final List<String> meanings;
|
||||
final List<String> readings;
|
||||
final List<PronunciationAudio> pronunciationAudios;
|
||||
final Map<String, VocabSrsItem> srsItems = {};
|
||||
|
||||
VocabularyItem({
|
||||
required this.id,
|
||||
required this.characters,
|
||||
required this.meanings,
|
||||
required this.readings,
|
||||
});
|
||||
VocabularyItem(
|
||||
{required this.id,
|
||||
required this.characters,
|
||||
required this.meanings,
|
||||
required this.readings,
|
||||
required this.pronunciationAudios});
|
||||
|
||||
factory VocabularyItem.fromSubject(Map<String, dynamic> subj) {
|
||||
final int id = subj['id'] as int;
|
||||
@@ -116,6 +124,7 @@ class VocabularyItem {
|
||||
final String characters = (data['characters'] ?? '') as String;
|
||||
final List<String> meanings = <String>[];
|
||||
final List<String> readings = <String>[];
|
||||
final List<PronunciationAudio> pronunciationAudios = <PronunciationAudio>[];
|
||||
|
||||
if (data['meanings'] != null) {
|
||||
for (final m in data['meanings'] as List) {
|
||||
@@ -129,11 +138,26 @@ class VocabularyItem {
|
||||
}
|
||||
}
|
||||
|
||||
if (data['pronunciation_audios'] != null) {
|
||||
for (final audio in data['pronunciation_audios'] as List) {
|
||||
final url = audio['url'] as String?;
|
||||
final metadata = audio['metadata'] as Map<String, dynamic>?;
|
||||
final gender = metadata?['gender'] as String?;
|
||||
|
||||
if (url != null && gender != null) {
|
||||
pronunciationAudios.add(PronunciationAudio(
|
||||
url: url,
|
||||
gender: gender,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return VocabularyItem(
|
||||
id: id,
|
||||
characters: characters,
|
||||
meanings: meanings,
|
||||
readings: readings,
|
||||
);
|
||||
id: id,
|
||||
characters: characters,
|
||||
meanings: meanings,
|
||||
readings: readings,
|
||||
pronunciationAudios: pronunciationAudios);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,8 @@ class _VocabScreenState extends State<VocabScreen> {
|
||||
}
|
||||
|
||||
var items = await repo.loadVocabulary();
|
||||
if (items.isEmpty) {
|
||||
if (items.isEmpty ||
|
||||
items.every((item) => item.pronunciationAudios.isEmpty)) {
|
||||
setState(() {
|
||||
_status = 'Fetching deck...';
|
||||
});
|
||||
@@ -149,7 +150,6 @@ class _VocabScreenState extends State<VocabScreen> {
|
||||
_asked += 1;
|
||||
if (isCorrect) {
|
||||
_score += 1;
|
||||
_audioPlayer.play(AssetSource('sfx/confirm.mp3'));
|
||||
srsItem.srsStage += 1;
|
||||
} else {
|
||||
srsItem.srsStage = max(0, srsItem.srsStage - 1);
|
||||
@@ -183,7 +183,23 @@ class _VocabScreenState extends State<VocabScreen> {
|
||||
ScaffoldMessenger.of(context).showSnackBar(snack);
|
||||
}
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 900), _nextQuestion);
|
||||
if (isCorrect) {
|
||||
await _audioPlayer.play(AssetSource('sfx/confirm.mp3'));
|
||||
final maleAudios =
|
||||
current.pronunciationAudios.where((a) => a.gender == 'male');
|
||||
if (maleAudios.isNotEmpty) {
|
||||
try {
|
||||
await _audioPlayer.play(UrlSource(maleAudios.first.url));
|
||||
} catch (e) {
|
||||
// Ignore player errors
|
||||
}
|
||||
}
|
||||
await Future.delayed(const Duration(milliseconds: 400));
|
||||
} else {
|
||||
await Future.delayed(const Duration(milliseconds: 900));
|
||||
}
|
||||
|
||||
_nextQuestion();
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
@@ -23,7 +24,7 @@ class DeckRepository {
|
||||
|
||||
_db = await openDatabase(
|
||||
path,
|
||||
version: 5,
|
||||
version: 6,
|
||||
onCreate: (db, version) async {
|
||||
await db.execute(
|
||||
'''CREATE TABLE kanji (id INTEGER PRIMARY KEY, characters TEXT, meanings TEXT, onyomi TEXT, kunyomi TEXT)''');
|
||||
@@ -32,7 +33,7 @@ class DeckRepository {
|
||||
await db.execute(
|
||||
'''CREATE TABLE srs_items (kanjiId INTEGER, quizMode TEXT, readingType TEXT, srsStage INTEGER, lastAsked TEXT, PRIMARY KEY (kanjiId, quizMode, readingType))''');
|
||||
await db.execute(
|
||||
'''CREATE TABLE vocabulary (id INTEGER PRIMARY KEY, characters TEXT, meanings TEXT, readings TEXT)''');
|
||||
'''CREATE TABLE vocabulary (id INTEGER PRIMARY KEY, characters TEXT, meanings TEXT, readings TEXT, pronunciation_audios TEXT)''');
|
||||
await db.execute(
|
||||
'''CREATE TABLE srs_vocab_items (vocabId INTEGER, quizMode TEXT, srsStage INTEGER, lastAsked TEXT, PRIMARY KEY (vocabId, quizMode))''');
|
||||
},
|
||||
@@ -56,6 +57,13 @@ class DeckRepository {
|
||||
await db.execute(
|
||||
'''CREATE TABLE srs_vocab_items (vocabId INTEGER, quizMode TEXT, srsStage INTEGER, lastAsked TEXT, PRIMARY KEY (vocabId, quizMode))''');
|
||||
}
|
||||
if (oldVersion < 6) {
|
||||
try {
|
||||
await db.execute('ALTER TABLE vocabulary ADD COLUMN pronunciation_audios TEXT');
|
||||
} catch (_) {
|
||||
// Ignore error, column might already exist
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -257,6 +265,9 @@ class DeckRepository {
|
||||
final db = await _openDb();
|
||||
final batch = db.batch();
|
||||
for (final it in items) {
|
||||
final audios = it.pronunciationAudios
|
||||
.map((a) => {'url': a.url, 'gender': a.gender})
|
||||
.toList();
|
||||
batch.insert(
|
||||
'vocabulary',
|
||||
{
|
||||
@@ -264,6 +275,7 @@ class DeckRepository {
|
||||
'characters': it.characters,
|
||||
'meanings': it.meanings.join('|'),
|
||||
'readings': it.readings.join('|'),
|
||||
'pronunciation_audios': jsonEncode(audios),
|
||||
},
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
@@ -275,18 +287,36 @@ class DeckRepository {
|
||||
final db = await _openDb();
|
||||
final rows = await db.query('vocabulary');
|
||||
final vocabItems = rows
|
||||
.map((r) => VocabularyItem(
|
||||
id: r['id'] as int,
|
||||
characters: r['characters'] as String,
|
||||
meanings: (r['meanings'] as String)
|
||||
.split('|')
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toList(),
|
||||
readings: (r['readings'] as String)
|
||||
.split('|')
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toList(),
|
||||
))
|
||||
.map((r) {
|
||||
final audiosRaw = r['pronunciation_audios'] as String?;
|
||||
final List<PronunciationAudio> audios = [];
|
||||
if (audiosRaw != null && audiosRaw.isNotEmpty) {
|
||||
try {
|
||||
final decoded = jsonDecode(audiosRaw) as List;
|
||||
for (final audioData in decoded) {
|
||||
audios.add(PronunciationAudio(
|
||||
url: audioData['url'] as String,
|
||||
gender: audioData['gender'] as String,
|
||||
));
|
||||
}
|
||||
} catch (e) {
|
||||
// Error decoding, so we'll just have no audio for this item
|
||||
}
|
||||
}
|
||||
return VocabularyItem(
|
||||
id: r['id'] as int,
|
||||
characters: r['characters'] as String,
|
||||
meanings: (r['meanings'] as String)
|
||||
.split('|')
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toList(),
|
||||
readings: (r['readings'] as String)
|
||||
.split('|')
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toList(),
|
||||
pronunciationAudios: audios,
|
||||
);
|
||||
})
|
||||
.toList();
|
||||
|
||||
for (final item in vocabItems) {
|
||||
|
||||
Reference in New Issue
Block a user