From 56eb61ac71b0ebfb2ac337d41b7074e2716a4010 Mon Sep 17 00:00:00 2001 From: Hampus Hammarlund Date: Fri, 12 Jun 2026 22:06:25 +0900 Subject: [PATCH 01/13] fix: add show note to flashcard set backup --- lib/src/datamodels/flashcard_sets.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/datamodels/flashcard_sets.dart b/lib/src/datamodels/flashcard_sets.dart index e2fd3b9..b18e7ef 100644 --- a/lib/src/datamodels/flashcard_sets.dart +++ b/lib/src/datamodels/flashcard_sets.dart @@ -118,6 +118,7 @@ class FlashcardSet implements Insertable { kanjiShowReading, SagaseDictionaryConstants.backupFlashcardSetVocabShowPartsOfSpeech: vocabShowPartsOfSpeech, + SagaseDictionaryConstants.backupFlashcardSetShowNote: showNote, SagaseDictionaryConstants.backupFlashcardSetTimestamp: timestamp.millisecondsSinceEpoch, SagaseDictionaryConstants.backupFlashcardSetPredefinedDictionaryLists: From 81a7559abebc48fa097f54e19e6c2e9284039490 Mon Sep 17 00:00:00 2001 From: Hampus Hammarlund Date: Fri, 12 Jun 2026 22:34:43 +0900 Subject: [PATCH 02/13] fix: improve safety of getAll for vocab and kanji --- lib/src/dao/kanjis_dao.dart | 2 +- lib/src/dao/vocabs_dao.dart | 5 +++-- test/dao/kanjis_dao_test.dart | 3 +++ test/dao/vocabs_dao_test.dart | 11 +++++++++++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/src/dao/kanjis_dao.dart b/lib/src/dao/kanjis_dao.dart index a973129..9fc4a7f 100644 --- a/lib/src/dao/kanjis_dao.dart +++ b/lib/src/dao/kanjis_dao.dart @@ -161,7 +161,7 @@ class KanjisDao extends DatabaseAccessor with _$KanjisDaoMixin { ..where((note) => note.id.isIn(idList))) .get(); for (final note in notes) { - kanjiMap[note.id]!.note = note.note; + kanjiMap[note.id]?.note = note.note; } // Put the results in the same order as the input diff --git a/lib/src/dao/vocabs_dao.dart b/lib/src/dao/vocabs_dao.dart index cc331d3..f1b9d9a 100644 --- a/lib/src/dao/vocabs_dao.dart +++ b/lib/src/dao/vocabs_dao.dart @@ -172,13 +172,14 @@ class VocabsDao extends DatabaseAccessor with _$VocabsDaoMixin { final notes = await noteFuture; for (final note in notes) { - vocabMap[note.id]!.note = note.note; + vocabMap[note.id]?.note = note.note; } // Put the results in the same order as the input List vocabList = []; for (final id in idList) { - vocabList.add(vocabMap[id]!); + final vocab = vocabMap[id]; + if (vocab != null) vocabList.add(vocab); } return vocabList; diff --git a/test/dao/kanjis_dao_test.dart b/test/dao/kanjis_dao_test.dart index fcb3887..65564e5 100644 --- a/test/dao/kanjis_dao_test.dart +++ b/test/dao/kanjis_dao_test.dart @@ -126,6 +126,9 @@ void main() { group('getAll', () { test('One kanji does not exist', () async { + // Note referencing a kanji that does not exist + await database.kanjisDao.setNote('s'.kanjiCodePoint(), 'Orphan note'); + final kanjiList = await database.kanjisDao.getAll( ['亜'.kanjiCodePoint(), 's'.kanjiCodePoint()], ); diff --git a/test/dao/vocabs_dao_test.dart b/test/dao/vocabs_dao_test.dart index f6ce1c7..4aa968a 100644 --- a/test/dao/vocabs_dao_test.dart +++ b/test/dao/vocabs_dao_test.dart @@ -120,6 +120,17 @@ void main() { expect(vocabList[1].note, null); }); + test('Skips ids missing from the database', () async { + // Note referencing a vocab that does not exist + await database.vocabsDao.setNote(999999, 'Orphan note'); + + final vocabList = + await database.vocabsDao.getAll([1000220, 999999, 1000160]); + expect(vocabList.length, 2); + expect(vocabList[0].id, 1000220); + expect(vocabList[1].id, 1000160); + }); + group('With front type', () { test('Spaced repetition does not exist', () async { final vocabList = await database.vocabsDao.getAll( From 7e7c8e481949effb4987e51cf51eec562afb7c54 Mon Sep 17 00:00:00 2001 From: Hampus Hammarlund Date: Fri, 12 Jun 2026 22:53:23 +0900 Subject: [PATCH 03/13] fix: improve single kanji search --- lib/src/dao/kanjis_dao.dart | 2 +- lib/src/utils/string_utils.dart | 12 +++++++++ test/common.dart | 32 ++++++++++++++++++++++ test/dao/kanjis_dao_test.dart | 15 ++++++++--- test/utils/string_utils_test.dart | 44 +++++++++++++++++++++++++++++++ 5 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 test/utils/string_utils_test.dart diff --git a/lib/src/dao/kanjis_dao.dart b/lib/src/dao/kanjis_dao.dart index 9fc4a7f..7bc0b76 100644 --- a/lib/src/dao/kanjis_dao.dart +++ b/lib/src/dao/kanjis_dao.dart @@ -327,7 +327,7 @@ class KanjisDao extends DatabaseAccessor with _$KanjisDaoMixin { if (cleanedText.isEmpty) return []; // If given a single kanji character return only that - if (cleanedText.length == 1 && _kanaKit.isKanji(cleanedText)) { + if (cleanedText.runes.length == 1 && cleanedText.isKanji()) { final kanji = await getKanji(cleanedText); if (kanji != null) return [kanji]; } diff --git a/lib/src/utils/string_utils.dart b/lib/src/utils/string_utils.dart index 1407732..42bb6be 100644 --- a/lib/src/utils/string_utils.dart +++ b/lib/src/utils/string_utils.dart @@ -29,6 +29,18 @@ extension JapaneseTextHelpers on String { } } + // Checks that the string is non-empty and contains only kanji + bool isKanji() => isNotEmpty && runes.every(_isCodePointKanji); + + static bool _isCodePointKanji(int codePoint) => + (codePoint >= 0x4E00 && codePoint <= 0x9FFF) || // CJK Unified Ideographs + (codePoint >= 0x3400 && codePoint <= 0x4DBF) || // CJK Extension A + (codePoint >= 0xF900 && codePoint <= 0xFAFF) || // CJK Compatibility + (codePoint >= 0x20000 && codePoint <= 0x2EBEF) || // CJK Extensions B-F + (codePoint >= 0x2F800 && codePoint <= 0x2FA1F) || // CJK Compat. Suppl. + (codePoint >= 0x30000 && codePoint <= 0x323AF) || // CJK Extensions G-H + codePoint == 0x3005; // 々 kanji repetition mark + // To be used with names for my dictionary lists and flashcard sets String sanitizeName() { // Remove new line characters diff --git a/test/common.dart b/test/common.dart index 5ca3a8a..d89584e 100644 --- a/test/common.dart +++ b/test/common.dart @@ -1400,6 +1400,38 @@ const shortKanjidic2 = ''' ゆく + + +𠮟 + +20B9F +1-47-52 + + +30 + + +8 +5 +53F1 + + +210 + + +1-3-2 + + + +シツ +シチ + +しか.る +scold +reprove + + + '''; diff --git a/test/dao/kanjis_dao_test.dart b/test/dao/kanjis_dao_test.dart index 65564e5..6a987b9 100644 --- a/test/dao/kanjis_dao_test.dart +++ b/test/dao/kanjis_dao_test.dart @@ -384,6 +384,12 @@ void main() { expect(results[0].kanji, '悪'); }); + test('Single kanji outside the basic CJK block', () async { + final results = await database.kanjisDao.search('𠮟'); + expect(results.length, 1); + expect(results[0].kanji, '𠮟'); + }); + test('Meaning single word', () async { final results = await database.kanjisDao.search('asia'); expect(results.length, 1); @@ -457,13 +463,15 @@ void main() { test('Reading partial', () async { // On reading var results = await database.kanjisDao.search('し'); - expect(results.length, 1); + expect(results.length, 2); expect(results[0].kanji, '心'); + expect(results[1].kanji, '𠮟'); // On reading katakana results = await database.kanjisDao.search('シ'); - expect(results.length, 1); + expect(results.length, 2); expect(results[0].kanji, '心'); + expect(results[1].kanji, '𠮟'); // Kun reading results = await database.kanjisDao.search('ここ'); @@ -520,8 +528,9 @@ void main() { test('Reading romaji partial', () async { // On reading var results = await database.kanjisDao.search('sh'); - expect(results.length, 1); + expect(results.length, 2); expect(results[0].kanji, '心'); + expect(results[1].kanji, '𠮟'); // Kun reading results = await database.kanjisDao.search('koko'); diff --git a/test/utils/string_utils_test.dart b/test/utils/string_utils_test.dart new file mode 100644 index 0000000..d59f3ed --- /dev/null +++ b/test/utils/string_utils_test.dart @@ -0,0 +1,44 @@ +import 'package:sagase_dictionary/src/utils/string_utils.dart'; +import 'package:test/test.dart'; + +void main() { + group('JapaneseTextHelpers', () { + group('isKanji', () { + test('Basic CJK block', () { + expect('亜'.isKanji(), true); + expect('悪'.isKanji(), true); + }); + + test('Kanji outside the basic CJK block', () { + // CJK extension A + expect('㐂'.isKanji(), true); + // CJK compatibility ideograph + expect('﨑'.isKanji(), true); + // Supplementary plane (CJK extension B) + expect('𠮟'.isKanji(), true); + }); + + test('Multiple kanji', () { + expect('日本語'.isKanji(), true); + }); + + test('Kanji repetition mark', () { + expect('々'.isKanji(), true); + expect('人々'.isKanji(), true); + }); + + test('Non-kanji', () { + expect(''.isKanji(), false); + expect('a'.isKanji(), false); + expect('あ'.isKanji(), false); + expect('ア'.isKanji(), false); + expect('。'.isKanji(), false); + }); + + test('Mixed kanji and non-kanji', () { + expect('行く'.isKanji(), false); + expect('日本語a'.isKanji(), false); + }); + }); + }); +} From 4e8a1c43908865f9ff5212ad1f56513c8f9ce4be Mon Sep 17 00:00:00 2001 From: Hampus Hammarlund Date: Fri, 12 Jun 2026 23:03:51 +0900 Subject: [PATCH 04/13] fix: improve wildcard searching --- lib/src/dao/vocabs_dao.dart | 6 ++++-- test/dao/vocabs_dao_test.dart | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/src/dao/vocabs_dao.dart b/lib/src/dao/vocabs_dao.dart index f1b9d9a..f1ec3b1 100644 --- a/lib/src/dao/vocabs_dao.dart +++ b/lib/src/dao/vocabs_dao.dart @@ -431,7 +431,7 @@ class VocabsDao extends DatabaseAccessor with _$VocabsDaoMixin { Future> search(String text) async { String cleanedText = RegExp.escape(text) .replaceAll('_', '') - .replaceAll('%', r'\%') + .replaceAll('%', '') .toLowerCase() .removeDiacritics(); @@ -520,9 +520,11 @@ class VocabsDao extends DatabaseAccessor with _$VocabsDaoMixin { } } else { // Japanese text + final searchByReading = + _kanaKit.isKana(cleanedText.replaceAll(r'\*', '')); cleanedText = cleanedText.replaceAll(r'\*', '_'); - if (_kanaKit.isKana(cleanedText)) { + if (searchByReading) { // Search by reading final searchReading = Subquery( db.select(db.vocabReadings) diff --git a/test/dao/vocabs_dao_test.dart b/test/dao/vocabs_dao_test.dart index 4aa968a..0fa8190 100644 --- a/test/dao/vocabs_dao_test.dart +++ b/test/dao/vocabs_dao_test.dart @@ -450,6 +450,21 @@ void main() { expect(results.length, 1); expect(results[0].id, 1000220); }); + + test('Searching with wildcard in kana', () async { + final results = await database.vocabsDao.search('めい*く'); + expect(results.length, 1); + expect(results[0].id, 1000220); + }); + + test('Searching with LIKE wildcard characters', () async { + expect(await database.vocabsDao.search('%'), isEmpty); + + // Wildcards are removed from the search text + final results = await database.vocabsDao.search('明%白'); + expect(results.length, 1); + expect(results[0].id, 1000220); + }); }); test('VocabNote', () async { From 29c1297bc03c1dc4774dd620d4f9d61b32885c48 Mon Sep 17 00:00:00 2001 From: Hampus Hammarlund Date: Fri, 12 Jun 2026 23:07:38 +0900 Subject: [PATCH 05/13] fix: improve performance and data consistency for my dictionary list import --- lib/src/dao/my_dictionary_lists_dao.dart | 87 ++++++++++------------ test/dao/my_dictionary_lists_dao_test.dart | 19 +++++ 2 files changed, 58 insertions(+), 48 deletions(-) diff --git a/lib/src/dao/my_dictionary_lists_dao.dart b/lib/src/dao/my_dictionary_lists_dao.dart index 90da9fa..4414ead 100644 --- a/lib/src/dao/my_dictionary_lists_dao.dart +++ b/lib/src/dao/my_dictionary_lists_dao.dart @@ -257,49 +257,46 @@ class MyDictionaryListsDao extends DatabaseAccessor String sanitizedName = dictionaryList.name.sanitizeName(); if (sanitizedName.isEmpty) sanitizedName = 'My list'; - // Delete existing my dictionary list if it exists - final conflicting = await (db.select(db.myDictionaryLists) - ..where((list) => list.id.equals(dictionaryList.id))) - .getSingleOrNull(); - if (conflicting != null) await deleteMyDictionaryList(conflicting); - - // Add my dictionary list itself - await db.into(db.myDictionaryLists).insert( - MyDictionaryListsCompanion( - id: Value(dictionaryList.id), - name: Value(sanitizedName), - ), - ); + await db.transaction(() async { + // Delete existing my dictionary list if it exists + final conflicting = await (db.select(db.myDictionaryLists) + ..where((list) => list.id.equals(dictionaryList.id))) + .getSingleOrNull(); + if (conflicting != null) await deleteMyDictionaryList(conflicting); + + // Add my dictionary list itself + await db.into(db.myDictionaryLists).insert( + MyDictionaryListsCompanion( + id: Value(dictionaryList.id), + name: Value(sanitizedName), + ), + ); - // Add dictionary list item for all valid vocab - // In reverse order to preserve order for user - final vocabList = await db.vocabsDao.validateAll(dictionaryList.vocab); - for (final vocab in vocabList.reversed) { - await addDictionaryItem(dictionaryList, vocab); - } + // Add dictionary list items for all valid vocab + // In reverse order to preserve order for user + final vocabList = await db.vocabsDao.validateAll(dictionaryList.vocab); + await addDictionaryItems(dictionaryList, vocabList.reversed.toList()); - // Add dictionary list item for all valid kanji - // In reverse order to preserve order for user - final kanjiList = await db.kanjisDao.validateAll(dictionaryList.kanji); - for (final kanji in kanjiList.reversed) { - await addDictionaryItem(dictionaryList, kanji); - } + // Add dictionary list items for all valid kanji + // In reverse order to preserve order for user + final kanjiList = await db.kanjisDao.validateAll(dictionaryList.kanji); + await addDictionaryItems(dictionaryList, kanjiList.reversed.toList()); - // Add dictionary list item for all valid grammar - // In reverse order to preserve order for user - final grammarList = await db.grammarsDao.validateAll(dictionaryList.grammar); - for (final grammar in grammarList.reversed) { - await addDictionaryItem(dictionaryList, grammar); - } + // Add dictionary list items for all valid grammar + // In reverse order to preserve order for user + final grammarList = + await db.grammarsDao.validateAll(dictionaryList.grammar); + await addDictionaryItems(dictionaryList, grammarList.reversed.toList()); - // Set original timestamp - await (db.update(db.myDictionaryLists) - ..where((list) => list.id.equals(dictionaryList.id))) - .write( - MyDictionaryListsCompanion( - timestamp: Value(dictionaryList.timestamp), - ), - ); + // Set original timestamp + await (db.update(db.myDictionaryLists) + ..where((list) => list.id.equals(dictionaryList.id))) + .write( + MyDictionaryListsCompanion( + timestamp: Value(dictionaryList.timestamp), + ), + ); + }); } Future importShare(String source) async { @@ -319,25 +316,19 @@ class MyDictionaryListsDao extends DatabaseAccessor // In reverse order to preserve order for user final validatedVocab = await db.vocabsDao.validateAll(sourceDictionaryList.vocab); - for (final vocab in validatedVocab.reversed) { - await addDictionaryItem(myList, vocab); - } + await addDictionaryItems(myList, validatedVocab.reversed.toList()); // Validate kanji and add dictionary items // In reverse order to preserve order for user final validatedKanji = await db.kanjisDao.validateAll(sourceDictionaryList.kanji); - for (final kanji in validatedKanji.reversed) { - await addDictionaryItem(myList, kanji); - } + await addDictionaryItems(myList, validatedKanji.reversed.toList()); // Validate grammar and add dictionary items // In reverse order to preserve order for user final validatedGrammar = await db.grammarsDao.validateAll(sourceDictionaryList.grammar); - for (final grammar in validatedGrammar.reversed) { - await addDictionaryItem(myList, grammar); - } + await addDictionaryItems(myList, validatedGrammar.reversed.toList()); return myList; }); diff --git a/test/dao/my_dictionary_lists_dao_test.dart b/test/dao/my_dictionary_lists_dao_test.dart index 3f251cb..8c4e94b 100644 --- a/test/dao/my_dictionary_lists_dao_test.dart +++ b/test/dao/my_dictionary_lists_dao_test.dart @@ -518,6 +518,25 @@ void main() { expect(result.grammarIds[0], 2); }); + test('importBackup - duplicate items', () async { + String export = MyDictionaryList( + id: 0, + name: 'list1', + timestamp: DateTime.now(), + vocab: [1003430, 1003430], + kanji: ['亞'.kanjiCodePoint(), '亞'.kanjiCodePoint()], + grammar: [], + ).toBackupJson(); + + await database.myDictionaryListsDao.importBackup(export); + final myList = await database.myDictionaryListsDao.get(0); + + final result = + await database.myDictionaryListsDao.getDictionaryListItems(myList); + expect(result.vocabIds, [1003430]); + expect(result.kanjiIds, ['亞'.kanjiCodePoint()]); + }); + test('importShare', () async { String export = MyDictionaryList( id: 0, From 2367a0c4fe3a84ccac8c857cad1796cc0bf69b0c Mon Sep 17 00:00:00 2001 From: Hampus Hammarlund Date: Sat, 20 Jun 2026 22:36:55 +0900 Subject: [PATCH 06/13] fix: add missing transactions to deletions --- lib/src/dao/flashcard_sets_dao.dart | 16 ++++++++++------ lib/src/dao/my_dictionary_lists_dao.dart | 2 ++ lib/src/dao/proper_nouns_dao.dart | 5 +++++ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/src/dao/flashcard_sets_dao.dart b/lib/src/dao/flashcard_sets_dao.dart index c156f16..4880cb0 100644 --- a/lib/src/dao/flashcard_sets_dao.dart +++ b/lib/src/dao/flashcard_sets_dao.dart @@ -35,11 +35,13 @@ class FlashcardSetsDao extends DatabaseAccessor } Future deleteFlashcardSet(FlashcardSet flashcardSet) async { - await deleteFlashcardSetReports(flashcardSet); + await db.transaction(() async { + await deleteFlashcardSetReports(flashcardSet); - await (db.delete(db.flashcardSets) - ..where((set) => set.id.equals(flashcardSet.id))) - .go(); + await (db.delete(db.flashcardSets) + ..where((set) => set.id.equals(flashcardSet.id))) + .go(); + }); } Future> getAll() async { @@ -152,7 +154,9 @@ class FlashcardSetsDao extends DatabaseAccessor } Future deleteAll() async { - await db.delete(db.flashcardSets).go(); - await db.delete(db.flashcardSetReports).go(); + await db.transaction(() async { + await db.delete(db.flashcardSets).go(); + await db.delete(db.flashcardSetReports).go(); + }); } } diff --git a/lib/src/dao/my_dictionary_lists_dao.dart b/lib/src/dao/my_dictionary_lists_dao.dart index 4414ead..f9c369b 100644 --- a/lib/src/dao/my_dictionary_lists_dao.dart +++ b/lib/src/dao/my_dictionary_lists_dao.dart @@ -335,7 +335,9 @@ class MyDictionaryListsDao extends DatabaseAccessor } Future deleteAll() async { + await db.transaction(() async { await db.delete(db.myDictionaryLists).go(); await db.delete(db.myDictionaryListItems).go(); + }); } } diff --git a/lib/src/dao/proper_nouns_dao.dart b/lib/src/dao/proper_nouns_dao.dart index 9a0769c..f2863d2 100644 --- a/lib/src/dao/proper_nouns_dao.dart +++ b/lib/src/dao/proper_nouns_dao.dart @@ -15,6 +15,7 @@ class ProperNounsDao extends DatabaseAccessor Future importProperNouns(String path) async { await db.customStatement('ATTACH DATABASE ? AS proper_noun_db', [path]); + try { await db.transaction(() async { await db.customStatement( 'INSERT INTO ${db.properNouns.actualTableName} SELECT * FROM proper_noun_db.${db.properNouns.actualTableName}', @@ -23,12 +24,16 @@ class ProperNounsDao extends DatabaseAccessor 'INSERT INTO ${db.properNounRomajiWords.actualTableName} SELECT * FROM proper_noun_db.${db.properNounRomajiWords.actualTableName}', ); }); + } finally { await db.customStatement('DETACH DATABASE proper_noun_db'); + } } Future deleteProperNouns() async { + await db.transaction(() async { await db.delete(db.properNouns).go(); await db.delete(db.properNounRomajiWords).go(); + }); } Future> getByWriting(String text) async { From 28d0e44c02323e773a03908183271c2fd0572902 Mon Sep 17 00:00:00 2001 From: Hampus Hammarlund Date: Sat, 20 Jun 2026 22:39:37 +0900 Subject: [PATCH 07/13] fix: truncate names on runes --- lib/src/utils/string_utils.dart | 4 ++-- test/utils/string_utils_test.dart | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/src/utils/string_utils.dart b/lib/src/utils/string_utils.dart index 42bb6be..6a3a73d 100644 --- a/lib/src/utils/string_utils.dart +++ b/lib/src/utils/string_utils.dart @@ -1,4 +1,3 @@ -import 'dart:math'; import 'package:diacritic/diacritic.dart' as diacritic; extension JapaneseTextHelpers on String { @@ -46,7 +45,8 @@ extension JapaneseTextHelpers on String { // Remove new line characters String name = replaceAll('\n', ''); // Enforce character length and trim whitespace - return name.substring(0, min(50, name.length)).trim(); + // Operates on runes to avoid splitting a surrogate pair at the cutoff + return String.fromCharCodes(name.runes.take(50)).trim(); } static final splitWordsRegExp = RegExp(r'\b[^\s]+\b'); diff --git a/test/utils/string_utils_test.dart b/test/utils/string_utils_test.dart index d59f3ed..b134a67 100644 --- a/test/utils/string_utils_test.dart +++ b/test/utils/string_utils_test.dart @@ -40,5 +40,22 @@ void main() { expect('日本語a'.isKanji(), false); }); }); + + group('sanitizeName', () { + test('Removes new lines and trims whitespace', () { + expect(' my\nlist '.sanitizeName(), 'mylist'); + }); + + test('Enforces character limit', () { + expect(('a' * 50 + 'b').sanitizeName(), 'a' * 50); + }); + + test('Does not split a surrogate pair at the cutoff', () { + // 𠮟 consists of 2 code units; cutting at 50 code units would + // leave an unpaired surrogate + final name = ('a' * 49 + '𠮟𠮟').sanitizeName(); + expect(name, 'a' * 49 + '𠮟'); + }); + }); }); } From dc9a9aea401350298abb8b7f3053618aca98288c Mon Sep 17 00:00:00 2001 From: Hampus Hammarlund Date: Sat, 20 Jun 2026 22:43:51 +0900 Subject: [PATCH 08/13] fix: return get all grammar in same order as arg list --- lib/src/dao/grammars_dao.dart | 48 ++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/lib/src/dao/grammars_dao.dart b/lib/src/dao/grammars_dao.dart index c69e670..fb8b43e 100644 --- a/lib/src/dao/grammars_dao.dart +++ b/lib/src/dao/grammars_dao.dart @@ -21,23 +21,37 @@ class GrammarsDao extends DatabaseAccessor final baseQuery = db.select(db.grammars) ..where((grammars) => grammars.id.isIn(idList)); - if (frontType == null) return baseQuery.get(); - - final query = baseQuery.join([ - leftOuterJoin( - db.spacedRepetitionDatas, - Expression.and([ - db.spacedRepetitionDatas.itemId.equalsExp(db.grammars.id), - db.spacedRepetitionDatas.itemType - .equals(DictionaryItemType.grammar.index), - db.spacedRepetitionDatas.frontType.equals(frontType.index), - ])) - ]).map( - (row) => row.readTable(db.grammars) - ..spacedRepetitionData = row.readTableOrNull(db.spacedRepetitionDatas), - ); - - return query.get(); + final List grammars; + if (frontType == null) { + grammars = await baseQuery.get(); + } else { + final query = baseQuery.join([ + leftOuterJoin( + db.spacedRepetitionDatas, + Expression.and([ + db.spacedRepetitionDatas.itemId.equalsExp(db.grammars.id), + db.spacedRepetitionDatas.itemType + .equals(DictionaryItemType.grammar.index), + db.spacedRepetitionDatas.frontType.equals(frontType.index), + ])) + ]).map( + (row) => row.readTable(db.grammars) + ..spacedRepetitionData = + row.readTableOrNull(db.spacedRepetitionDatas), + ); + + grammars = await query.get(); + } + + // Put the results in the same order as the input + final grammarMap = {for (var grammar in grammars) grammar.id: grammar}; + List grammarList = []; + for (final id in idList) { + final grammar = grammarMap[id]; + if (grammar != null) grammarList.add(grammar); + } + + return grammarList; } Future> validateAll(List idList) async { From 49e08f611c55375fc98d05a388bc12b2c025d773 Mon Sep 17 00:00:00 2001 From: Hampus Hammarlund Date: Sat, 20 Jun 2026 22:59:21 +0900 Subject: [PATCH 09/13] fix: improve wildcard searching and sorting --- lib/src/dao/kanjis_dao.dart | 12 ++++++++++-- lib/src/dao/proper_nouns_dao.dart | 29 +++++++++++++++++------------ lib/src/dao/vocabs_dao.dart | 12 ++++++------ 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/lib/src/dao/kanjis_dao.dart b/lib/src/dao/kanjis_dao.dart index 7bc0b76..355048c 100644 --- a/lib/src/dao/kanjis_dao.dart +++ b/lib/src/dao/kanjis_dao.dart @@ -323,7 +323,12 @@ class KanjisDao extends DatabaseAccessor with _$KanjisDaoMixin { } Future> search(String text) async { - final cleanedText = RegExp.escape(text).toLowerCase().removeDiacritics(); + final cleanedText = text + .replaceAll('_', '') + .replaceAll('%', '') + .toLowerCase() + .removeDiacritics() + .trim(); if (cleanedText.isEmpty) return []; // If given a single kanji character return only that @@ -539,7 +544,10 @@ class KanjisDao extends DatabaseAccessor with _$KanjisDaoMixin { .get(); // Sort exact matches of the search string to the top - final regExp = RegExp(r'\b' + cleanedText, caseSensitive: false); + final regExp = RegExp( + r'\b' + RegExp.escape(cleanedText), + caseSensitive: false, + ); final List exactMatchKanjiList = []; for (int i = 0; i < unsortedKanjiList.length; i++) { diff --git a/lib/src/dao/proper_nouns_dao.dart b/lib/src/dao/proper_nouns_dao.dart index f2863d2..6308c2a 100644 --- a/lib/src/dao/proper_nouns_dao.dart +++ b/lib/src/dao/proper_nouns_dao.dart @@ -16,23 +16,23 @@ class ProperNounsDao extends DatabaseAccessor Future importProperNouns(String path) async { await db.customStatement('ATTACH DATABASE ? AS proper_noun_db', [path]); try { - await db.transaction(() async { - await db.customStatement( - 'INSERT INTO ${db.properNouns.actualTableName} SELECT * FROM proper_noun_db.${db.properNouns.actualTableName}', - ); - await db.customStatement( - 'INSERT INTO ${db.properNounRomajiWords.actualTableName} SELECT * FROM proper_noun_db.${db.properNounRomajiWords.actualTableName}', - ); - }); + await db.transaction(() async { + await db.customStatement( + 'INSERT INTO ${db.properNouns.actualTableName} SELECT * FROM proper_noun_db.${db.properNouns.actualTableName}', + ); + await db.customStatement( + 'INSERT INTO ${db.properNounRomajiWords.actualTableName} SELECT * FROM proper_noun_db.${db.properNounRomajiWords.actualTableName}', + ); + }); } finally { - await db.customStatement('DETACH DATABASE proper_noun_db'); + await db.customStatement('DETACH DATABASE proper_noun_db'); } } Future deleteProperNouns() async { await db.transaction(() async { - await db.delete(db.properNouns).go(); - await db.delete(db.properNounRomajiWords).go(); + await db.delete(db.properNouns).go(); + await db.delete(db.properNounRomajiWords).go(); }); } @@ -78,7 +78,12 @@ class ProperNounsDao extends DatabaseAccessor } Future> search(String text) async { - final cleanedText = RegExp.escape(text).toLowerCase().removeDiacritics(); + final cleanedText = text + .replaceAll('_', '') + .replaceAll('%', '') + .toLowerCase() + .removeDiacritics() + .trim(); if (cleanedText.isEmpty) return []; if (_kanaKit.isRomaji(cleanedText)) { diff --git a/lib/src/dao/vocabs_dao.dart b/lib/src/dao/vocabs_dao.dart index f1ec3b1..baf3902 100644 --- a/lib/src/dao/vocabs_dao.dart +++ b/lib/src/dao/vocabs_dao.dart @@ -429,7 +429,7 @@ class VocabsDao extends DatabaseAccessor with _$VocabsDaoMixin { } Future> search(String text) async { - String cleanedText = RegExp.escape(text) + String cleanedText = text .replaceAll('_', '') .replaceAll('%', '') .toLowerCase() @@ -520,9 +520,8 @@ class VocabsDao extends DatabaseAccessor with _$VocabsDaoMixin { } } else { // Japanese text - final searchByReading = - _kanaKit.isKana(cleanedText.replaceAll(r'\*', '')); - cleanedText = cleanedText.replaceAll(r'\*', '_'); + final searchByReading = _kanaKit.isKana(cleanedText.replaceAll('*', '')); + cleanedText = cleanedText.replaceAll('*', '_'); if (searchByReading) { // Search by reading @@ -704,14 +703,15 @@ class VocabsDao extends DatabaseAccessor with _$VocabsDaoMixin { // Nested list 4: no match found List> nestedSortingList = [[], [], [], [], []]; + final escapedText = RegExp.escape(cleanedText); // Match word starting with search text final searchTextRegExp = RegExp( - r'\b' + cleanedText, + r'\b' + escapedText, caseSensitive: false, ); // Match search text at the start of string or after ';' and ignore leading/trailing parenthesis final startingRegExp = RegExp( - r'(^|(; ))(\([^)]*\) )?\b' + cleanedText + r'( \([^)]*\))?', + r'(^|(; ))(\([^)]*\) )?\b' + escapedText + r'( \([^)]*\))?', caseSensitive: false, ); // Same as startingRegExp but with end of sub-definition From 5c94de91e62f257455712a46df79098a37657785 Mon Sep 17 00:00:00 2001 From: Hampus Hammarlund Date: Sun, 21 Jun 2026 10:12:43 +0900 Subject: [PATCH 10/13] fix: improve kanji components search query --- lib/src/dao/kanjis_dao.dart | 40 +++++++++++++------------------------ 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/lib/src/dao/kanjis_dao.dart b/lib/src/dao/kanjis_dao.dart index 355048c..b758866 100644 --- a/lib/src/dao/kanjis_dao.dart +++ b/lib/src/dao/kanjis_dao.dart @@ -270,32 +270,6 @@ class KanjisDao extends DatabaseAccessor with _$KanjisDaoMixin { final selectedCodePoints = components.map((c) => c.kanjiCodePoint()).toSet(); - final kanji = await (db.select(db.kanjis).join([ - innerJoin( - db.kanjiComponentConnections, - db.kanjiComponentConnections.kanjiCodePoint.equalsExp(db.kanjis.id), - useColumns: false, - ), - ]) - ..where(db.kanjiComponentConnections.componentCodePoint - .isIn(selectedCodePoints)) - ..groupBy( - [db.kanjis.id], - having: db.kanjiComponentConnections.componentCodePoint - .count(distinct: true) - .equals(selectedCodePoints.length), - ) - ..orderBy([ - OrderingTerm.asc(db.kanjis.strokeCount), - OrderingTerm.asc(db.kanjis.frequency, nulls: NullsOrder.last), - ])) - .map((row) => row.readTable(db.kanjis).kanji) - .get(); - - if (kanji.isEmpty) { - return (kanji: const [], validComponents: const []); - } - final matchingIds = db.selectOnly(db.kanjiComponentConnections) ..addColumns([db.kanjiComponentConnections.kanjiCodePoint]) ..where(db.kanjiComponentConnections.componentCodePoint @@ -307,6 +281,20 @@ class KanjisDao extends DatabaseAccessor with _$KanjisDaoMixin { .equals(selectedCodePoints.length), ); + final kanji = await (db.select(db.kanjis) + ..where((kanji) => kanji.id.isInQuery(matchingIds)) + ..orderBy([ + (kanji) => OrderingTerm.asc(kanji.strokeCount), + (kanji) => + OrderingTerm.asc(kanji.frequency, nulls: NullsOrder.last), + ])) + .map((kanji) => kanji.kanji) + .get(); + + if (kanji.isEmpty) { + return (kanji: const [], validComponents: const []); + } + final validComponents = await (db.selectOnly(db.kanjiComponentConnections, distinct: true) ..addColumns([db.kanjiComponentConnections.componentCodePoint]) From 1ca7b15ea694becc84bb02b5372a63cf6a53ec9d Mon Sep 17 00:00:00 2001 From: Hampus Hammarlund Date: Sun, 21 Jun 2026 10:36:49 +0900 Subject: [PATCH 11/13] fix: abstract repeated code --- lib/src/dao/kanjis_dao.dart | 103 +++++++------------------------ lib/src/dao/vocabs_dao.dart | 115 ++++++----------------------------- lib/src/utils/dao_utils.dart | 22 +++++++ 3 files changed, 65 insertions(+), 175 deletions(-) create mode 100644 lib/src/utils/dao_utils.dart diff --git a/lib/src/dao/kanjis_dao.dart b/lib/src/dao/kanjis_dao.dart index b758866..8c993e0 100644 --- a/lib/src/dao/kanjis_dao.dart +++ b/lib/src/dao/kanjis_dao.dart @@ -5,6 +5,7 @@ import 'package:sagase_dictionary/src/datamodels/kanji/kanji_notes.dart'; import 'package:sagase_dictionary/src/datamodels/kanji_component_connections.dart'; import 'package:sagase_dictionary/src/datamodels/kanjis.dart'; import 'package:sagase_dictionary/src/datamodels/spaced_repetition_datas.dart'; +import 'package:sagase_dictionary/src/utils/dao_utils.dart'; import 'package:sagase_dictionary/src/utils/enums.dart'; import 'package:sagase_dictionary/src/utils/string_utils.dart'; @@ -51,15 +52,23 @@ class KanjisDao extends DatabaseAccessor with _$KanjisDaoMixin { if (kanji == null) return null; - List onReadings = []; - List kunReadings = []; - List nanori = []; - final readings = await (db.select(db.kanjiReadings) ..where((reading) => reading.kanjiId.equals(kanji!.id)) ..orderBy([(reading) => OrderingTerm.asc(reading.id)])) .get(); + _setReadings(kanji, readings); + + final note = await getNote(kanji.id); + + return kanji..note = note?.note; + } + + void _setReadings(Kanji kanji, List readings) { + List onReadings = []; + List kunReadings = []; + List nanori = []; + for (final reading in readings) { switch (reading.type) { case KanjiReadingType.on: @@ -74,13 +83,10 @@ class KanjisDao extends DatabaseAccessor with _$KanjisDaoMixin { } } - final note = await getNote(kanji.id); - - return kanji + kanji ..onReadings = onReadings.isEmpty ? null : onReadings ..kunReadings = kunReadings.isEmpty ? null : kunReadings - ..nanori = nanori.isEmpty ? null : nanori - ..note = note?.note; + ..nanori = nanori.isEmpty ? null : nanori; } Future getKanji(String kanji, {FrontType? frontType}) async { @@ -92,7 +98,7 @@ class KanjisDao extends DatabaseAccessor with _$KanjisDaoMixin { final baseQuery = db.select(db.kanjis)..where((row) => row.id.isIn(idList)); - late Map kanjiMap = {}; + final Map kanjiMap; if (frontType == null) { // Get kanji only kanjiMap = {for (var kanji in (await baseQuery.get())) kanji.id: kanji}; @@ -121,41 +127,10 @@ class KanjisDao extends DatabaseAccessor with _$KanjisDaoMixin { ..orderBy([(reading) => OrderingTerm.asc(reading.id)])) .get(); - if (readings.isNotEmpty) { - int currentKanjiId = readings[0].kanjiId; - List onReadings = []; - List kunReadings = []; - List nanori = []; - for (final reading in readings) { - // If id does not match got to new kanji, add current readings and clear lists - if (reading.kanjiId != currentKanjiId) { - kanjiMap[currentKanjiId]! - ..onReadings = onReadings.isEmpty ? null : onReadings - ..kunReadings = kunReadings.isEmpty ? null : kunReadings - ..nanori = nanori.isEmpty ? null : nanori; - currentKanjiId = reading.kanjiId; - onReadings = []; - kunReadings = []; - nanori = []; - } - switch (reading.type) { - case KanjiReadingType.on: - onReadings.add(reading); - break; - case KanjiReadingType.kun: - kunReadings.add(reading); - break; - case KanjiReadingType.nanori: - nanori.add(reading); - break; - } - } - // Add remaining readings - kanjiMap[currentKanjiId]! - ..onReadings = onReadings.isEmpty ? null : onReadings - ..kunReadings = kunReadings.isEmpty ? null : kunReadings - ..nanori = nanori.isEmpty ? null : nanori; - } + groupRowsById(readings, (reading) => reading.kanjiId, (id, rows) { + final kanji = kanjiMap[id]; + if (kanji != null) _setReadings(kanji, rows); + }); final notes = await (db.select(db.kanjiNotes) ..where((note) => note.id.isIn(idList))) @@ -184,41 +159,9 @@ class KanjisDao extends DatabaseAccessor with _$KanjisDaoMixin { ..orderBy([(reading) => OrderingTerm.asc(reading.id)])) .get(); - if (readings.isNotEmpty) { - int currentKanjiId = readings[0].kanjiId; - List onReadings = []; - List kunReadings = []; - List nanori = []; - for (final reading in readings) { - // If id does not match got to new kanji, add current readings and clear lists - if (reading.kanjiId != currentKanjiId) { - kanjiMap[currentKanjiId]! - ..onReadings = onReadings.isEmpty ? null : onReadings - ..kunReadings = kunReadings.isEmpty ? null : kunReadings - ..nanori = nanori.isEmpty ? null : nanori; - currentKanjiId = reading.kanjiId; - onReadings = []; - kunReadings = []; - nanori = []; - } - switch (reading.type) { - case KanjiReadingType.on: - onReadings.add(reading); - break; - case KanjiReadingType.kun: - kunReadings.add(reading); - break; - case KanjiReadingType.nanori: - nanori.add(reading); - break; - } - } - // Add remaining readings - kanjiMap[currentKanjiId]! - ..onReadings = onReadings.isEmpty ? null : onReadings - ..kunReadings = kunReadings.isEmpty ? null : kunReadings - ..nanori = nanori.isEmpty ? null : nanori; - } + groupRowsById(readings, (reading) => reading.kanjiId, (id, rows) { + _setReadings(kanjiMap[id]!, rows); + }); final notes = await (db.select(db.kanjiNotes) ..where((note) => note.id.isIn(kanjiMap.keys))) diff --git a/lib/src/dao/vocabs_dao.dart b/lib/src/dao/vocabs_dao.dart index baf3902..069770c 100644 --- a/lib/src/dao/vocabs_dao.dart +++ b/lib/src/dao/vocabs_dao.dart @@ -3,6 +3,7 @@ import 'package:kana_kit/kana_kit.dart'; import 'package:sagase_dictionary/src/database.dart'; import 'package:sagase_dictionary/src/datamodels/vocab/vocab_notes.dart'; import 'package:sagase_dictionary/src/datamodels/vocabs.dart'; +import 'package:sagase_dictionary/src/utils/dao_utils.dart'; import 'package:sagase_dictionary/src/utils/enums.dart'; import 'package:sagase_dictionary/src/utils/string_utils.dart'; @@ -73,7 +74,7 @@ class VocabsDao extends DatabaseAccessor with _$VocabsDaoMixin { final baseQuery = db.select(db.vocabs) ..where((vocab) => vocab.id.isIn(idList)); - late Map vocabMap = {}; + final Map vocabMap; if (frontType == null) { // Get vocab only vocabMap = {for (var vocab in (await baseQuery.get())) vocab.id: vocab}; @@ -108,23 +109,9 @@ class VocabsDao extends DatabaseAccessor with _$VocabsDaoMixin { ..orderBy([(reading) => OrderingTerm.asc(reading.id)])) .get(); - if (writings.isNotEmpty) { - int currentVocabId = writings[0].vocabId; - List currentWritings = []; - for (final writing in writings) { - // If id does not match got to new vocab, add current writings and clear list - if (writing.vocabId != currentVocabId) { - vocabMap[currentVocabId]!.writings = - currentWritings.isEmpty ? null : currentWritings; - currentVocabId = writing.vocabId; - currentWritings = []; - } - currentWritings.add(writing); - } - // Add remaining writings - vocabMap[currentVocabId]!.writings = - currentWritings.isEmpty ? null : currentWritings; - } + groupRowsById(writings, (writing) => writing.vocabId, (id, rows) { + vocabMap[id]?.writings = rows; + }); final readings = await readingsFuture; // Start loading definitions while processing readings @@ -133,42 +120,18 @@ class VocabsDao extends DatabaseAccessor with _$VocabsDaoMixin { ..orderBy([(definition) => OrderingTerm.asc(definition.id)])) .get(); - if (readings.isNotEmpty) { - int currentVocabId = readings[0].vocabId; - List currentReadings = []; - for (final reading in readings) { - // If id does not match got to new vocab, add current readings and clear list - if (reading.vocabId != currentVocabId) { - vocabMap[currentVocabId]!.readings = currentReadings; - currentVocabId = reading.vocabId; - currentReadings = []; - } - currentReadings.add(reading); - } - // Add remaining readings - vocabMap[currentVocabId]!.readings = currentReadings; - } + groupRowsById(readings, (reading) => reading.vocabId, (id, rows) { + vocabMap[id]?.readings = rows; + }); final definitions = await definitionFuture; // Start loading notes while processing definitions final noteFuture = (db.select(db.vocabNotes)..where((note) => note.id.isIn(idList))).get(); - if (definitions.isNotEmpty) { - int currentVocabId = definitions[0].vocabId; - List currentDefinitions = []; - for (final definition in definitions) { - // If id does not match got to new vocab, add current definitions and clear list - if (definition.vocabId != currentVocabId) { - vocabMap[currentVocabId]!.definitions = currentDefinitions; - currentVocabId = definition.vocabId; - currentDefinitions = []; - } - currentDefinitions.add(definition); - } - // Add remaining definitions - vocabMap[currentVocabId]!.definitions = currentDefinitions; - } + groupRowsById(definitions, (definition) => definition.vocabId, (id, rows) { + vocabMap[id]?.definitions = rows; + }); final notes = await noteFuture; for (final note in notes) { @@ -201,23 +164,9 @@ class VocabsDao extends DatabaseAccessor with _$VocabsDaoMixin { ..orderBy([(reading) => OrderingTerm.asc(reading.id)])) .get(); - if (writings.isNotEmpty) { - int currentVocabId = writings[0].vocabId; - List currentWritings = []; - for (final writing in writings) { - // If id does not match got to new vocab, add current writings and clear list - if (writing.vocabId != currentVocabId) { - vocabMap[currentVocabId]!.writings = - currentWritings.isEmpty ? null : currentWritings; - currentVocabId = writing.vocabId; - currentWritings = []; - } - currentWritings.add(writing); - } - // Add remaining writings - vocabMap[currentVocabId]!.writings = - currentWritings.isEmpty ? null : currentWritings; - } + groupRowsById(writings, (writing) => writing.vocabId, (id, rows) { + vocabMap[id]!.writings = rows; + }); final readings = await readingsFuture; // Start loading definitions while processing readings @@ -226,21 +175,9 @@ class VocabsDao extends DatabaseAccessor with _$VocabsDaoMixin { ..orderBy([(definition) => OrderingTerm.asc(definition.id)])) .get(); - if (readings.isNotEmpty) { - int currentVocabId = readings[0].vocabId; - List currentReadings = []; - for (final reading in readings) { - // If id does not match got to new vocab, add current readings and clear list - if (reading.vocabId != currentVocabId) { - vocabMap[currentVocabId]!.readings = currentReadings; - currentVocabId = reading.vocabId; - currentReadings = []; - } - currentReadings.add(reading); - } - // Add remaining readings - vocabMap[currentVocabId]!.readings = currentReadings; - } + groupRowsById(readings, (reading) => reading.vocabId, (id, rows) { + vocabMap[id]!.readings = rows; + }); final definitions = await definitionFuture; // Start loading notes while processing definitions @@ -248,21 +185,9 @@ class VocabsDao extends DatabaseAccessor with _$VocabsDaoMixin { ..where((note) => note.id.isIn(vocabMap.keys))) .get(); - if (definitions.isNotEmpty) { - int currentVocabId = definitions[0].vocabId; - List currentDefinitions = []; - for (final definition in definitions) { - // If id does not match got to new vocab, add current definitions and clear list - if (definition.vocabId != currentVocabId) { - vocabMap[currentVocabId]!.definitions = currentDefinitions; - currentVocabId = definition.vocabId; - currentDefinitions = []; - } - currentDefinitions.add(definition); - } - // Add remaining definitions - vocabMap[currentVocabId]!.definitions = currentDefinitions; - } + groupRowsById(definitions, (definition) => definition.vocabId, (id, rows) { + vocabMap[id]!.definitions = rows; + }); final notes = await noteFuture; for (final note in notes) { diff --git a/lib/src/utils/dao_utils.dart b/lib/src/utils/dao_utils.dart new file mode 100644 index 0000000..bd7e28c --- /dev/null +++ b/lib/src/utils/dao_utils.dart @@ -0,0 +1,22 @@ +// Groups consecutive rows sharing the same id, as given by idOf, and calls +// assign once per group with a non-empty list. Rows with the same id must be +// consecutive for the grouping to be complete. +void groupRowsById( + List rows, + int Function(T row) idOf, + void Function(int id, List rows) assign, +) { + if (rows.isEmpty) return; + + int currentId = idOf(rows.first); + List currentRows = []; + for (final row in rows) { + if (idOf(row) != currentId) { + assign(currentId, currentRows); + currentId = idOf(row); + currentRows = []; + } + currentRows.add(row); + } + assign(currentId, currentRows); +} From 847605593ef4054fb6b4e17365adc676eec275c4 Mon Sep 17 00:00:00 2001 From: Hampus Hammarlund Date: Sun, 21 Jun 2026 10:38:37 +0900 Subject: [PATCH 12/13] fix: add ordering to get all my lists --- lib/src/dao/my_dictionary_lists_dao.dart | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/src/dao/my_dictionary_lists_dao.dart b/lib/src/dao/my_dictionary_lists_dao.dart index f9c369b..c589712 100644 --- a/lib/src/dao/my_dictionary_lists_dao.dart +++ b/lib/src/dao/my_dictionary_lists_dao.dart @@ -55,9 +55,18 @@ class MyDictionaryListsDao extends DatabaseAccessor } Future> getAllFromList(List ids) async { + if (ids.isEmpty) return []; + + final lists = await (db.select(db.myDictionaryLists) + ..where((list) => list.id.isIn(ids))) + .get(); + + // Put the results in the same order as the input + final listMap = {for (var list in lists) list.id: list}; List dictionaryLists = []; for (final id in ids) { - dictionaryLists.add(await get(id)); + final list = listMap[id]; + if (list != null) dictionaryLists.add(list); } return dictionaryLists; } @@ -336,8 +345,8 @@ class MyDictionaryListsDao extends DatabaseAccessor Future deleteAll() async { await db.transaction(() async { - await db.delete(db.myDictionaryLists).go(); - await db.delete(db.myDictionaryListItems).go(); + await db.delete(db.myDictionaryLists).go(); + await db.delete(db.myDictionaryListItems).go(); }); } } From faa6ecfadccfb7565a55ad78a5f339816a9ce1b9 Mon Sep 17 00:00:00 2001 From: Hampus Hammarlund Date: Sun, 21 Jun 2026 21:46:07 +0900 Subject: [PATCH 13/13] feat: add delete all notes --- lib/src/dao/kanjis_dao.dart | 4 ++++ lib/src/dao/vocabs_dao.dart | 4 ++++ test/dao/kanjis_dao_test.dart | 4 ++++ test/dao/vocabs_dao_test.dart | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/lib/src/dao/kanjis_dao.dart b/lib/src/dao/kanjis_dao.dart index 8c993e0..74889c7 100644 --- a/lib/src/dao/kanjis_dao.dart +++ b/lib/src/dao/kanjis_dao.dart @@ -519,6 +519,10 @@ class KanjisDao extends DatabaseAccessor with _$KanjisDaoMixin { .go(); } + Future deleteAllNotes() async { + await db.delete(db.kanjiNotes).go(); + } + // Inverse of String.kanjiCodePoint() String _kanjiFromCodePoint(int codePoint) { if (codePoint <= 0xFFFF) return String.fromCharCode(codePoint); diff --git a/lib/src/dao/vocabs_dao.dart b/lib/src/dao/vocabs_dao.dart index 069770c..f33ddab 100644 --- a/lib/src/dao/vocabs_dao.dart +++ b/lib/src/dao/vocabs_dao.dart @@ -702,4 +702,8 @@ class VocabsDao extends DatabaseAccessor with _$VocabsDaoMixin { await (db.delete(db.vocabNotes)..where((note) => note.id.equals(vocabId))) .go(); } + + Future deleteAllNotes() async { + await db.delete(db.vocabNotes).go(); + } } diff --git a/test/dao/kanjis_dao_test.dart b/test/dao/kanjis_dao_test.dart index 6a987b9..7251db2 100644 --- a/test/dao/kanjis_dao_test.dart +++ b/test/dao/kanjis_dao_test.dart @@ -577,6 +577,10 @@ void main() { final deletedNote = await database.kanjisDao.getNote('亜'.kanjiCodePoint()); expect(deletedNote, null); + + await database.kanjisDao.deleteAllNotes(); + final remainingNotes = await database.kanjisDao.getAllNotes(); + expect(remainingNotes.length, 0); }); }); } diff --git a/test/dao/vocabs_dao_test.dart b/test/dao/vocabs_dao_test.dart index 0fa8190..2faa07d 100644 --- a/test/dao/vocabs_dao_test.dart +++ b/test/dao/vocabs_dao_test.dart @@ -488,6 +488,10 @@ void main() { await database.vocabsDao.deleteNote(1000220); final deletedNote = await database.vocabsDao.getNote(1000220); expect(deletedNote, null); + + await database.vocabsDao.deleteAllNotes(); + final remainingNotes = await database.vocabsDao.getAllNotes(); + expect(remainingNotes.length, 0); }); }); }