enum QuizMode { kanjiToEnglish, englishToKanji, reading } class SrsItem { final int kanjiId; final QuizMode quizMode; final String? readingType; // 'onyomi' or 'kunyomi' int srsStage; DateTime lastAsked; SrsItem({ required this.kanjiId, required this.quizMode, this.readingType, this.srsStage = 0, DateTime? lastAsked, }) : lastAsked = lastAsked ?? DateTime.now(); } class KanjiItem { final int id; final int level; final String characters; final List meanings; final List onyomi; final List kunyomi; final Map srsItems = {}; KanjiItem({ required this.id, required this.level, required this.characters, required this.meanings, required this.onyomi, required this.kunyomi, }); factory KanjiItem.fromSubject(Map subj) { final int id = subj['id'] as int; final data = subj['data'] as Map; final int level = data['level'] as int; final String characters = (data['characters'] ?? '') as String; final List meanings = []; final List onyomi = []; final List kunyomi = []; if (data['meanings'] != null) { for (final m in data['meanings'] as List) { meanings.add((m['meaning'] as String).toLowerCase()); } } if (data['readings'] != null) { for (final r in data['readings'] as List) { final typ = r['type'] as String? ?? ''; final reading = r['reading'] as String? ?? ''; if (typ == 'onyomi') { onyomi.add(_katakanaToHiragana(reading)); } else if (typ == 'kunyomi') { kunyomi.add(reading); } } } return KanjiItem( id: id, level: level, characters: characters, meanings: meanings, onyomi: onyomi, kunyomi: kunyomi, ); } } String _katakanaToHiragana(String input) { final buf = StringBuffer(); for (final r in input.runes) { if (r >= 0x30A1 && r <= 0x30FA) { buf.writeCharCode(r - 0x60); } else { buf.writeCharCode(r); } } return buf.toString(); } enum VocabQuizMode { vocabToEnglish, englishToVocab, audioToEnglish } class VocabSrsItem { final int vocabId; final VocabQuizMode quizMode; int srsStage; DateTime lastAsked; VocabSrsItem({ required this.vocabId, required this.quizMode, this.srsStage = 0, DateTime? lastAsked, }) : 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 int level; final String characters; final List meanings; final List readings; final List pronunciationAudios; final Map srsItems = {}; VocabularyItem( {required this.id, required this.level, required this.characters, required this.meanings, required this.readings, required this.pronunciationAudios}); factory VocabularyItem.fromSubject(Map subj) { final int id = subj['id'] as int; final data = subj['data'] as Map; final int level = data['level'] as int; final String characters = (data['characters'] ?? '') as String; final List meanings = []; final List readings = []; final List pronunciationAudios = []; if (data['meanings'] != null) { for (final m in data['meanings'] as List) { meanings.add((m['meaning'] as String).toLowerCase()); } } if (data['readings'] != null) { for (final r in data['readings'] as List) { readings.add(r['reading'] as String); } } 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?; final gender = metadata?['gender'] as String?; if (url != null && gender != null) { pronunciationAudios.add(PronunciationAudio( url: url, gender: gender, )); } } } return VocabularyItem( id: id, level: level, characters: characters, meanings: meanings, readings: readings, pronunciationAudios: pronunciationAudios); } }