diff --git a/lib/models/kanjidic/kanji_search_radical.dart b/lib/models/kanjidic/kanji_search_radical.dart new file mode 100644 index 0000000..56f8a7e --- /dev/null +++ b/lib/models/kanjidic/kanji_search_radical.dart @@ -0,0 +1,40 @@ +import 'package:equatable/equatable.dart'; + +class KanjiSearchRadical extends Equatable { + /// The radical symbol. + final String symbol; + + /// The radical forms used in this kanji. + final List forms; + + /// The meaning of the radical. + final String meaning; + + // ignore: public_member_api_docs + const KanjiSearchRadical({ + required this.symbol, + this.forms = const [], + required this.meaning, + }); + + @override + List get props => [ + symbol, + forms, + meaning, + ]; + + Map toJson() => { + 'symbol': symbol, + 'forms': forms, + 'meaning': meaning, + }; + + factory KanjiSearchRadical.fromJson(Map json) { + return KanjiSearchRadical( + symbol: json['symbol'] as String, + forms: (json['forms'] as List).map((e) => e as String).toList(), + meaning: json['meaning'] as String, + ); + } +} diff --git a/lib/models/kanjidic/kanji_search_result.dart b/lib/models/kanjidic/kanji_search_result.dart index 6119c54..7258084 100644 --- a/lib/models/kanjidic/kanji_search_result.dart +++ b/lib/models/kanjidic/kanji_search_result.dart @@ -1,3 +1,117 @@ -class KanjiSearchResult { - // TODO: implement me +import 'package:equatable/equatable.dart'; + +import 'kanji_search_radical.dart'; + +class KanjiSearchResult extends Equatable { + /// The kanji symbol + final String kanji; + + /// The school level that the kanji is taught in, if applicable. + final String? taughtIn; + + /// The lowest JLPT exam that this kanji is likely to appear in, if applicable. + /// + /// 'N5' or 'N4' or 'N3' or 'N2' or 'N1'. + final String? jlptLevel; + + /// A number representing this kanji's frequency rank in newspapers, if applicable. + final int? newspaperFrequencyRank; + + /// How many strokes this kanji is typically drawn in. + final int strokeCount; + + /// The meanings of the kanji. + final List meanings; + + /// This character's kunyomi. + final List kunyomi; + + /// This character's onyomi. + final List onyomi; + + // /// Examples of this character's kunyomi being used. + // final List kunyomiExamples; + + // /// Examples of this character's onyomi being used. + // final List onyomiExamples; + + /// Information about this character's radical, if applicable. + final KanjiSearchRadical? radical; + + // TODO: document more accurately what kind of parts? + /// The parts used in this kanji. + final List parts; + + const KanjiSearchResult({ + required this.kanji, + this.taughtIn, + this.jlptLevel, + this.newspaperFrequencyRank, + required this.strokeCount, + required this.meanings, + this.kunyomi = const [], + this.onyomi = const [], + // this.kunyomiExamples = const [], + // this.onyomiExamples = const [], + this.radical, + this.parts = const [], + }); + + @override + // ignore: public_member_api_docs + List get props => [ + taughtIn, + jlptLevel, + newspaperFrequencyRank, + strokeCount, + meanings, + kunyomi, + onyomi, + // kunyomiExamples, + // onyomiExamples, + radical, + parts, + ]; + + Map toJson() => { + 'kanji': kanji, + 'taughtIn': taughtIn, + 'jlptLevel': jlptLevel, + 'newspaperFrequencyRank': newspaperFrequencyRank, + 'strokeCount': strokeCount, + 'meanings': meanings, + 'kunyomi': kunyomi, + 'onyomi': onyomi, + // 'onyomiExamples': onyomiExamples, + // 'kunyomiExamples': kunyomiExamples, + 'radical': radical?.toJson(), + 'parts': parts, + // 'strokeOrderDiagramUri': strokeOrderDiagramUri, + // 'strokeOrderSvgUri': strokeOrderSvgUri, + // 'strokeOrderGifUri': strokeOrderGifUri, + // 'uri': uri, + }; + + factory KanjiSearchResult.fromJson(Map json) { + return KanjiSearchResult( + kanji: json['kanji'] as String, + taughtIn: json['taughtIn'] as String?, + jlptLevel: json['jlptLevel'] as String?, + newspaperFrequencyRank: json['newspaperFrequencyRank'] as int?, + strokeCount: json['strokeCount'] as int, + meanings: (json['meanings'] as List).map((e) => e as String).toList(), + kunyomi: (json['kunyomi'] as List).map((e) => e as String).toList(), + onyomi: (json['onyomi'] as List).map((e) => e as String).toList(), + // kunyomiExamples: (json['kunyomiExamples'] as List) + // .map((e) => YomiExample.fromJson(e)) + // .toList(), + // onyomiExamples: (json['onyomiExamples'] as List) + // .map((e) => YomiExample.fromJson(e)) + // .toList(), + radical: json['radical'] != null + ? KanjiSearchRadical.fromJson(json['radical']) + : null, + parts: (json['parts'] as List).map((e) => e as String).toList(), + ); + } } diff --git a/lib/search.dart b/lib/search.dart index 454c7f0..2fe5713 100644 --- a/lib/search.dart +++ b/lib/search.dart @@ -2,14 +2,23 @@ import 'package:jadb/models/jmdict/word_search_result.dart'; import 'package:jadb/models/kanjidic/kanji_search_result.dart'; import 'package:jadb/models/radkfile/radicals_search_result.dart'; -Future searchKanji(String kanji) async { - throw UnimplementedError(); -} +import 'package:jadb/search/kanji.dart'; -Future searchKanjiByRadicals(List radicals) { - throw UnimplementedError(); -} +import 'package:sqflite_common/sqlite_api.dart'; -Future searchWord(String word) async { - throw UnimplementedError(); +class JaDBConnection { + final DatabaseExecutor _connection; + + const JaDBConnection(this._connection); + + Future searchKanji(String kanji) async => + searchKanjiWithDbConnection(this._connection, kanji); + + Future searchKanjiByRadicals(List radicals) { + throw UnimplementedError(); + } + + Future searchWord(String word) async { + throw UnimplementedError(); + } } diff --git a/lib/search/kanji.dart b/lib/search/kanji.dart new file mode 100644 index 0000000..7466b69 --- /dev/null +++ b/lib/search/kanji.dart @@ -0,0 +1,158 @@ +import 'package:jadb/models/kanjidic/kanji_search_result.dart'; +import 'package:sqflite_common/sqflite.dart'; + +Future searchKanjiWithDbConnection( + DatabaseExecutor connection, + String kanji, +) async { + late final List> characters; + final characters_query = connection.query( + "KANJIDIC_Character", + where: "KANJIDIC_Character.literal = ?", + whereArgs: [kanji], + ); + + late final List> codepoints; + final codepoints_query = connection.query( + "KANJIDIC_Codepoint", + where: "KANJIDIC_Codepoint.kanji = ?", + whereArgs: [kanji], + ); + + late final List> kunyomis; + final kunyomis_query = connection.query( + "KANJIDIC_Kunyomi", + where: "KANJIDIC_Kunyomi.kanji = ?", + whereArgs: [kanji], + ); + + late final List> onyomis; + final onyomis_query = connection.query( + "KANJIDIC_Onyomi", + where: "KANJIDIC_Onyomi.kanji = ?", + whereArgs: [kanji], + ); + + late final List> meanings; + final meanings_query = connection.query( + "KANJIDIC_Meaning", + where: "KANJIDIC_Meaning.kanji = ? AND KANJIDIC_Meaning.language = ?", + whereArgs: [kanji, 'eng'], + ); + + late final List> nanoris; + final nanoris_query = connection.query( + "KANJIDIC_Nanori", + where: "KANJIDIC_Nanori.kanji = ?", + whereArgs: [kanji], + ); + + late final List> dictionary_references; + final dictionary_references_query = connection.query( + "KANJIDIC_DictionaryReference", + where: "KANJIDIC_DictionaryReference.kanji = ?", + whereArgs: [kanji], + ); + + late final List> query_codes; + final query_codes_query = connection.query( + "KANJIDIC_QueryCode", + where: "KANJIDIC_QueryCode.kanji = ?", + whereArgs: [kanji], + ); + + late final List> radicals; + final radicals_query = connection.query( + "KANJIDIC_Radical", + where: "KANJIDIC_Radical.kanji = ?", + whereArgs: [kanji], + ); + + late final List> radical_names; + final radical_names_query = connection.query( + "KANJIDIC_RadicalName", + where: "KANJIDIC_RadicalName.kanji = ?", + whereArgs: [kanji], + ); + + late final List> readings; + final readings_query = connection.query( + "KANJIDIC_Reading", + where: "KANJIDIC_Reading.kanji = ?", + whereArgs: [kanji], + ); + + late final List> stroke_miscounts; + final stroke_miscounts_query = connection.query( + "KANJIDIC_StrokeMiscount", + where: "KANJIDIC_StrokeMiscount.kanji = ?", + whereArgs: [kanji], + ); + + late final List> variants; + final variants_query = connection.query( + "KANJIDIC_Variant", + where: "KANJIDIC_Variant.kanji = ?", + whereArgs: [kanji], + ); + +// TODO: Search for kunyomi and onyomi usage of the characters +// from JMDict. We'll need to fuzzy aquery JMDict_KanjiElement for mathces, +// filter JMdict_ReadingElement for kunyomi/onyomi, and then sort the main entry +// by JLPT, news frequency, etc. + + await characters_query.then((value) => characters = value); + + if (characters.isEmpty) { + return null; + } + + await Future.wait({ + codepoints_query.then((value) => codepoints = value), + kunyomis_query.then((value) => kunyomis = value), + onyomis_query.then((value) => onyomis = value), + meanings_query.then((value) => meanings = value), + nanoris_query.then((value) => nanoris = value), + dictionary_references_query.then((value) => dictionary_references = value), + query_codes_query.then((value) => query_codes = value), + radicals_query.then((value) => radicals = value), + radical_names_query.then((value) => radical_names = value), + readings_query.then((value) => readings = value), + stroke_miscounts_query.then((value) => stroke_miscounts = value), + variants_query.then((value) => variants = value), + }); + + final entry = characters.first; + + final String? grade = { + 1: 'grade 1', + 2: 'grade 2', + 3: 'grade 3', + 4: 'grade 4', + 5: 'grade 5', + 6: 'grade 6', + 7: 'grade 7', + 8: 'grade 8', + 9: 'grade 9', + 10: 'grade 10', + }[entry['grade'] as int?]; + + final String? jlptLevel = { + 5: 'N5', + 4: 'N4', + 3: 'N3', + 2: 'N2', + 1: 'N1', + }[entry['jlpt'] as int?]; + + return KanjiSearchResult( + kanji: entry['literal']! as String, + taughtIn: grade, + jlptLevel: jlptLevel, + newspaperFrequencyRank: entry['frequency'] as int?, + strokeCount: entry['strokeCount'] as int, + meanings: meanings.map((item) => item['meaning'] as String).toList(), + kunyomi: kunyomis.map((item) => item['yomi'] as String).toList(), + onyomi: onyomis.map((item) => item['yomi'] as String).toList(), + ); +} diff --git a/pubspec.lock b/pubspec.lock index 4430eb7..538abf5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + url: "https://pub.dev" + source: hosted + version: "2.0.7" ffi: dependency: transitive description: @@ -58,7 +66,7 @@ packages: source: hosted version: "6.1.0" sqflite_common: - dependency: transitive + dependency: "direct main" description: name: sqflite_common sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" diff --git a/pubspec.yaml b/pubspec.yaml index 20e99ec..c031263 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,6 +9,8 @@ environment: dependencies: args: ^2.7.0 collection: ^1.19.1 + equatable: ^2.0.7 + sqflite_common: ^2.5.5 sqflite_common_ffi: ^2.3.5 xml: ^6.5.0