themes and some refractoring
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import '../models/subject.dart';
|
||||||
|
import '../models/kanji_item.dart';
|
||||||
|
import '../models/vocabulary_item.dart';
|
||||||
|
|
||||||
class WkClient {
|
class WkClient {
|
||||||
final String apiKey;
|
final String apiKey;
|
||||||
@@ -85,4 +88,13 @@ class WkClient {
|
|||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
static Subject createSubjectFromMap(Map<String, dynamic> map) {
|
||||||
|
final String object = map['object'];
|
||||||
|
if (object == 'kanji') {
|
||||||
|
return KanjiItem.fromSubject(map);
|
||||||
|
} else if (object == 'vocabulary') {
|
||||||
|
return VocabularyItem.fromSubject(map);
|
||||||
|
}
|
||||||
|
throw Exception('Unknown subject type: $object');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +1,24 @@
|
|||||||
enum QuizMode { kanjiToEnglish, englishToKanji, reading }
|
import 'subject.dart';
|
||||||
|
|
||||||
class SrsItem {
|
class KanjiItem extends Subject {
|
||||||
final int kanjiId;
|
|
||||||
final QuizMode quizMode;
|
|
||||||
final String? readingType;
|
|
||||||
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<String> meanings;
|
|
||||||
final List<String> onyomi;
|
final List<String> onyomi;
|
||||||
final List<String> kunyomi;
|
final List<String> kunyomi;
|
||||||
final Map<String, SrsItem> srsItems = {};
|
|
||||||
|
|
||||||
KanjiItem({
|
KanjiItem({
|
||||||
required this.id,
|
required super.id,
|
||||||
required this.level,
|
required super.level,
|
||||||
required this.characters,
|
required super.characters,
|
||||||
required this.meanings,
|
required super.meanings,
|
||||||
required this.onyomi,
|
required this.onyomi,
|
||||||
required this.kunyomi,
|
required this.kunyomi,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory KanjiItem.fromSubject(Map<String, dynamic> subj) {
|
factory KanjiItem.fromSubject(Map<String, dynamic> subj) {
|
||||||
final int id = subj['id'] as int;
|
final commonFields = Subject.parseCommonFields(subj);
|
||||||
final data = subj['data'] as Map<String, dynamic>;
|
final data = commonFields['data'] as Map<String, dynamic>;
|
||||||
final int level = data['level'] as int;
|
|
||||||
final String characters = (data['characters'] ?? '') as String;
|
|
||||||
final List<String> meanings = <String>[];
|
|
||||||
final List<String> onyomi = <String>[];
|
final List<String> onyomi = <String>[];
|
||||||
final List<String> kunyomi = <String>[];
|
final List<String> kunyomi = <String>[];
|
||||||
|
|
||||||
if (data['meanings'] != null) {
|
|
||||||
for (final m in data['meanings'] as List) {
|
|
||||||
meanings.add((m['meaning'] as String).toLowerCase());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data['readings'] != null) {
|
if (data['readings'] != null) {
|
||||||
for (final r in data['readings'] as List) {
|
for (final r in data['readings'] as List) {
|
||||||
final typ = r['type'] as String? ?? '';
|
final typ = r['type'] as String? ?? '';
|
||||||
@@ -62,10 +32,10 @@ class KanjiItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return KanjiItem(
|
return KanjiItem(
|
||||||
id: id,
|
id: commonFields['id'] as int,
|
||||||
level: level,
|
level: commonFields['level'] as int,
|
||||||
characters: characters,
|
characters: commonFields['characters'] as String,
|
||||||
meanings: meanings,
|
meanings: commonFields['meanings'] as List<String>,
|
||||||
onyomi: onyomi,
|
onyomi: onyomi,
|
||||||
kunyomi: kunyomi,
|
kunyomi: kunyomi,
|
||||||
);
|
);
|
||||||
@@ -83,88 +53,3 @@ String _katakanaToHiragana(String input) {
|
|||||||
}
|
}
|
||||||
return buf.toString();
|
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<String> meanings;
|
|
||||||
final List<String> readings;
|
|
||||||
final List<PronunciationAudio> pronunciationAudios;
|
|
||||||
final Map<String, VocabSrsItem> srsItems = {};
|
|
||||||
|
|
||||||
VocabularyItem({
|
|
||||||
required this.id,
|
|
||||||
required this.level,
|
|
||||||
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;
|
|
||||||
final data = subj['data'] as Map<String, dynamic>;
|
|
||||||
final int level = data['level'] as int;
|
|
||||||
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) {
|
|
||||||
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<String, dynamic>?;
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
17
lib/src/models/srs_item.dart
Normal file
17
lib/src/models/srs_item.dart
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
enum QuizMode { kanjiToEnglish, englishToKanji, reading, vocabToEnglish, englishToVocab, audioToEnglish }
|
||||||
|
|
||||||
|
class SrsItem {
|
||||||
|
final int subjectId;
|
||||||
|
final QuizMode quizMode;
|
||||||
|
final String? readingType;
|
||||||
|
int srsStage;
|
||||||
|
DateTime lastAsked;
|
||||||
|
|
||||||
|
SrsItem({
|
||||||
|
required this.subjectId,
|
||||||
|
required this.quizMode,
|
||||||
|
this.readingType,
|
||||||
|
this.srsStage = 0,
|
||||||
|
DateTime? lastAsked,
|
||||||
|
}) : lastAsked = lastAsked ?? DateTime.now();
|
||||||
|
}
|
||||||
38
lib/src/models/subject.dart
Normal file
38
lib/src/models/subject.dart
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import 'srs_item.dart';
|
||||||
|
|
||||||
|
abstract class Subject {
|
||||||
|
final int id;
|
||||||
|
final int level;
|
||||||
|
final String characters;
|
||||||
|
final List<String> meanings;
|
||||||
|
final Map<String, SrsItem> srsItems = {};
|
||||||
|
|
||||||
|
Subject({
|
||||||
|
required this.id,
|
||||||
|
required this.level,
|
||||||
|
required this.characters,
|
||||||
|
required this.meanings,
|
||||||
|
});
|
||||||
|
|
||||||
|
static Map<String, dynamic> parseCommonFields(Map<String, dynamic> subj) {
|
||||||
|
final int id = subj['id'] as int;
|
||||||
|
final data = subj['data'] as Map<String, dynamic>;
|
||||||
|
final int level = data['level'] as int;
|
||||||
|
final String characters = (data['characters'] ?? '') as String;
|
||||||
|
final List<String> meanings = <String>[];
|
||||||
|
|
||||||
|
if (data['meanings'] != null) {
|
||||||
|
for (final m in data['meanings'] as List) {
|
||||||
|
meanings.add((m['meaning'] as String).toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'level': level,
|
||||||
|
'characters': characters,
|
||||||
|
'meanings': meanings,
|
||||||
|
'data': data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
15
lib/src/models/subject_factory.dart
Normal file
15
lib/src/models/subject_factory.dart
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import 'kanji_item.dart';
|
||||||
|
import 'vocabulary_item.dart';
|
||||||
|
import 'subject.dart';
|
||||||
|
|
||||||
|
class SubjectFactory {
|
||||||
|
static Subject fromMap(Map<String, dynamic> map) {
|
||||||
|
final String object = map['object'];
|
||||||
|
if (object == 'kanji') {
|
||||||
|
return KanjiItem.fromSubject(map);
|
||||||
|
} else if (object == 'vocabulary') {
|
||||||
|
return VocabularyItem.fromSubject(map);
|
||||||
|
}
|
||||||
|
throw Exception('Unknown subject type: $object');
|
||||||
|
}
|
||||||
|
}
|
||||||
56
lib/src/models/vocabulary_item.dart
Normal file
56
lib/src/models/vocabulary_item.dart
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import 'subject.dart';
|
||||||
|
|
||||||
|
class PronunciationAudio {
|
||||||
|
final String url;
|
||||||
|
final String gender;
|
||||||
|
|
||||||
|
PronunciationAudio({required this.url, required this.gender});
|
||||||
|
}
|
||||||
|
|
||||||
|
class VocabularyItem extends Subject {
|
||||||
|
final List<String> readings;
|
||||||
|
final List<PronunciationAudio> pronunciationAudios;
|
||||||
|
|
||||||
|
VocabularyItem({
|
||||||
|
required super.id,
|
||||||
|
required super.level,
|
||||||
|
required super.characters,
|
||||||
|
required super.meanings,
|
||||||
|
required this.readings,
|
||||||
|
required this.pronunciationAudios,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory VocabularyItem.fromSubject(Map<String, dynamic> subj) {
|
||||||
|
final commonFields = Subject.parseCommonFields(subj);
|
||||||
|
final data = commonFields['data'] as Map<String, dynamic>;
|
||||||
|
final List<String> readings = <String>[];
|
||||||
|
final List<PronunciationAudio> pronunciationAudios = <PronunciationAudio>[];
|
||||||
|
|
||||||
|
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<String, dynamic>?;
|
||||||
|
final gender = metadata?['gender'] as String?;
|
||||||
|
|
||||||
|
if (url != null && gender != null) {
|
||||||
|
pronunciationAudios.add(PronunciationAudio(url: url, gender: gender));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return VocabularyItem(
|
||||||
|
id: commonFields['id'] as int,
|
||||||
|
level: commonFields['level'] as int,
|
||||||
|
characters: commonFields['characters'] as String,
|
||||||
|
meanings: commonFields['meanings'] as List<String>,
|
||||||
|
readings: readings,
|
||||||
|
pronunciationAudios: pronunciationAudios,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ import 'package:hirameki_srs/src/themes.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import '../models/kanji_item.dart';
|
import '../models/kanji_item.dart';
|
||||||
|
import '../models/vocabulary_item.dart';
|
||||||
|
import '../models/srs_item.dart';
|
||||||
import '../services/deck_repository.dart';
|
import '../services/deck_repository.dart';
|
||||||
import 'package:hirameki_srs/src/services/vocab_deck_repository.dart';
|
import 'package:hirameki_srs/src/services/vocab_deck_repository.dart';
|
||||||
import '../services/custom_deck_repository.dart';
|
import '../services/custom_deck_repository.dart';
|
||||||
@@ -264,9 +266,9 @@ class _BrowseScreenState extends State<BrowseScreen>
|
|||||||
|
|
||||||
Widget _buildVocabListTile(VocabularyItem item) {
|
Widget _buildVocabListTile(VocabularyItem item) {
|
||||||
final requiredModes = <String>[
|
final requiredModes = <String>[
|
||||||
VocabQuizMode.vocabToEnglish.toString(),
|
QuizMode.vocabToEnglish.toString(),
|
||||||
VocabQuizMode.englishToVocab.toString(),
|
QuizMode.englishToVocab.toString(),
|
||||||
VocabQuizMode.audioToEnglish.toString(),
|
QuizMode.audioToEnglish.toString(),
|
||||||
];
|
];
|
||||||
|
|
||||||
int minSrsStage = 9;
|
int minSrsStage = 9;
|
||||||
@@ -422,6 +424,8 @@ class _BrowseScreenState extends State<BrowseScreen>
|
|||||||
srsScores['Reading (kunyomi)'] = srsItem.srsStage;
|
srsScores['Reading (kunyomi)'] = srsItem.srsStage;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -670,7 +674,8 @@ class _BrowseScreenState extends State<BrowseScreen>
|
|||||||
setState(() {
|
setState(() {
|
||||||
if (_selectedItems.length == _customDeck.length) {
|
if (_selectedItems.length == _customDeck.length) {
|
||||||
_selectedItems.clear();
|
_selectedItems.clear();
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
_selectedItems = List.from(_customDeck);
|
_selectedItems = List.from(_customDeck);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -968,15 +973,17 @@ class _VocabDetailsDialogState extends State<_VocabDetailsDialog> {
|
|||||||
for (final entry in widget.vocab.srsItems.entries) {
|
for (final entry in widget.vocab.srsItems.entries) {
|
||||||
final srsItem = entry.value;
|
final srsItem = entry.value;
|
||||||
switch (srsItem.quizMode) {
|
switch (srsItem.quizMode) {
|
||||||
case VocabQuizMode.vocabToEnglish:
|
case QuizMode.vocabToEnglish:
|
||||||
srsScores['JP -> EN'] = srsItem.srsStage;
|
srsScores['JP -> EN'] = srsItem.srsStage;
|
||||||
break;
|
break;
|
||||||
case VocabQuizMode.englishToVocab:
|
case QuizMode.englishToVocab:
|
||||||
srsScores['EN -> JP'] = srsItem.srsStage;
|
srsScores['EN -> JP'] = srsItem.srsStage;
|
||||||
break;
|
break;
|
||||||
case VocabQuizMode.audioToEnglish:
|
case QuizMode.audioToEnglish:
|
||||||
srsScores['Audio'] = srsItem.srsStage;
|
srsScores['Audio'] = srsItem.srsStage;
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1052,3 +1059,4 @@ void _showVocabDetailsDialog(BuildContext context, VocabularyItem vocab) {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import '../models/kanji_item.dart';
|
import '../models/kanji_item.dart';
|
||||||
|
import '../models/srs_item.dart';
|
||||||
import '../services/deck_repository.dart';
|
import '../services/deck_repository.dart';
|
||||||
import '../services/distractor_generator.dart';
|
import '../services/distractor_generator.dart';
|
||||||
import '../widgets/kanji_card.dart';
|
import '../widgets/kanji_card.dart';
|
||||||
@@ -61,9 +62,6 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
super.initState();
|
super.initState();
|
||||||
_tabController = TabController(length: 3, vsync: this);
|
_tabController = TabController(length: 3, vsync: this);
|
||||||
_tabController.addListener(() {
|
_tabController.addListener(() {
|
||||||
if (_tabController.indexIsChanging) {
|
|
||||||
_nextQuestion();
|
|
||||||
}
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
});
|
});
|
||||||
_dg = widget.distractorGenerator ?? DistractorGenerator();
|
_dg = widget.distractorGenerator ?? DistractorGenerator();
|
||||||
@@ -266,6 +264,10 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
...distractors.take(3),
|
...distractors.take(3),
|
||||||
])..shuffle();
|
])..shuffle();
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
// Handle other QuizMode cases if necessary, or throw an error
|
||||||
|
// if these modes are not expected in this context.
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -294,7 +296,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
var srsItem = current.srsItems[srsKey];
|
var srsItem = current.srsItems[srsKey];
|
||||||
final isNew = srsItem == null;
|
final isNew = srsItem == null;
|
||||||
final srsItemForUpdate = srsItem ??= SrsItem(
|
final srsItemForUpdate = srsItem ??= SrsItem(
|
||||||
kanjiId: current.id,
|
subjectId: current.id,
|
||||||
quizMode: mode,
|
quizMode: mode,
|
||||||
readingType: readingType,
|
readingType: readingType,
|
||||||
);
|
);
|
||||||
@@ -432,6 +434,10 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
prompt = quizState.current!.characters;
|
prompt = quizState.current!.characters;
|
||||||
subtitle = quizState.readingHint;
|
subtitle = quizState.readingHint;
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
// Handle other QuizMode cases if necessary, or throw an error
|
||||||
|
// if these modes are not expected in this context.
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import 'dart:math';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import '../models/kanji_item.dart';
|
import '../models/vocabulary_item.dart';
|
||||||
|
import '../models/srs_item.dart';
|
||||||
import 'package:hirameki_srs/src/services/vocab_deck_repository.dart';
|
import 'package:hirameki_srs/src/services/vocab_deck_repository.dart';
|
||||||
import '../services/distractor_generator.dart';
|
import '../services/distractor_generator.dart';
|
||||||
import '../widgets/kanji_card.dart';
|
import '../widgets/kanji_card.dart';
|
||||||
@@ -53,9 +54,6 @@ class _VocabScreenState extends State<VocabScreen>
|
|||||||
super.initState();
|
super.initState();
|
||||||
_tabController = TabController(length: 3, vsync: this);
|
_tabController = TabController(length: 3, vsync: this);
|
||||||
_tabController.addListener(() {
|
_tabController.addListener(() {
|
||||||
if (_tabController.indexIsChanging) {
|
|
||||||
_nextQuestion();
|
|
||||||
}
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
});
|
});
|
||||||
_loadSettings();
|
_loadSettings();
|
||||||
@@ -130,16 +128,16 @@ class _VocabScreenState extends State<VocabScreen>
|
|||||||
.join(' ');
|
.join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
VocabQuizMode _modeForIndex(int index) {
|
QuizMode _modeForIndex(int index) {
|
||||||
switch (index) {
|
switch (index) {
|
||||||
case 0:
|
case 0:
|
||||||
return VocabQuizMode.vocabToEnglish;
|
return QuizMode.vocabToEnglish;
|
||||||
case 1:
|
case 1:
|
||||||
return VocabQuizMode.englishToVocab;
|
return QuizMode.englishToVocab;
|
||||||
case 2:
|
case 2:
|
||||||
return VocabQuizMode.audioToEnglish;
|
return QuizMode.audioToEnglish;
|
||||||
default:
|
default:
|
||||||
return VocabQuizMode.vocabToEnglish;
|
return QuizMode.vocabToEnglish;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +148,7 @@ class _VocabScreenState extends State<VocabScreen>
|
|||||||
final mode = _modeForIndex(index ?? _tabController.index);
|
final mode = _modeForIndex(index ?? _tabController.index);
|
||||||
|
|
||||||
List<VocabularyItem> currentDeckForMode = _deck;
|
List<VocabularyItem> currentDeckForMode = _deck;
|
||||||
if (mode == VocabQuizMode.audioToEnglish) {
|
if (mode == QuizMode.audioToEnglish) {
|
||||||
currentDeckForMode = _deck
|
currentDeckForMode = _deck
|
||||||
.where((item) => item.pronunciationAudios.isNotEmpty)
|
.where((item) => item.pronunciationAudios.isNotEmpty)
|
||||||
.toList();
|
.toList();
|
||||||
@@ -169,10 +167,10 @@ class _VocabScreenState extends State<VocabScreen>
|
|||||||
quizState.shuffledDeck.sort((a, b) {
|
quizState.shuffledDeck.sort((a, b) {
|
||||||
final aSrsItem =
|
final aSrsItem =
|
||||||
a.srsItems[mode.toString()] ??
|
a.srsItems[mode.toString()] ??
|
||||||
VocabSrsItem(vocabId: a.id, quizMode: mode);
|
SrsItem(subjectId: a.id, quizMode: mode);
|
||||||
final bSrsItem =
|
final bSrsItem =
|
||||||
b.srsItems[mode.toString()] ??
|
b.srsItems[mode.toString()] ??
|
||||||
VocabSrsItem(vocabId: b.id, quizMode: mode);
|
SrsItem(subjectId: b.id, quizMode: mode);
|
||||||
final stageComparison = aSrsItem.srsStage.compareTo(bSrsItem.srsStage);
|
final stageComparison = aSrsItem.srsStage.compareTo(bSrsItem.srsStage);
|
||||||
if (stageComparison != 0) {
|
if (stageComparison != 0) {
|
||||||
return stageComparison;
|
return stageComparison;
|
||||||
@@ -186,18 +184,14 @@ class _VocabScreenState extends State<VocabScreen>
|
|||||||
quizState.currentIndex++;
|
quizState.currentIndex++;
|
||||||
|
|
||||||
quizState.key = UniqueKey();
|
quizState.key = UniqueKey();
|
||||||
if (mode == VocabQuizMode.audioToEnglish) {
|
|
||||||
_playCurrentAudio();
|
|
||||||
}
|
|
||||||
|
|
||||||
quizState.correctAnswers = [];
|
quizState.correctAnswers = [];
|
||||||
quizState.options = [];
|
quizState.options = [];
|
||||||
quizState.selectedOption = null;
|
quizState.selectedOption = null;
|
||||||
quizState.showResult = false;
|
quizState.showResult = false;
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case VocabQuizMode.vocabToEnglish:
|
case QuizMode.vocabToEnglish:
|
||||||
case VocabQuizMode.audioToEnglish:
|
case QuizMode.audioToEnglish:
|
||||||
quizState.correctAnswers = [quizState.current!.meanings.first];
|
quizState.correctAnswers = [quizState.current!.meanings.first];
|
||||||
quizState.options = [
|
quizState.options = [
|
||||||
quizState.correctAnswers.first,
|
quizState.correctAnswers.first,
|
||||||
@@ -205,13 +199,15 @@ class _VocabScreenState extends State<VocabScreen>
|
|||||||
].map(_toTitleCase).toList()..shuffle();
|
].map(_toTitleCase).toList()..shuffle();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case VocabQuizMode.englishToVocab:
|
case QuizMode.englishToVocab:
|
||||||
quizState.correctAnswers = [quizState.current!.characters];
|
quizState.correctAnswers = [quizState.current!.characters];
|
||||||
quizState.options = [
|
quizState.options = [
|
||||||
quizState.correctAnswers.first,
|
quizState.correctAnswers.first,
|
||||||
..._dg.generateVocab(quizState.current!, _deck, 3),
|
..._dg.generateVocab(quizState.current!, _deck, 3),
|
||||||
]..shuffle();
|
]..shuffle();
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -219,10 +215,12 @@ class _VocabScreenState extends State<VocabScreen>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _playCurrentAudio() async {
|
Future<void> _playCurrentAudio({bool playOnLoad = false}) async {
|
||||||
final current = _currentQuizState.current;
|
final current = _currentQuizState.current;
|
||||||
if (current == null || current.pronunciationAudios.isEmpty) return;
|
if (current == null || current.pronunciationAudios.isEmpty) return;
|
||||||
|
|
||||||
|
if (playOnLoad && !_playAudio) return;
|
||||||
|
|
||||||
final maleAudios = current.pronunciationAudios.where(
|
final maleAudios = current.pronunciationAudios.where(
|
||||||
(a) => a.gender == 'male',
|
(a) => a.gender == 'male',
|
||||||
);
|
);
|
||||||
@@ -250,7 +248,7 @@ class _VocabScreenState extends State<VocabScreen>
|
|||||||
var srsItemNullable = current.srsItems[srsKey];
|
var srsItemNullable = current.srsItems[srsKey];
|
||||||
final isNew = srsItemNullable == null;
|
final isNew = srsItemNullable == null;
|
||||||
final srsItem =
|
final srsItem =
|
||||||
srsItemNullable ?? VocabSrsItem(vocabId: current.id, quizMode: mode);
|
srsItemNullable ?? SrsItem(subjectId: current.id, quizMode: mode);
|
||||||
|
|
||||||
quizState.asked += 1;
|
quizState.asked += 1;
|
||||||
quizState.selectedOption = option;
|
quizState.selectedOption = option;
|
||||||
@@ -272,7 +270,7 @@ class _VocabScreenState extends State<VocabScreen>
|
|||||||
await repo.updateVocabSrsItem(srsItem);
|
await repo.updateVocabSrsItem(srsItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
final correctDisplay = (mode == VocabQuizMode.vocabToEnglish)
|
final correctDisplay = (mode == QuizMode.vocabToEnglish)
|
||||||
? _toTitleCase(quizState.correctAnswers.first)
|
? _toTitleCase(quizState.correctAnswers.first)
|
||||||
: quizState.correctAnswers.first;
|
: quizState.correctAnswers.first;
|
||||||
|
|
||||||
@@ -281,7 +279,9 @@ class _VocabScreenState extends State<VocabScreen>
|
|||||||
content: Text(
|
content: Text(
|
||||||
isCorrect ? 'Correct!' : 'Wrong — correct: $correctDisplay',
|
isCorrect ? 'Correct!' : 'Wrong — correct: $correctDisplay',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: isCorrect ? Theme.of(context).colorScheme.tertiary : Theme.of(context).colorScheme.error,
|
color: isCorrect
|
||||||
|
? Theme.of(context).colorScheme.tertiary
|
||||||
|
: Theme.of(context).colorScheme.error,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -294,7 +294,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 && mode != VocabQuizMode.audioToEnglish) {
|
if (_playAudio) {
|
||||||
final maleAudios = current.pronunciationAudios.where(
|
final maleAudios = current.pronunciationAudios.where(
|
||||||
(a) => a.gender == 'male',
|
(a) => a.gender == 'male',
|
||||||
);
|
);
|
||||||
@@ -330,7 +330,12 @@ class _VocabScreenState extends State<VocabScreen>
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
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),
|
const SizedBox(height: 16),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
@@ -375,26 +380,35 @@ class _VocabScreenState extends State<VocabScreen>
|
|||||||
|
|
||||||
if (quizState.current == null) {
|
if (quizState.current == null) {
|
||||||
promptWidget = const SizedBox.shrink();
|
promptWidget = const SizedBox.shrink();
|
||||||
} else if (mode == VocabQuizMode.audioToEnglish) {
|
} else if (mode == QuizMode.audioToEnglish) {
|
||||||
promptWidget = IconButton(
|
promptWidget = IconButton(
|
||||||
icon: Icon(Icons.volume_up, color: Theme.of(context).colorScheme.onSurface, size: 64),
|
icon: Icon(
|
||||||
|
Icons.volume_up,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
size: 64,
|
||||||
|
),
|
||||||
onPressed: _playCurrentAudio,
|
onPressed: _playCurrentAudio,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
String promptText = '';
|
String promptText = '';
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case VocabQuizMode.vocabToEnglish:
|
case QuizMode.vocabToEnglish:
|
||||||
promptText = quizState.current!.characters;
|
promptText = quizState.current!.characters;
|
||||||
break;
|
break;
|
||||||
case VocabQuizMode.englishToVocab:
|
case QuizMode.englishToVocab:
|
||||||
promptText = _toTitleCase(quizState.current!.meanings.first);
|
promptText = _toTitleCase(quizState.current!.meanings.first);
|
||||||
break;
|
break;
|
||||||
case VocabQuizMode.audioToEnglish:
|
case QuizMode.audioToEnglish:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
promptWidget = Text(
|
promptWidget = Text(
|
||||||
promptText,
|
promptText,
|
||||||
style: TextStyle(fontSize: 48, color: Theme.of(context).colorScheme.onSurface),
|
style: TextStyle(
|
||||||
|
fontSize: 48,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,9 +419,18 @@ class _VocabScreenState extends State<VocabScreen>
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: Text(_status, style: TextStyle(color: Theme.of(context).colorScheme.onSurface))),
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
_status,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
if (_loading)
|
if (_loading)
|
||||||
CircularProgressIndicator(color: Theme.of(context).colorScheme.primary),
|
CircularProgressIndicator(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 18),
|
const SizedBox(height: 18),
|
||||||
@@ -440,7 +463,9 @@ class _VocabScreenState extends State<VocabScreen>
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Score: ${quizState.score} / ${quizState.asked}',
|
'Score: ${quizState.score} / ${quizState.asked}',
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -23,12 +23,7 @@ class CustomDeckRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateCard(CustomKanjiItem item) async {
|
Future<void> updateCard(CustomKanjiItem item) async {
|
||||||
final deck = await getCustomDeck();
|
await updateCards([item]);
|
||||||
final index = deck.indexWhere((element) => element.characters == item.characters);
|
|
||||||
if (index != -1) {
|
|
||||||
deck[index] = item;
|
|
||||||
await saveDeck(deck);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateCards(List<CustomKanjiItem> itemsToUpdate) async {
|
Future<void> updateCards(List<CustomKanjiItem> itemsToUpdate) async {
|
||||||
|
|||||||
27
lib/src/services/database_constants.dart
Normal file
27
lib/src/services/database_constants.dart
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
|
||||||
|
class DbConstants {
|
||||||
|
static const String settingsTable = 'settings';
|
||||||
|
static const String kanjiTable = 'kanji';
|
||||||
|
static const String srsItemsTable = 'srs_items';
|
||||||
|
static const String vocabularyTable = 'vocabulary';
|
||||||
|
static const String srsVocabItemsTable = 'srs_vocab_items';
|
||||||
|
|
||||||
|
static const String keyColumn = 'key';
|
||||||
|
static const String valueColumn = 'value';
|
||||||
|
|
||||||
|
static const String idColumn = 'id';
|
||||||
|
static const String levelColumn = 'level';
|
||||||
|
static const String charactersColumn = 'characters';
|
||||||
|
static const String meaningsColumn = 'meanings';
|
||||||
|
static const String onyomiColumn = 'onyomi';
|
||||||
|
static const String kunyomiColumn = 'kunyomi';
|
||||||
|
static const String readingsColumn = 'readings';
|
||||||
|
static const String pronunciationAudiosColumn = 'pronunciation_audios';
|
||||||
|
|
||||||
|
static const String kanjiIdColumn = 'kanjiId';
|
||||||
|
static const String vocabIdColumn = 'vocabId';
|
||||||
|
static const String quizModeColumn = 'quizMode';
|
||||||
|
static const String readingTypeColumn = 'readingType';
|
||||||
|
static const String srsStageColumn = 'srsStage';
|
||||||
|
static const String lastAskedColumn = 'lastAsked';
|
||||||
|
}
|
||||||
92
lib/src/services/database_helper.dart
Normal file
92
lib/src/services/database_helper.dart
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import 'package:path/path.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:sqflite/sqflite.dart';
|
||||||
|
import 'database_constants.dart';
|
||||||
|
|
||||||
|
class DatabaseHelper {
|
||||||
|
static final DatabaseHelper _instance = DatabaseHelper._internal();
|
||||||
|
static Database? _db;
|
||||||
|
|
||||||
|
factory DatabaseHelper() {
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseHelper._internal();
|
||||||
|
|
||||||
|
Future<Database> get db async {
|
||||||
|
if (_db != null) return _db!;
|
||||||
|
_db = await _openDb();
|
||||||
|
return _db!;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> close() async {
|
||||||
|
if (_db != null) {
|
||||||
|
await _db!.close();
|
||||||
|
_db = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Database> _openDb() async {
|
||||||
|
final dir = await getApplicationDocumentsDirectory();
|
||||||
|
final path = join(dir.path, 'wanikani_srs.db');
|
||||||
|
|
||||||
|
return openDatabase(
|
||||||
|
path,
|
||||||
|
version: 7,
|
||||||
|
onCreate: (db, version) async {
|
||||||
|
await db.execute(
|
||||||
|
'''CREATE TABLE ${DbConstants.kanjiTable} (${DbConstants.idColumn} INTEGER PRIMARY KEY, ${DbConstants.levelColumn} INTEGER, ${DbConstants.charactersColumn} TEXT, ${DbConstants.meaningsColumn} TEXT, ${DbConstants.onyomiColumn} TEXT, ${DbConstants.kunyomiColumn} TEXT)''',
|
||||||
|
);
|
||||||
|
await db.execute(
|
||||||
|
'''CREATE TABLE ${DbConstants.settingsTable} (${DbConstants.keyColumn} TEXT PRIMARY KEY, ${DbConstants.valueColumn} TEXT)''',
|
||||||
|
);
|
||||||
|
await db.execute(
|
||||||
|
'''CREATE TABLE ${DbConstants.srsItemsTable} (${DbConstants.kanjiIdColumn} INTEGER, ${DbConstants.quizModeColumn} TEXT, ${DbConstants.readingTypeColumn} TEXT, ${DbConstants.srsStageColumn} INTEGER, ${DbConstants.lastAskedColumn} TEXT, PRIMARY KEY (${DbConstants.kanjiIdColumn}, ${DbConstants.quizModeColumn}, ${DbConstants.readingTypeColumn}))''',
|
||||||
|
);
|
||||||
|
await db.execute(
|
||||||
|
'''CREATE TABLE ${DbConstants.vocabularyTable} (${DbConstants.idColumn} INTEGER PRIMARY KEY, ${DbConstants.levelColumn} INTEGER, ${DbConstants.charactersColumn} TEXT, ${DbConstants.meaningsColumn} TEXT, ${DbConstants.readingsColumn} TEXT, ${DbConstants.pronunciationAudiosColumn} TEXT)''',
|
||||||
|
);
|
||||||
|
await db.execute(
|
||||||
|
'''CREATE TABLE ${DbConstants.srsVocabItemsTable} (${DbConstants.vocabIdColumn} INTEGER, ${DbConstants.quizModeColumn} TEXT, ${DbConstants.srsStageColumn} INTEGER, ${DbConstants.lastAskedColumn} TEXT, PRIMARY KEY (${DbConstants.vocabIdColumn}, ${DbConstants.quizModeColumn}))''',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onUpgrade: (db, oldVersion, newVersion) async {
|
||||||
|
if (oldVersion < 2) {
|
||||||
|
await db.execute(
|
||||||
|
'''CREATE TABLE IF NOT EXISTS ${DbConstants.settingsTable} (${DbConstants.keyColumn} TEXT PRIMARY KEY, ${DbConstants.valueColumn} TEXT)''',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (oldVersion < 4) {
|
||||||
|
await db.execute(
|
||||||
|
'''CREATE TABLE IF NOT EXISTS ${DbConstants.srsItemsTable} (${DbConstants.kanjiIdColumn} INTEGER, ${DbConstants.quizModeColumn} TEXT, ${DbConstants.readingTypeColumn} TEXT, ${DbConstants.srsStageColumn} INTEGER, ${DbConstants.lastAskedColumn} TEXT, PRIMARY KEY (${DbConstants.kanjiIdColumn}, ${DbConstants.quizModeColumn}, ${DbConstants.readingTypeColumn}))''',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (oldVersion < 5) {
|
||||||
|
await db.execute(
|
||||||
|
'''CREATE TABLE IF NOT EXISTS ${DbConstants.vocabularyTable} (${DbConstants.idColumn} INTEGER PRIMARY KEY, ${DbConstants.charactersColumn} TEXT, ${DbConstants.meaningsColumn} TEXT, ${DbConstants.readingsColumn} TEXT)''',
|
||||||
|
);
|
||||||
|
await db.execute(
|
||||||
|
'''CREATE TABLE IF NOT EXISTS ${DbConstants.srsVocabItemsTable} (${DbConstants.vocabIdColumn} INTEGER, ${DbConstants.quizModeColumn} TEXT, ${DbConstants.srsStageColumn} INTEGER, ${DbConstants.lastAskedColumn} TEXT, PRIMARY KEY (${DbConstants.vocabIdColumn}, ${DbConstants.quizModeColumn}))''',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (oldVersion < 6) {
|
||||||
|
try {
|
||||||
|
await db.execute(
|
||||||
|
'ALTER TABLE ${DbConstants.vocabularyTable} ADD COLUMN ${DbConstants.pronunciationAudiosColumn} TEXT',
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
// Ignore error, column might already exist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (oldVersion < 7) {
|
||||||
|
try {
|
||||||
|
await db.execute('ALTER TABLE ${DbConstants.kanjiTable} ADD COLUMN ${DbConstants.levelColumn} INTEGER');
|
||||||
|
await db.execute('ALTER TABLE ${DbConstants.vocabularyTable} ADD COLUMN ${DbConstants.levelColumn} INTEGER');
|
||||||
|
} catch (_) {
|
||||||
|
// Ignore error, column might already exist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:path/path.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:sqflite/sqflite.dart';
|
import 'package:sqflite/sqflite.dart';
|
||||||
import '../models/kanji_item.dart';
|
import '../models/kanji_item.dart';
|
||||||
|
import '../models/srs_item.dart';
|
||||||
import '../api/wk_client.dart';
|
import '../api/wk_client.dart';
|
||||||
|
import 'database_constants.dart';
|
||||||
|
import 'database_helper.dart';
|
||||||
|
|
||||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
|
|
||||||
class DeckRepository {
|
class DeckRepository {
|
||||||
Database? _db;
|
|
||||||
String? _apiKey;
|
String? _apiKey;
|
||||||
|
|
||||||
Future<void> setApiKey(String apiKey) async {
|
Future<void> setApiKey(String apiKey) async {
|
||||||
@@ -18,146 +18,75 @@ class DeckRepository {
|
|||||||
|
|
||||||
String? get apiKey => _apiKey;
|
String? get apiKey => _apiKey;
|
||||||
|
|
||||||
Future<Database> _openDb() async {
|
|
||||||
if (_db != null) return _db!;
|
|
||||||
final dir = await getApplicationDocumentsDirectory();
|
|
||||||
final path = join(dir.path, 'wanikani_srs.db');
|
|
||||||
|
|
||||||
_db = await openDatabase(
|
|
||||||
path,
|
|
||||||
version: 7,
|
|
||||||
onCreate: (db, version) async {
|
|
||||||
await db.execute(
|
|
||||||
'''CREATE TABLE kanji (id INTEGER PRIMARY KEY, level INTEGER, characters TEXT, meanings TEXT, onyomi TEXT, kunyomi TEXT)''',
|
|
||||||
);
|
|
||||||
await db.execute(
|
|
||||||
'''CREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT)''',
|
|
||||||
);
|
|
||||||
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, level INTEGER, 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))''',
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onUpgrade: (db, oldVersion, newVersion) async {
|
|
||||||
if (oldVersion < 2) {
|
|
||||||
await db.execute(
|
|
||||||
'''CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT)''',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (oldVersion < 3) {
|
|
||||||
// Migration from version 2 to 3 was flawed, so we just drop the columns if they exist
|
|
||||||
}
|
|
||||||
if (oldVersion < 4) {
|
|
||||||
await db.execute(
|
|
||||||
'''CREATE TABLE srs_items (kanjiId INTEGER, quizMode TEXT, readingType TEXT, srsStage INTEGER, lastAsked TEXT, PRIMARY KEY (kanjiId, quizMode, readingType))''',
|
|
||||||
);
|
|
||||||
// We are not migrating the old srs data, as it was not mode-specific.
|
|
||||||
// Old columns will be dropped.
|
|
||||||
}
|
|
||||||
if (oldVersion < 5) {
|
|
||||||
await db.execute(
|
|
||||||
'''CREATE TABLE vocabulary (id INTEGER PRIMARY KEY, characters TEXT, meanings TEXT, readings TEXT)''',
|
|
||||||
);
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 7) {
|
|
||||||
try {
|
|
||||||
await db.execute('ALTER TABLE kanji ADD COLUMN level INTEGER');
|
|
||||||
await db.execute('ALTER TABLE vocabulary ADD COLUMN level INTEGER');
|
|
||||||
} catch (_) {
|
|
||||||
// Ignore error, column might already exist
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return _db!;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> saveApiKey(String apiKey) async {
|
Future<void> saveApiKey(String apiKey) async {
|
||||||
final db = await _openDb();
|
final db = await DatabaseHelper().db;
|
||||||
await db.insert('settings', {
|
await db.insert(DbConstants.settingsTable, {
|
||||||
'key': 'apiKey',
|
DbConstants.keyColumn: 'apiKey',
|
||||||
'value': apiKey,
|
DbConstants.valueColumn: apiKey,
|
||||||
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> loadApiKey() async {
|
Future<String?> loadApiKey() async {
|
||||||
String? envApiKey;
|
final db = await DatabaseHelper().db;
|
||||||
try {
|
|
||||||
envApiKey = dotenv.env['WANIKANI_API_KEY'];
|
|
||||||
} catch (e) {
|
|
||||||
envApiKey = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (envApiKey != null && envApiKey.isNotEmpty) {
|
|
||||||
_apiKey = envApiKey;
|
|
||||||
return _apiKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
final db = await _openDb();
|
|
||||||
final rows = await db.query(
|
final rows = await db.query(
|
||||||
'settings',
|
DbConstants.settingsTable,
|
||||||
where: 'key = ?',
|
where: '${DbConstants.keyColumn} = ?',
|
||||||
whereArgs: ['apiKey'],
|
whereArgs: ['apiKey'],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (rows.isNotEmpty) {
|
if (rows.isNotEmpty) {
|
||||||
_apiKey = rows.first['value'] as String;
|
_apiKey = rows.first[DbConstants.valueColumn] as String;
|
||||||
return _apiKey;
|
return _apiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final envApiKey = dotenv.env['WANIKANI_API_KEY'];
|
||||||
|
if (envApiKey != null && envApiKey.isNotEmpty) {
|
||||||
|
await saveApiKey(envApiKey);
|
||||||
|
_apiKey = envApiKey;
|
||||||
|
return _apiKey;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// dotenv is not initialized
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveKanji(List<KanjiItem> items) async {
|
Future<void> saveKanji(List<KanjiItem> items) async {
|
||||||
final db = await _openDb();
|
final db = await DatabaseHelper().db;
|
||||||
final batch = db.batch();
|
final batch = db.batch();
|
||||||
for (final it in items) {
|
for (final it in items) {
|
||||||
batch.insert('kanji', {
|
batch.insert(DbConstants.kanjiTable, {
|
||||||
'id': it.id,
|
DbConstants.idColumn: it.id,
|
||||||
'level': it.level,
|
DbConstants.levelColumn: it.level,
|
||||||
'characters': it.characters,
|
DbConstants.charactersColumn: it.characters,
|
||||||
'meanings': it.meanings.join('|'),
|
DbConstants.meaningsColumn: it.meanings.join('|'),
|
||||||
'onyomi': it.onyomi.join('|'),
|
DbConstants.onyomiColumn: it.onyomi.join('|'),
|
||||||
'kunyomi': it.kunyomi.join('|'),
|
DbConstants.kunyomiColumn: it.kunyomi.join('|'),
|
||||||
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
||||||
}
|
}
|
||||||
await batch.commit(noResult: true);
|
await batch.commit(noResult: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<KanjiItem>> loadKanji() async {
|
Future<List<KanjiItem>> loadKanji() async {
|
||||||
final db = await _openDb();
|
final db = await DatabaseHelper().db;
|
||||||
final rows = await db.query('kanji');
|
final rows = await db.query(DbConstants.kanjiTable);
|
||||||
final kanjiItems = rows
|
final kanjiItems = rows
|
||||||
.map(
|
.map(
|
||||||
(r) => KanjiItem(
|
(r) => KanjiItem(
|
||||||
id: r['id'] as int,
|
id: r[DbConstants.idColumn] as int,
|
||||||
level: r['level'] as int? ?? 0,
|
level: r[DbConstants.levelColumn] as int? ?? 0,
|
||||||
characters: r['characters'] as String,
|
characters: r[DbConstants.charactersColumn] as String,
|
||||||
meanings: (r['meanings'] as String)
|
meanings: (r[DbConstants.meaningsColumn] as String)
|
||||||
.split('|')
|
.split('|')
|
||||||
.where((s) => s.isNotEmpty)
|
.where((s) => s.isNotEmpty)
|
||||||
.toList(),
|
.toList(),
|
||||||
onyomi: (r['onyomi'] as String)
|
onyomi: (r[DbConstants.onyomiColumn] as String)
|
||||||
.split('|')
|
.split('|')
|
||||||
.where((s) => s.isNotEmpty)
|
.where((s) => s.isNotEmpty)
|
||||||
.toList(),
|
.toList(),
|
||||||
kunyomi: (r['kunyomi'] as String)
|
kunyomi: (r[DbConstants.kunyomiColumn] as String)
|
||||||
.split('|')
|
.split('|')
|
||||||
.where((s) => s.isNotEmpty)
|
.where((s) => s.isNotEmpty)
|
||||||
.toList(),
|
.toList(),
|
||||||
@@ -165,8 +94,23 @@ class DeckRepository {
|
|||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
final srsRows = await db.query(DbConstants.srsItemsTable);
|
||||||
|
final srsItemsByKanjiId = <int, List<SrsItem>>{};
|
||||||
|
for (final r in srsRows) {
|
||||||
|
final srsItem = SrsItem(
|
||||||
|
subjectId: r[DbConstants.kanjiIdColumn] as int,
|
||||||
|
quizMode: QuizMode.values.firstWhere(
|
||||||
|
(e) => e.toString() == r[DbConstants.quizModeColumn] as String,
|
||||||
|
),
|
||||||
|
readingType: r[DbConstants.readingTypeColumn] as String?,
|
||||||
|
srsStage: r[DbConstants.srsStageColumn] as int,
|
||||||
|
lastAsked: DateTime.parse(r[DbConstants.lastAskedColumn] as String),
|
||||||
|
);
|
||||||
|
srsItemsByKanjiId.putIfAbsent(srsItem.subjectId, () => []).add(srsItem);
|
||||||
|
}
|
||||||
|
|
||||||
for (final item in kanjiItems) {
|
for (final item in kanjiItems) {
|
||||||
final srsItems = await getSrsItems(item.id);
|
final srsItems = srsItemsByKanjiId[item.id] ?? [];
|
||||||
for (final srsItem in srsItems) {
|
for (final srsItem in srsItems) {
|
||||||
final key = srsItem.quizMode.toString() + (srsItem.readingType ?? '');
|
final key = srsItem.quizMode.toString() + (srsItem.readingType ?? '');
|
||||||
item.srsItems[key] = srsItem;
|
item.srsItems[key] = srsItem;
|
||||||
@@ -176,47 +120,27 @@ class DeckRepository {
|
|||||||
return kanjiItems;
|
return kanjiItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<SrsItem>> getSrsItems(int kanjiId) async {
|
|
||||||
final db = await _openDb();
|
|
||||||
final rows = await db.query(
|
|
||||||
'srs_items',
|
|
||||||
where: 'kanjiId = ?',
|
|
||||||
whereArgs: [kanjiId],
|
|
||||||
);
|
|
||||||
return rows.map((r) {
|
|
||||||
return SrsItem(
|
|
||||||
kanjiId: r['kanjiId'] as int,
|
|
||||||
quizMode: QuizMode.values.firstWhere(
|
|
||||||
(e) => e.toString() == r['quizMode'] as String,
|
|
||||||
),
|
|
||||||
readingType: r['readingType'] as String?,
|
|
||||||
srsStage: r['srsStage'] as int,
|
|
||||||
lastAsked: DateTime.parse(r['lastAsked'] as String),
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updateSrsItem(SrsItem item) async {
|
Future<void> updateSrsItem(SrsItem item) async {
|
||||||
final db = await _openDb();
|
final db = await DatabaseHelper().db;
|
||||||
await db.update(
|
await db.update(
|
||||||
'srs_items',
|
DbConstants.srsItemsTable,
|
||||||
{
|
{
|
||||||
'srsStage': item.srsStage,
|
DbConstants.srsStageColumn: item.srsStage,
|
||||||
'lastAsked': item.lastAsked.toIso8601String(),
|
DbConstants.lastAskedColumn: item.lastAsked.toIso8601String(),
|
||||||
},
|
},
|
||||||
where: 'kanjiId = ? AND quizMode = ? AND readingType = ?',
|
where: '${DbConstants.kanjiIdColumn} = ? AND ${DbConstants.quizModeColumn} = ? AND ${DbConstants.readingTypeColumn} = ?',
|
||||||
whereArgs: [item.kanjiId, item.quizMode.toString(), item.readingType],
|
whereArgs: [item.subjectId, item.quizMode.toString(), item.readingType],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> insertSrsItem(SrsItem item) async {
|
Future<void> insertSrsItem(SrsItem item) async {
|
||||||
final db = await _openDb();
|
final db = await DatabaseHelper().db;
|
||||||
await db.insert('srs_items', {
|
await db.insert(DbConstants.srsItemsTable, {
|
||||||
'kanjiId': item.kanjiId,
|
DbConstants.kanjiIdColumn: item.subjectId,
|
||||||
'quizMode': item.quizMode.toString(),
|
DbConstants.quizModeColumn: item.quizMode.toString(),
|
||||||
'readingType': item.readingType,
|
DbConstants.readingTypeColumn: item.readingType,
|
||||||
'srsStage': item.srsStage,
|
DbConstants.srsStageColumn: item.srsStage,
|
||||||
'lastAsked': item.lastAsked.toIso8601String(),
|
DbConstants.lastAskedColumn: item.lastAsked.toIso8601String(),
|
||||||
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import '../models/kanji_item.dart';
|
import '../models/kanji_item.dart';
|
||||||
|
import '../models/vocabulary_item.dart';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
class DistractorGenerator {
|
class DistractorGenerator {
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:path/path.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:sqflite/sqflite.dart';
|
import 'package:sqflite/sqflite.dart';
|
||||||
import '../models/kanji_item.dart';
|
import '../models/vocabulary_item.dart';
|
||||||
|
import '../models/srs_item.dart';
|
||||||
import '../api/wk_client.dart';
|
import '../api/wk_client.dart';
|
||||||
|
import 'database_helper.dart';
|
||||||
|
|
||||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
|
|
||||||
class VocabDeckRepository {
|
class VocabDeckRepository {
|
||||||
Database? _db;
|
|
||||||
String? _apiKey;
|
String? _apiKey;
|
||||||
|
|
||||||
Future<void> setApiKey(String apiKey) async {
|
Future<void> setApiKey(String apiKey) async {
|
||||||
@@ -19,86 +18,15 @@ class VocabDeckRepository {
|
|||||||
|
|
||||||
String? get apiKey => _apiKey;
|
String? get apiKey => _apiKey;
|
||||||
|
|
||||||
Future<Database> _openDb() async {
|
|
||||||
if (_db != null) return _db!;
|
|
||||||
final dir = await getApplicationDocumentsDirectory();
|
|
||||||
final path = join(dir.path, 'wanikani_srs.db');
|
|
||||||
|
|
||||||
_db = await openDatabase(
|
|
||||||
path,
|
|
||||||
version: 7,
|
|
||||||
onCreate: (db, version) async {
|
|
||||||
await db.execute(
|
|
||||||
'''CREATE TABLE kanji (id INTEGER PRIMARY KEY, level INTEGER, characters TEXT, meanings TEXT, onyomi TEXT, kunyomi TEXT)''',
|
|
||||||
);
|
|
||||||
await db.execute(
|
|
||||||
'''CREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT)''',
|
|
||||||
);
|
|
||||||
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, level INTEGER, 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))''',
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onUpgrade: (db, oldVersion, newVersion) async {
|
|
||||||
if (oldVersion < 2) {
|
|
||||||
await db.execute(
|
|
||||||
'''CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT)''',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (oldVersion < 3) {
|
|
||||||
// Migration from version 2 to 3 was flawed, so we just drop the columns if they exist
|
|
||||||
}
|
|
||||||
if (oldVersion < 4) {
|
|
||||||
await db.execute(
|
|
||||||
'''CREATE TABLE srs_items (kanjiId INTEGER, quizMode TEXT, readingType TEXT, srsStage INTEGER, lastAsked TEXT, PRIMARY KEY (kanjiId, quizMode, readingType))''',
|
|
||||||
);
|
|
||||||
// We are not migrating the old srs data, as it was not mode-specific.
|
|
||||||
// Old columns will be dropped.
|
|
||||||
}
|
|
||||||
if (oldVersion < 5) {
|
|
||||||
await db.execute(
|
|
||||||
'''CREATE TABLE vocabulary (id INTEGER PRIMARY KEY, characters TEXT, meanings TEXT, readings TEXT)''',
|
|
||||||
);
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 7) {
|
|
||||||
try {
|
|
||||||
await db.execute('ALTER TABLE kanji ADD COLUMN level INTEGER');
|
|
||||||
await db.execute('ALTER TABLE vocabulary ADD COLUMN level INTEGER');
|
|
||||||
} catch (_) {
|
|
||||||
// Ignore error, column might already exist
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return _db!;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> saveApiKey(String apiKey) async {
|
Future<void> saveApiKey(String apiKey) async {
|
||||||
final db = await _openDb();
|
final db = await DatabaseHelper().db;
|
||||||
await db.insert('settings', {
|
await db.insert('settings', {
|
||||||
'key': 'apiKey',
|
'key': 'apiKey',
|
||||||
'value': apiKey,
|
'value': apiKey,
|
||||||
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Future<String?> loadApiKey() async {
|
Future<String?> loadApiKey() async {
|
||||||
String? envApiKey;
|
String? envApiKey;
|
||||||
try {
|
try {
|
||||||
@@ -112,7 +40,7 @@ class VocabDeckRepository {
|
|||||||
return _apiKey;
|
return _apiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
final db = await _openDb();
|
final db = await DatabaseHelper().db;
|
||||||
final rows = await db.query(
|
final rows = await db.query(
|
||||||
'settings',
|
'settings',
|
||||||
where: 'key = ?',
|
where: 'key = ?',
|
||||||
@@ -125,17 +53,17 @@ class VocabDeckRepository {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<VocabSrsItem>> getVocabSrsItems(int vocabId) async {
|
Future<List<SrsItem>> getVocabSrsItems(int vocabId) async {
|
||||||
final db = await _openDb();
|
final db = await DatabaseHelper().db;
|
||||||
final rows = await db.query(
|
final rows = await db.query(
|
||||||
'srs_vocab_items',
|
'srs_vocab_items',
|
||||||
where: 'vocabId = ?',
|
where: 'vocabId = ?',
|
||||||
whereArgs: [vocabId],
|
whereArgs: [vocabId],
|
||||||
);
|
);
|
||||||
return rows.map((r) {
|
return rows.map((r) {
|
||||||
return VocabSrsItem(
|
return SrsItem(
|
||||||
vocabId: r['vocabId'] as int,
|
subjectId: r['vocabId'] as int,
|
||||||
quizMode: VocabQuizMode.values.firstWhere(
|
quizMode: QuizMode.values.firstWhere(
|
||||||
(e) => e.toString() == r['quizMode'] as String,
|
(e) => e.toString() == r['quizMode'] as String,
|
||||||
),
|
),
|
||||||
srsStage: r['srsStage'] as int,
|
srsStage: r['srsStage'] as int,
|
||||||
@@ -144,8 +72,8 @@ class VocabDeckRepository {
|
|||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateVocabSrsItem(VocabSrsItem item) async {
|
Future<void> updateVocabSrsItem(SrsItem item) async {
|
||||||
final db = await _openDb();
|
final db = await DatabaseHelper().db;
|
||||||
await db.update(
|
await db.update(
|
||||||
'srs_vocab_items',
|
'srs_vocab_items',
|
||||||
{
|
{
|
||||||
@@ -153,14 +81,14 @@ class VocabDeckRepository {
|
|||||||
'lastAsked': item.lastAsked.toIso8601String(),
|
'lastAsked': item.lastAsked.toIso8601String(),
|
||||||
},
|
},
|
||||||
where: 'vocabId = ? AND quizMode = ?',
|
where: 'vocabId = ? AND quizMode = ?',
|
||||||
whereArgs: [item.vocabId, item.quizMode.toString()],
|
whereArgs: [item.subjectId, item.quizMode.toString()],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> insertVocabSrsItem(VocabSrsItem item) async {
|
Future<void> insertVocabSrsItem(SrsItem item) async {
|
||||||
final db = await _openDb();
|
final db = await DatabaseHelper().db;
|
||||||
await db.insert('srs_vocab_items', {
|
await db.insert('srs_vocab_items', {
|
||||||
'vocabId': item.vocabId,
|
'vocabId': item.subjectId,
|
||||||
'quizMode': item.quizMode.toString(),
|
'quizMode': item.quizMode.toString(),
|
||||||
'srsStage': item.srsStage,
|
'srsStage': item.srsStage,
|
||||||
'lastAsked': item.lastAsked.toIso8601String(),
|
'lastAsked': item.lastAsked.toIso8601String(),
|
||||||
@@ -168,7 +96,7 @@ class VocabDeckRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveVocabulary(List<VocabularyItem> items) async {
|
Future<void> saveVocabulary(List<VocabularyItem> items) async {
|
||||||
final db = await _openDb();
|
final db = await DatabaseHelper().db;
|
||||||
final batch = db.batch();
|
final batch = db.batch();
|
||||||
for (final it in items) {
|
for (final it in items) {
|
||||||
final audios = it.pronunciationAudios
|
final audios = it.pronunciationAudios
|
||||||
@@ -187,7 +115,7 @@ class VocabDeckRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<List<VocabularyItem>> loadVocabulary() async {
|
Future<List<VocabularyItem>> loadVocabulary() async {
|
||||||
final db = await _openDb();
|
final db = await DatabaseHelper().db;
|
||||||
final rows = await db.query('vocabulary');
|
final rows = await db.query('vocabulary');
|
||||||
final vocabItems = rows.map((r) {
|
final vocabItems = rows.map((r) {
|
||||||
final audiosRaw = r['pronunciation_audios'] as String?;
|
final audiosRaw = r['pronunciation_audios'] as String?;
|
||||||
|
|||||||
Reference in New Issue
Block a user