diff --git a/analysis_options.yaml b/analysis_options.yaml index aff5bcf2..4b73cf90 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,26 +1,6 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml +include: package:lint/strict.yaml linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at - # https://dart-lang.github.io/linter/lints/index.html. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. rules: - prefer_double_quotes - avoid_final_parameters @@ -31,11 +11,14 @@ linter: - prefer_const_constructors - prefer_const_constructors_in_immutables - prefer_const_declarations + - unawaited_futures analyzer: language: strict-casts: true strict-raw-types: true - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options + errors: + todo: info + exclude: [ + lib/**.g.dart, + ] diff --git a/lib/main.dart b/lib/main.dart index 3f113087..dd208f6e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,8 +15,10 @@ void main() async { } void registerKanjivgLicense() => LicenseRegistry.addLicense(() async* { - yield LicenseEntryWithLineBreaks(["KanjiVg"], - await rootBundle.loadString("assets/kanjivg/LICENSE.kanjivg.txt")); + yield LicenseEntryWithLineBreaks( + ["KanjiVg"], + await rootBundle.loadString("assets/kanjivg/LICENSE.kanjivg.txt"), + ); }); const mainColor = Color(0xFF27CA27); @@ -26,8 +28,9 @@ class JsDictApp extends StatelessWidget { @override Widget build(BuildContext context) { - return DynamicColorBuilder(builder: (lightDynamic, darkDynamic) { - return MultiProvider( + return DynamicColorBuilder( + builder: (lightDynamic, darkDynamic) { + return MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => QueryProvider()), ChangeNotifierProvider(create: (_) => ThemeProvider()), @@ -57,8 +60,10 @@ class JsDictApp extends StatelessWidget { ), home: const SearchScreen(), ); - }); - }); + }, + ); + }, + ); } // Adds a "Debug" suffix if the app is running in debug mode. diff --git a/lib/models/kanji.dart b/lib/models/kanji.dart index 8b725025..4e3e59ff 100644 --- a/lib/models/kanji.dart +++ b/lib/models/kanji.dart @@ -20,15 +20,16 @@ class Kanji implements SearchType { static String createUrl(String kanji) => "https://jisho.org/search/${Uri.encodeComponent("$kanji #kanji")}"; - const Kanji( - {required this.kanji, - required this.strokeCount, - this.type, - this.jlptLevel = JLPTLevel.none, - this.meanings = const [], - this.kunReadings = const [], - this.onReadings = const [], - this.details}); + const Kanji({ + required this.kanji, + required this.strokeCount, + this.type, + this.jlptLevel = JLPTLevel.none, + this.meanings = const [], + this.kunReadings = const [], + this.onReadings = const [], + this.details, + }); } class KanjiDetails { @@ -42,13 +43,14 @@ class KanjiDetails { final List onCompounds; final List kunCompounds; - const KanjiDetails( - {required this.parts, - required this.variants, - required this.radical, - required this.frequency, - required this.onCompounds, - required this.kunCompounds}); + const KanjiDetails({ + required this.parts, + required this.variants, + required this.radical, + required this.frequency, + required this.onCompounds, + required this.kunCompounds, + }); } class Radical { diff --git a/lib/models/models.dart b/lib/models/models.dart index ab45478b..54ffe9c1 100644 --- a/lib/models/models.dart +++ b/lib/models/models.dart @@ -1,6 +1,5 @@ import "package:jsdict/packages/inflection.dart"; import "package:jsdict/packages/is_kanji.dart"; -import "package:jsdict/packages/list_extensions.dart"; part "furigana.dart"; part "jlpt.dart"; diff --git a/lib/models/name.dart b/lib/models/name.dart index 58dee228..c950077d 100644 --- a/lib/models/name.dart +++ b/lib/models/name.dart @@ -15,5 +15,10 @@ class Name implements SearchType { String toString() => reading != null ? "$japanese【$reading】" : japanese; const Name( - this.japanese, this.reading, this.english, this.type, this.wikipediaWord); + this.japanese, + this.reading, + this.english, + this.type, + this.wikipediaWord, + ); } diff --git a/lib/models/search.dart b/lib/models/search.dart index 64fec3a1..1e072e2a 100644 --- a/lib/models/search.dart +++ b/lib/models/search.dart @@ -13,21 +13,23 @@ class SearchResponse { final GrammarInfo? grammarInfo; final Conversion? conversion; - const SearchResponse( - {required this.results, - this.hasNextPage = false, - this.zenEntries = const [], - this.correction, - this.noMatchesFor = const [], - this.grammarInfo, - this.conversion}); - - const SearchResponse.noMatches(this.noMatchesFor, - {this.zenEntries = const [], - this.correction, - this.grammarInfo, - this.conversion}) - : results = const [], + const SearchResponse({ + required this.results, + this.hasNextPage = false, + this.zenEntries = const [], + this.correction, + this.noMatchesFor = const [], + this.grammarInfo, + this.conversion, + }); + + const SearchResponse.noMatches( + this.noMatchesFor, { + this.zenEntries = const [], + this.correction, + this.grammarInfo, + this.conversion, + }) : results = const [], hasNextPage = false; } diff --git a/lib/models/sentence.dart b/lib/models/sentence.dart index 53cf7500..3ec27409 100644 --- a/lib/models/sentence.dart +++ b/lib/models/sentence.dart @@ -24,7 +24,12 @@ class Sentence implements SearchType { kanji = null; const Sentence.all( - this.id, this.japanese, this.english, this.copyright, this.kanji); + this.id, + this.japanese, + this.english, + this.copyright, + this.kanji, + ); Sentence withKanji(List kanji) => Sentence.all(id, japanese, english, copyright, kanji); diff --git a/lib/models/word.dart b/lib/models/word.dart index 0e826e94..74d19952 100644 --- a/lib/models/word.dart +++ b/lib/models/word.dart @@ -23,35 +23,37 @@ class Word implements SearchType { final WordDetails? details; - const Word( - {required this.word, - required this.definitions, - this.otherForms = const [], - this.commonWord = false, - this.wanikaniLevels = const [], - this.jlptLevel = JLPTLevel.none, - this.audioUrl, - this.notes = const [], - this.collocations = const [], - this.id, - this.inflectionId = "", - this.hasWikipedia = false, - this.details}); + const Word({ + required this.word, + required this.definitions, + this.otherForms = const [], + this.commonWord = false, + this.wanikaniLevels = const [], + this.jlptLevel = JLPTLevel.none, + this.audioUrl, + this.notes = const [], + this.collocations = const [], + this.id, + this.inflectionId = "", + this.hasWikipedia = false, + this.details, + }); Word withDetails(WordDetails details) => Word( - details: details, - word: word, - definitions: definitions, - otherForms: otherForms, - commonWord: commonWord, - wanikaniLevels: wanikaniLevels, - jlptLevel: jlptLevel, - audioUrl: audioUrl, - notes: notes, - collocations: collocations, - id: id, - inflectionId: inflectionId, - hasWikipedia: hasWikipedia); + details: details, + word: word, + definitions: definitions, + otherForms: otherForms, + commonWord: commonWord, + wanikaniLevels: wanikaniLevels, + jlptLevel: jlptLevel, + audioUrl: audioUrl, + notes: notes, + collocations: collocations, + id: id, + inflectionId: inflectionId, + hasWikipedia: hasWikipedia, + ); InflectionData? get inflectionData => inflectionId.isNotEmpty ? InflectionData(word.text, inflectionId) : null; @@ -78,12 +80,13 @@ class Definition { final Sentence? exampleSentence; - const Definition( - {required this.meanings, - this.types = const [], - this.tags = const [], - this.seeAlso = const [], - this.exampleSentence}); + const Definition({ + required this.meanings, + this.types = const [], + this.tags = const [], + this.seeAlso = const [], + this.exampleSentence, + }); @override String toString() { @@ -98,11 +101,13 @@ class WikipediaInfo { final WikipediaPage? wikipediaJapanese; final WikipediaPage? dbpedia; - const WikipediaInfo(this.title, - {this.textAbstract, - this.wikipediaEnglish, - this.wikipediaJapanese, - this.dbpedia}); + const WikipediaInfo( + this.title, { + this.textAbstract, + this.wikipediaEnglish, + this.wikipediaJapanese, + this.dbpedia, + }); } class WikipediaPage { @@ -140,21 +145,11 @@ class Note { const Note(this.form, this.note); - @override - String toString() => "$form: $note"; - - static Note parse(String text) { + factory Note.parse(String text) { final split = text.split(": "); return Note(split.first, split.last); } - static List parseAll(String text) { - return text - .trim() - .replaceFirst(RegExp(r"\.$"), "") - .split(". ") - .deduplicate() - .map(Note.parse) - .toList(); - } + @override + String toString() => "$form: $note"; } diff --git a/lib/packages/furigana_ruby.dart b/lib/packages/furigana_ruby.dart index 2e559350..a9397de6 100644 --- a/lib/packages/furigana_ruby.dart +++ b/lib/packages/furigana_ruby.dart @@ -2,6 +2,10 @@ import "package:jsdict/models/models.dart"; import "package:ruby_text/ruby_text.dart"; extension FuriganaMethods on Furigana { - List get rubyData => map((part) => RubyTextData(part.text, - ruby: part.furigana.isNotEmpty ? part.furigana : null)).toList(); + List get rubyData => map( + (part) => RubyTextData( + part.text, + ruby: part.furigana.isNotEmpty ? part.furigana : null, + ), + ).toList(); } diff --git a/lib/packages/inflection.dart b/lib/packages/inflection.dart index 1f19eddc..4ec23ed4 100644 --- a/lib/packages/inflection.dart +++ b/lib/packages/inflection.dart @@ -6,11 +6,11 @@ typedef _StringInflectionEntry = ({String affermative, String negative}); sealed class InflectionData { final String name; - const InflectionData._(this.name); - factory InflectionData(String word, String code) => _createInflectionData(word, code); + const InflectionData._(this.name); + Map toMap(); } @@ -107,18 +107,19 @@ VerbData _verb(String word, String code) => switch (code) { }; IAdjectiveData _iAdjective(String word) => _withStem( - word, - "い", - (stem) => IAdjectiveData._( - nonPast: ( - affermative: word, - negative: "$stemくない", - ), - past: ( - affermative: "$stemかった", - negative: "$stemくなかった", - ), - )); + word, + "い", + (stem) => IAdjectiveData._( + nonPast: ( + affermative: word, + negative: "$stemくない", + ), + past: ( + affermative: "$stemかった", + negative: "$stemくなかった", + ), + ), + ); VerbData _ichidan(String word) => _withStem( word, @@ -161,8 +162,14 @@ enum _GodanData { final String takei; final String ending; - const _GodanData(this.base, this.renyokei, this.mizenkei, this.meireikei, - this.takei, this.ending); + const _GodanData( + this.base, + this.renyokei, + this.mizenkei, + this.meireikei, + this.takei, + this.ending, + ); factory _GodanData.fromCode(String code) => switch (code) { "u" => u, @@ -231,107 +238,113 @@ VerbData _godan(String word, _GodanData data) => _withStem( ), ); -typedef _FuriganaSuffixer = Furigana Function(String furigana, String suffix); - T _withFuriganaSuffixer( - String stem, T Function(_FuriganaSuffixer suffix) f) => - f((furigana, suffix) => - [FuriganaPart(stem, furigana), FuriganaPart.textOnly(suffix)]); + String stem, + T Function(Furigana Function(String, String) suffix) f, +) => + f( + (furigana, suffix) => + [FuriganaPart(stem, furigana), FuriganaPart.textOnly(suffix)], + ); final _kuru = _withFuriganaSuffixer( - "来", - (suffix) => VerbData._( - name: "Kuru verb - special class", - nonPast: ( - affermative: suffix("く", "る"), - negative: suffix("こ", "ない"), - ), - past: ( - affermative: suffix("き", "た"), - negative: suffix("こ", "なかった"), - ), - nonPastPolite: ( - affermative: suffix("き", "ます"), - negative: suffix("き", "ません"), - ), - pastPolite: ( - affermative: suffix("き", "ました"), - negative: suffix("き", "ませんでした"), - ), - teForm: ( - affermative: suffix("き", "て"), - negative: suffix("こ", "なくて"), - ), - potential: ( - affermative: suffix("こ", "られる"), - negative: suffix("こ", "られない"), - ), - passive: ( - affermative: suffix("こ", "られる"), - negative: suffix("こ", "られない"), - ), - causative: ( - affermative: suffix("こ", "させる"), - negative: suffix("こ", "させない"), - ), - causativePassive: ( - affermative: suffix("こ", "させられる"), - negative: suffix("こ", "させられない"), - ), - imperative: ( - affermative: suffix("こ", "い"), - negative: suffix("く", "るな"), - ), - )); + "来", + (suffix) => VerbData._( + name: "Kuru verb - special class", + nonPast: ( + affermative: suffix("く", "る"), + negative: suffix("こ", "ない"), + ), + past: ( + affermative: suffix("き", "た"), + negative: suffix("こ", "なかった"), + ), + nonPastPolite: ( + affermative: suffix("き", "ます"), + negative: suffix("き", "ません"), + ), + pastPolite: ( + affermative: suffix("き", "ました"), + negative: suffix("き", "ませんでした"), + ), + teForm: ( + affermative: suffix("き", "て"), + negative: suffix("こ", "なくて"), + ), + potential: ( + affermative: suffix("こ", "られる"), + negative: suffix("こ", "られない"), + ), + passive: ( + affermative: suffix("こ", "られる"), + negative: suffix("こ", "られない"), + ), + causative: ( + affermative: suffix("こ", "させる"), + negative: suffix("こ", "させない"), + ), + causativePassive: ( + affermative: suffix("こ", "させられる"), + negative: suffix("こ", "させられない"), + ), + imperative: ( + affermative: suffix("こ", "い"), + negative: suffix("く", "るな"), + ), + ), +); final _suruSpecial = _withFuriganaSuffixer( - "為", - (suffix) => VerbData._( - name: "Suru verb - included", - nonPast: ( - affermative: suffix("す", "る"), - negative: suffix("し", "ない"), - ), - past: ( - affermative: suffix("し", "た"), - negative: suffix("し", "なかった"), - ), - nonPastPolite: ( - affermative: suffix("し", "ます"), - negative: suffix("し", "ません"), - ), - pastPolite: ( - affermative: suffix("し", "ました"), - negative: suffix("し", "ませんでした"), - ), - teForm: ( - affermative: suffix("し", "て"), - negative: suffix("し", "なくて"), - ), - potential: ( - affermative: "できる".furigana, - negative: "できない".furigana, - ), - passive: ( - affermative: suffix("さ", "れる"), - negative: suffix("さ", "れない"), - ), - causative: ( - affermative: suffix("さ", "せる"), - negative: suffix("さ", "せない"), - ), - causativePassive: ( - affermative: suffix("さ", "せられる"), - negative: suffix("さ", "せられない"), - ), - imperative: ( - affermative: suffix("し", "ろ"), - negative: suffix("す", "るな"), - ), - )); + "為", + (suffix) => VerbData._( + name: "Suru verb - included", + nonPast: ( + affermative: suffix("す", "る"), + negative: suffix("し", "ない"), + ), + past: ( + affermative: suffix("し", "た"), + negative: suffix("し", "なかった"), + ), + nonPastPolite: ( + affermative: suffix("し", "ます"), + negative: suffix("し", "ません"), + ), + pastPolite: ( + affermative: suffix("し", "ました"), + negative: suffix("し", "ませんでした"), + ), + teForm: ( + affermative: suffix("し", "て"), + negative: suffix("し", "なくて"), + ), + potential: ( + affermative: "できる".furigana, + negative: "できない".furigana, + ), + passive: ( + affermative: suffix("さ", "れる"), + negative: suffix("さ", "れない"), + ), + causative: ( + affermative: suffix("さ", "せる"), + negative: suffix("さ", "せない"), + ), + causativePassive: ( + affermative: suffix("さ", "せられる"), + negative: suffix("さ", "せられない"), + ), + imperative: ( + affermative: suffix("し", "ろ"), + negative: suffix("す", "るな"), + ), + ), +); _StringInflectionEntry _entryReadingWithStem( - String stem, InflectionEntry entry) => + String stem, + InflectionEntry entry, +) => ( affermative: stem + entry.affermative.reading, negative: stem + entry.negative.reading diff --git a/lib/packages/is_kanji.dart b/lib/packages/is_kanji.dart index 4e449f9a..82b1d061 100644 --- a/lib/packages/is_kanji.dart +++ b/lib/packages/is_kanji.dart @@ -5,14 +5,17 @@ const _cjkUnifiedIdeographsEnd = 0x9FFF; /// checks whether [text] only contains kanji characters. bool isKanji(String text) => - text.trim().codeUnits.firstWhereOrNull((unit) => - !(_cjkUnifiedIdeographsStart <= unit && - unit <= _cjkUnifiedIdeographsEnd)) == + text.trim().codeUnits.firstWhereOrNull( + (unit) => !(_cjkUnifiedIdeographsStart <= unit && + unit <= _cjkUnifiedIdeographsEnd), + ) == null; /// checks whether [text] doesn't contain any kanji characters. bool isNonKanji(String text) => - text.trim().codeUnits.firstWhereOrNull((unit) => - _cjkUnifiedIdeographsStart <= unit && - unit <= _cjkUnifiedIdeographsEnd) == + text.trim().codeUnits.firstWhereOrNull( + (unit) => + _cjkUnifiedIdeographsStart <= unit && + unit <= _cjkUnifiedIdeographsEnd, + ) == null; diff --git a/lib/packages/jisho_client/jisho_client.dart b/lib/packages/jisho_client/jisho_client.dart index a94249f3..6d07d0d5 100644 --- a/lib/packages/jisho_client/jisho_client.dart +++ b/lib/packages/jisho_client/jisho_client.dart @@ -1,14 +1,12 @@ -library jisho_dart; - import "dart:io"; import "package:html/dom.dart"; -import "package:http/http.dart" as http; import "package:html/parser.dart"; +import "package:http/http.dart" as http; import "package:jsdict/models/models.dart"; -import "exceptions.dart"; -import "parser/parser.dart"; +import "package:jsdict/packages/jisho_client/exceptions.dart"; +import "package:jsdict/packages/jisho_client/parser/parser.dart"; export "exceptions.dart"; @@ -30,7 +28,8 @@ class JishoClient { } throw HttpException( - "Unsuccessful response status: ${response.statusCode}"); + "Unsuccessful response status: ${response.statusCode}", + ); } return parse(response.body); @@ -52,15 +51,19 @@ class JishoClient { Kanji: "kanji", Word: "words", Sentence: "sentences", - Name: "names" + Name: "names", }; // returns query with everything lowercased except for tags String _lowercaseQuery(String query) => query.replaceAllMapped( - RegExp(r"(?<=^|\s)\w+"), (match) => match.group(0)!.toLowerCase()); - - Future> search(String query, - {int page = 1}) => + RegExp(r"(?<=^|\s)\w+"), + (match) => match.group(0)!.toLowerCase(), + ); + + Future> search( + String query, { + int page = 1, + }) => _getHtml(_searchPath(query, page: page)).then(parseSearch); Future kanjiDetails(String kanji) => diff --git a/lib/packages/jisho_client/parser/furigana.dart b/lib/packages/jisho_client/parser/furigana.dart index 7ff76ecf..20883aae 100644 --- a/lib/packages/jisho_client/parser/furigana.dart +++ b/lib/packages/jisho_client/parser/furigana.dart @@ -8,7 +8,7 @@ Furigana _parseSentenceFurigana(Element element) { if (node.nodeType == Node.TEXT_NODE) { return FuriganaPart.textOnly(node.text!.trim()); } else { - final element = (node as Element); + final element = node as Element; final text = element.querySelector("span.unlinked")!.firstChild!.text!.trim(); final furiganaElement = element.querySelector("span.furigana"); @@ -22,7 +22,7 @@ Furigana _parseSentenceFurigana(Element element) { } List _limitTextPartsSize(List list, int size) => - list.sublist(0, size - 1) + [list.sublist(size - 1).join("")]; + list.sublist(0, size - 1) + [list.sublist(size - 1).join()]; bool _hasEmpty(List list) => list.firstWhereOrNull((part) => part.isEmpty) != null; @@ -34,7 +34,8 @@ Furigana _parseWordFurigana(Element element) { final furiganaParts = element .querySelectorAll( - "div.concept_light-representation > span.furigana > span") + "div.concept_light-representation > span.furigana > span", + ) .allTrimmedText; if (furiganaParts.length == 1) { @@ -62,11 +63,11 @@ Furigana _parseWordFurigana(Element element) { assert(furiganaParts.length == textParts.length); - final text = textParts.join(""); + final text = textParts.join(); // kanji compounds that don't specify ruby locations if (isKanji(text) && _hasEmpty(furiganaParts)) { - return [FuriganaPart(text, furiganaParts.join(""))]; + return [FuriganaPart(text, furiganaParts.join())]; } return textParts diff --git a/lib/packages/jisho_client/parser/kanji.dart b/lib/packages/jisho_client/parser/kanji.dart index 07211376..02ed5a93 100644 --- a/lib/packages/jisho_client/parser/kanji.dart +++ b/lib/packages/jisho_client/parser/kanji.dart @@ -43,13 +43,14 @@ Kanji _parseKanjiEntry(Element element) { .allTrimmedText; return Kanji( - kanji: kanji, - strokeCount: strokeCount, - type: type, - jlptLevel: jlptLevel, - meanings: meanings, - kunReadings: kunReadings, - onReadings: onReadings); + kanji: kanji, + strokeCount: strokeCount, + type: type, + jlptLevel: jlptLevel, + meanings: meanings, + kunReadings: kunReadings, + onReadings: onReadings, + ); } Kanji _parseKanjiDetailsEntry(Element element) { @@ -75,7 +76,8 @@ Kanji _parseKanjiDetailsEntry(Element element) { final kunReadings = element .querySelectorAll( - "div.kanji-details__main-readings > dl.kun_yomi > dd > a") + "div.kanji-details__main-readings > dl.kun_yomi > dd > a", + ) .allTrimmedText; final onReadings = element @@ -85,14 +87,15 @@ Kanji _parseKanjiDetailsEntry(Element element) { final details = _parseKanjiDetails(element); return Kanji( - kanji: kanji, - strokeCount: strokeCount, - type: type, - jlptLevel: jlptLevel, - meanings: meanings, - kunReadings: kunReadings, - onReadings: onReadings, - details: details); + kanji: kanji, + strokeCount: strokeCount, + type: type, + jlptLevel: jlptLevel, + meanings: meanings, + kunReadings: kunReadings, + onReadings: onReadings, + details: details, + ); } KanjiDetails _parseKanjiDetails(Element element) { @@ -115,12 +118,13 @@ KanjiDetails _parseKanjiDetails(Element element) { final kunCompounds = _findCompounds(element, "Kun"); return KanjiDetails( - parts: parts, - variants: variants, - radical: radical, - frequency: frequency, - onCompounds: onCompounds, - kunCompounds: kunCompounds); + parts: parts, + variants: variants, + radical: radical, + frequency: frequency, + onCompounds: onCompounds, + kunCompounds: kunCompounds, + ); } Radical _parseRadical(Element e) { @@ -155,8 +159,10 @@ KanjiType? _getKanjiType(Element element) { List _findCompounds(Element element, String type) => element .querySelectorAll("div.row.compounds > div.columns") - .firstWhereOrNull((e) => - e.querySelector("h2")!.text.contains("$type reading compounds")) + .firstWhereOrNull( + (e) => + e.querySelector("h2")!.text.contains("$type reading compounds"), + ) ?.transform(_parseCompounds) ?? []; diff --git a/lib/packages/jisho_client/parser/name.dart b/lib/packages/jisho_client/parser/name.dart index 034eb7b2..13657f30 100644 --- a/lib/packages/jisho_client/parser/name.dart +++ b/lib/packages/jisho_client/parser/name.dart @@ -4,7 +4,7 @@ Name _parseNameEntry(Element element) { final japaneseText = element .querySelector("div.concept_light-readings")! .trimmedText - .transform((e) => e.replaceAll("\n", "").replaceAll(RegExp(r" +"), " ")); + .transform((e) => e.replaceAll("\n", "").replaceAll(RegExp(" +"), " ")); final japanese = japaneseText.split("【").last.replaceFirst(RegExp(r"】$"), ""); diff --git a/lib/packages/jisho_client/parser/search.dart b/lib/packages/jisho_client/parser/search.dart index 38e99f57..dbbcc7d9 100644 --- a/lib/packages/jisho_client/parser/search.dart +++ b/lib/packages/jisho_client/parser/search.dart @@ -17,8 +17,11 @@ SearchResponse parseSearch(Document document) { []; if (noMatchesFor.isNotEmpty) { - return SearchResponse.noMatches(noMatchesFor, - conversion: conversion, zenEntries: limitedZenEntries); + return SearchResponse.noMatches( + noMatchesFor, + conversion: conversion, + zenEntries: limitedZenEntries, + ); } final correction = @@ -61,13 +64,14 @@ SearchResponse parseSearch(Document document) { } as List; return SearchResponse( - results: results, - hasNextPage: hasNextPage, - zenEntries: limitedZenEntries, - correction: correction, - noMatchesFor: noMatchesFor, - grammarInfo: grammarInfo, - conversion: conversion); + results: results, + hasNextPage: hasNextPage, + zenEntries: limitedZenEntries, + correction: correction, + noMatchesFor: noMatchesFor, + grammarInfo: grammarInfo, + conversion: conversion, + ); } Conversion _parseConversion(Element e) => e.trimmedText diff --git a/lib/packages/jisho_client/parser/sentence.dart b/lib/packages/jisho_client/parser/sentence.dart index 18b1a0a0..c279c455 100644 --- a/lib/packages/jisho_client/parser/sentence.dart +++ b/lib/packages/jisho_client/parser/sentence.dart @@ -23,7 +23,8 @@ Sentence _parseSentenceEntry(Element element) { final japanese = _parseSentenceFurigana(element); final copyright = element.querySelector("span.inline_copyright a")?.transform( - (e) => SentenceCopyright(e.trimmedText, e.attributes["href"]!)); + (e) => SentenceCopyright(e.trimmedText, e.attributes["href"]!), + ); final id = element .querySelector("a.light-details_link") diff --git a/lib/packages/jisho_client/parser/wikipedia.dart b/lib/packages/jisho_client/parser/wikipedia.dart index afa5f351..48475540 100644 --- a/lib/packages/jisho_client/parser/wikipedia.dart +++ b/lib/packages/jisho_client/parser/wikipedia.dart @@ -25,8 +25,10 @@ WikipediaPage? _parseWikipediaPage(Element definitionElement, String name) => .querySelectorAll("span.meaning-abstract > a") .firstWhereOrNull((e) => e.text.contains(name)) ?.transform( - (e) => WikipediaPage(RegExp("“(.+?)”").firstMatch(e.text)!.group(1)!, - e.attributes["href"]!), + (e) => WikipediaPage( + RegExp("“(.+?)”").firstMatch(e.text)!.group(1)!, + e.attributes["href"]!, + ), ); final _abstractFixPattern = RegExp(r"^(?:is |was |, |\()"); diff --git a/lib/packages/jisho_client/parser/word.dart b/lib/packages/jisho_client/parser/word.dart index 17fe84f6..4bb49103 100644 --- a/lib/packages/jisho_client/parser/word.dart +++ b/lib/packages/jisho_client/parser/word.dart @@ -18,10 +18,12 @@ Word parseWordDetails(Document document) { .firstWhereOrNull((e) => e.text.contains("Wikipedia definition")) ?.transform((w) => _parseWikipediaInfo(w.nextElementSibling!)); - return word.withDetails(WordDetails( - kanji: kanji, - wikipedia: wikipedia, - )); + return word.withDetails( + WordDetails( + kanji: kanji, + wikipedia: wikipedia, + ), + ); } Word _parseWordEntry(Element element) { @@ -51,22 +53,25 @@ Word _parseWordEntry(Element element) { final notes = element .querySelector("div.meaning-representation_notes > span") ?.transform((e) => e.trimmedText) - .transform(Note.parseAll) ?? + .transform(_parseNotes) ?? []; final definitionElements = element.querySelectorAll("div.meaning-wrapper"); final otherForms = definitionElements .firstWhereOrNull( - (e) => e.previousElementSibling?.text == "Other forms") + (e) => e.previousElementSibling?.text == "Other forms", + ) ?.transform(_parseOtherForms) ?? []; final sourceDefinitions = definitionElements - .where((e) => - e.previousElementSibling?.text != "Other forms" && - e.previousElementSibling?.text != "Wikipedia definition" && - e.querySelector(".meaning-representation_notes") == null) + .where( + (e) => + e.previousElementSibling?.text != "Other forms" && + e.previousElementSibling?.text != "Wikipedia definition" && + e.querySelector(".meaning-representation_notes") == null, + ) .fold([], _parseDefinition).toList(); final wikipediaElement = element @@ -97,34 +102,47 @@ Word _parseWordEntry(Element element) { ?.transform( (e) => element .querySelectorAll( - "#${e.attributes["data-reveal-id"]!} > ul > li > a") - .map((e2) => e2.text - .trim() - .split(" - ") - .transform((split) => Collocation(split[0], split[1]))) + "#${e.attributes["data-reveal-id"]!} > ul > li > a", + ) + .map( + (e2) => e2.text + .trim() + .split(" - ") + .transform((split) => Collocation(split[0], split[1])), + ) .toList(), ) ?? []; return Word( - word: word, - definitions: definitions, - otherForms: otherForms, - commonWord: commonWord, - wanikaniLevels: wanikaniLevels, - jlptLevel: jlptLevel, - audioUrl: audioUrl, - notes: notes, - collocations: collocations, - id: id, - inflectionId: inflectionId, - hasWikipedia: hasWikipedia); + word: word, + definitions: definitions, + otherForms: otherForms, + commonWord: commonWord, + wanikaniLevels: wanikaniLevels, + jlptLevel: jlptLevel, + audioUrl: audioUrl, + notes: notes, + collocations: collocations, + id: id, + inflectionId: inflectionId, + hasWikipedia: hasWikipedia, + ); } +List _parseNotes(String text) => text + .trim() + .replaceFirst(RegExp(r"\.$"), "") + .split(". ") + .deduplicate() + .map(Note.parse) + .toList(); + List _parseWikipediaDefinition(Element e) => [ Definition( - meanings: [e.querySelector(".meaning-meaning")!.text], - types: ["Word"]) + meanings: [e.querySelector(".meaning-meaning")!.text], + types: ["Word"], + ), ]; List _parseDefinition(List previous, Element element) { @@ -156,18 +174,20 @@ List _parseDefinition(List previous, Element element) { .allTrimmedText .deduplicate(); - final exampleSentence = - element.querySelector("div.sentence")?.transform((e) => Sentence.example( - _parseSentenceFurigana(e), - e.querySelector("span.english")!.trimmedText, - )); + final exampleSentence = element.querySelector("div.sentence")?.transform( + (e) => Sentence.example( + _parseSentenceFurigana(e), + e.querySelector("span.english")!.trimmedText, + ), + ); final definition = Definition( - meanings: meanings, - types: types, - tags: tags, - seeAlso: seeAlso, - exampleSentence: exampleSentence); + meanings: meanings, + types: types, + tags: tags, + seeAlso: seeAlso, + exampleSentence: exampleSentence, + ); return previous + [definition]; } diff --git a/lib/packages/kanji_diagram/extensions.dart b/lib/packages/kanji_diagram/extensions.dart index beef92c6..1dde52b6 100644 --- a/lib/packages/kanji_diagram/extensions.dart +++ b/lib/packages/kanji_diagram/extensions.dart @@ -7,14 +7,16 @@ extension XmlBuilderExtension on XmlBuilder { void style(String styleData) => attribute("style", styleData); void opacity(double value) => attribute("opacity", value.toStringAsFixed(2)); - void line(String styleData, int x1, int y1, int x2, int y2) => - element("line", nest: () { - style(styleData); - attribute("x1", x1); - attribute("y1", y1); - attribute("x2", x2); - attribute("y2", y2); - }); + void line(String styleData, int x1, int y1, int x2, int y2) => element( + "line", + nest: () { + style(styleData); + attribute("x1", x1); + attribute("y1", y1); + attribute("x2", x2); + attribute("y2", y2); + }, + ); } extension XmlElementExtension on XmlElement { diff --git a/lib/packages/kanji_diagram/kanji_diagram.dart b/lib/packages/kanji_diagram/kanji_diagram.dart index 41f15881..e633535d 100644 --- a/lib/packages/kanji_diagram/kanji_diagram.dart +++ b/lib/packages/kanji_diagram/kanji_diagram.dart @@ -1,8 +1,7 @@ +import "package:jsdict/packages/kanji_diagram/extensions.dart"; +import "package:jsdict/packages/kanji_diagram/style.dart"; import "package:xml/xml.dart"; -import "extensions.dart"; -import "style.dart"; - /// A class for generating stroke order diagrams for kanji. class KanjiDiagram { /// [darkTheme] determines whether to use colors suitable for a dark theme. @@ -29,7 +28,12 @@ class KanjiDiagram { void addGlobalGuides(XmlBuilder builder, int canvasWidth) { builder.line(style.boundingBox, 1, 1, canvasWidth - 1, 1); builder.line( - style.guideLine, 0, canvasHeight ~/ 2, canvasWidth, canvasHeight ~/ 2); + style.guideLine, + 0, + canvasHeight ~/ 2, + canvasWidth, + canvasHeight ~/ 2, + ); // For aesthetic reasons, left and bottom guides are not shown. // builder.line(style.boundingBox, 1, 1, 1, canvasHeight - 1); @@ -37,26 +41,40 @@ class KanjiDiagram { // canvasHeight - 1); } - void addFrameGuideLine(XmlBuilder builder) => builder.line(style.guideLine, - frameSize ~/ 2, 1, frameSize - (frameSize ~/ 2), canvasHeight - 1); + void addFrameGuideLine(XmlBuilder builder) => builder.line( + style.guideLine, + frameSize ~/ 2, + 1, + frameSize - (frameSize ~/ 2), + canvasHeight - 1, + ); void addFrameBoundingBox(XmlBuilder builder) => builder.line( - style.boundingBox, frameSize - 1, 1, frameSize - 1, canvasHeight - 1); + style.boundingBox, + frameSize - 1, + 1, + frameSize - 1, + canvasHeight - 1, + ); static final strokePattern = RegExp( - r"^[LMT]\s*(\d+(?:\.\d+)?)[,\s](\d+(?:\.\d+)?)", - caseSensitive: false); + r"^[LMT]\s*(\d+(?:\.\d+)?)[,\s](\d+(?:\.\d+)?)", + caseSensitive: false, + ); void addStartPoint(XmlBuilder builder, XmlElement path) { final pathData = path.getAttribute("d")!; final match = strokePattern.firstMatch(pathData)!; - builder.element("circle", nest: () { - builder.style(style.startPoint); - builder.attribute("cx", match.group(1)!); - builder.attribute("cy", match.group(2)!); - builder.attribute("r", 4); - }); + builder.element( + "circle", + nest: () { + builder.style(Style.startPoint); + builder.attribute("cx", match.group(1)); + builder.attribute("cy", match.group(2)); + builder.attribute("r", 4); + }, + ); } void addStrokes(XmlBuilder builder, List paths) { @@ -66,29 +84,35 @@ class KanjiDiagram { for (final currentPath in paths) { final xOffset = frameSize * (pathIndex - 1); - builder.element("g", nest: () { - builder.attribute("transform", "translate($xOffset)"); - addFrameGuideLine(builder); - - if (pathIndex < paths.length) { - addFrameBoundingBox(builder); - } - - builder.element("g", nest: () { - // Offset strokes to align with grid. - // Likely needed because we're using translate instead of matrix. - builder.attribute("transform", "translate(-4, -4)"); + builder.element( + "g", + nest: () { + builder.attribute("transform", "translate($xOffset)"); + addFrameGuideLine(builder); - // Add previously drawn paths. - for (final drawnPath in drawnPaths) { - builder.copyElement(drawnPath.withStyle(style.existingStroke)); + if (pathIndex < paths.length) { + addFrameBoundingBox(builder); } - // Add current path and starting point. - builder.copyElement(currentPath.withStyle(style.currentStroke)); - addStartPoint(builder, currentPath); - }); - }); + builder.element( + "g", + nest: () { + // Offset strokes to align with grid. + // Likely needed because we're using translate instead of matrix. + builder.attribute("transform", "translate(-4, -4)"); + + // Add previously drawn paths. + for (final drawnPath in drawnPaths) { + builder.copyElement(drawnPath.withStyle(style.existingStroke)); + } + + // Add current path and starting point. + builder.copyElement(currentPath.withStyle(style.currentStroke)); + addStartPoint(builder, currentPath); + }, + ); + }, + ); drawnPaths.add(currentPath); pathIndex++; @@ -105,17 +129,23 @@ class KanjiDiagram { final builder = XmlBuilder(); - builder.element("svg", nest: () { - final canvasWidth = (paths.length * diagramSize) ~/ 2; - builder.attribute("viewBox", "0 0 $canvasWidth $canvasHeight"); - - builder.element("g", nest: () { - builder.style(style.global); - - addGlobalGuides(builder, canvasWidth); - addStrokes(builder, paths); - }); - }); + builder.element( + "svg", + nest: () { + final canvasWidth = (paths.length * diagramSize) ~/ 2; + builder.attribute("viewBox", "0 0 $canvasWidth $canvasHeight"); + + builder.element( + "g", + nest: () { + builder.style(Style.global); + + addGlobalGuides(builder, canvasWidth); + addStrokes(builder, paths); + }, + ); + }, + ); return builder.buildDocument().toXmlString(); } diff --git a/lib/packages/kanji_diagram/style.dart b/lib/packages/kanji_diagram/style.dart index 46d0dfd7..8f25f369 100644 --- a/lib/packages/kanji_diagram/style.dart +++ b/lib/packages/kanji_diagram/style.dart @@ -5,17 +5,17 @@ class Style { String get currentStrokeColor => darkTheme ? "#eee" : "#000"; String get existingStrokeColor => darkTheme ? "#999" : "#aaa"; - final global = - "fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round"; - static const _lineCommon = "stroke-width:2;stroke-linecap:square"; String get boundingBox => "$_lineCommon;stroke:$lineColor"; String get guideLine => "$boundingBox;stroke-dasharray:5,5"; - final startPoint = "fill:rgba(255,0,0,0.7);stroke:none"; - String get currentStroke => "stroke:$currentStrokeColor"; String get existingStroke => "stroke:$existingStrokeColor"; const Style(this.darkTheme); + + static const global = + "fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round"; + + static const startPoint = "fill:rgba(255,0,0,0.7);stroke:none"; } diff --git a/lib/packages/link_handler.dart b/lib/packages/link_handler.dart index adca1ffe..ac4d955a 100644 --- a/lib/packages/link_handler.dart +++ b/lib/packages/link_handler.dart @@ -1,5 +1,6 @@ import "dart:async"; +import "package:app_links/app_links.dart"; import "package:collection/collection.dart"; import "package:flutter/material.dart"; import "package:flutter/services.dart"; @@ -12,7 +13,6 @@ import "package:jsdict/screens/kanji_details/kanji_details_screen.dart"; import "package:jsdict/screens/sentence_details_screen.dart"; import "package:jsdict/screens/word_details/word_details_screen.dart"; import "package:jsdict/widgets/error_indicator.dart"; -import "package:app_links/app_links.dart"; /// Handles app/universal links by opening the corresponding screen. /// Search links are handled by returning to the top-level search screen diff --git a/lib/packages/radical_search/kanji_radicals.dart b/lib/packages/radical_search/kanji_radicals.dart index b2e128e1..fd255125 100644 --- a/lib/packages/radical_search/kanji_radicals.dart +++ b/lib/packages/radical_search/kanji_radicals.dart @@ -6353,5 +6353,5 @@ const Map> kanjiRadicals = { "驪": ["亠", "冂", "广", "比", "⺣", "馬", "鹿"], "鬱": ["冖", "凵", "匕", "彡", "木", "缶", "鬯"], "鸞": ["小", "幺", "⺣", "糸", "言", "鳥"], - "驫": ["⺣", "馬"] + "驫": ["⺣", "馬"], }; diff --git a/lib/packages/radical_search/radical_search.dart b/lib/packages/radical_search/radical_search.dart index 6690a7af..0bce0e33 100644 --- a/lib/packages/radical_search/radical_search.dart +++ b/lib/packages/radical_search/radical_search.dart @@ -1,45 +1,39 @@ import "package:collection/collection.dart"; import "package:jsdict/packages/list_extensions.dart"; -import "kanji_radicals.dart"; +import "package:jsdict/packages/radical_search/kanji_radicals.dart"; -class RadicalSearch { - static List kanjiByRadicals(List radicals) { - return kanjiRadicals.entries - .where((entry) { - for (final radical in radicals) { - if (!entry.value.contains(radical)) return false; - } - return true; - }) - .map((entry) => entry.key) - .toList(); - } +List kanjiByRadicals(List radicals) => kanjiRadicals.entries + .where((entry) { + for (final radical in radicals) { + if (!entry.value.contains(radical)) return false; + } + return true; + }) + .map((entry) => entry.key) + .toList(); - static List validRadicals(List kanjiList) { - return kanjiList - .map((kanji) => kanjiRadicals[kanji]!) - .toList() - .flattened - .toList() - .deduplicate(); - } +List findValidRadicals(List kanjiList) => kanjiList + .map((kanji) => kanjiRadicals[kanji]!) + .toList() + .flattened + .toList() + .deduplicate(); - static const Map> radicalsByStrokes = { - 1: ["一", "|", "丶", "ノ", "乙", "亅"], - 2: ["二", "亠", "人", "⺅", "𠆢", "儿", "入", "ハ", "丷", "冂", "冖", "冫", "几", "凵", "刀", "⺉", "力", "勹", "匕", "匚", "十", "卜", "卩", "厂", "厶", "又", "マ", "九", "ユ", "乃", "𠂉"], - 3: ["⻌", "口", "囗", "土", "士", "夂", "夕", "大", "女", "子", "宀", "寸", "小", "⺌", "尢", "尸", "屮", "山", "川", "巛", "工", "已", "巾", "干", "幺", "广", "廴", "廾", "弋", "弓", "ヨ", "彑", "彡", "彳", "⺖", "⺘", "⺡", "⺨", "⺾", "⻏", "⻖", "也", "亡", "及", "久"], - 4: ["⺹", "心", "戈", "戸", "手", "支", "攵", "文", "斗", "斤", "方", "无", "日", "曰", "月", "木", "欠", "止", "歹", "殳", "比", "毛", "氏", "气", "水", "火", "⺣", "爪", "父", "爻", "爿", "片", "牛", "犬", "⺭", "王", "元", "井", "勿", "尤", "五", "屯", "巴", "毋"], - 5: ["玄", "瓦", "甘", "生", "用", "田", "疋", "疒", "癶", "白", "皮", "皿", "目", "矛", "矢", "石", "示", "禸", "禾", "穴", "立", "⻂", "世", "巨", "冊", "母", "⺲", "牙"], - 6: ["瓜", "竹", "米", "糸", "缶", "羊", "羽", "而", "耒", "耳", "聿", "肉", "自", "至", "臼", "舌", "舟", "艮", "色", "虍", "虫", "血", "行", "衣", "西"], - 7: ["臣", "見", "角", "言", "谷", "豆", "豕", "豸", "貝", "赤", "走", "足", "身", "車", "辛", "辰", "酉", "釆", "里", "舛", "麦"], - 8: ["金", "長", "門", "隶", "隹", "雨", "青", "非", "奄", "岡", "免", "斉"], - 9: ["面", "革", "韭", "音", "頁", "風", "飛", "食", "首", "香", "品"], - 10: ["馬", "骨", "高", "髟", "鬥", "鬯", "鬲", "鬼", "竜", "韋"], - 11: ["魚", "鳥", "鹵", "鹿", "麻", "亀", "啇", "黄", "黒"], - 12: ["黍", "黹", "無", "歯"], - 13: ["黽", "鼎", "鼓", "鼠"], - 14: ["鼻", "齊"], - 17: ["龠"], - }; -} +const Map> radicalsByStrokeCount = { + 1: ["一", "|", "丶", "ノ", "乙", "亅"], + 2: ["二", "亠", "人", "⺅", "𠆢", "儿", "入", "ハ", "丷", "冂", "冖", "冫", "几", "凵", "刀", "⺉", "力", "勹", "匕", "匚", "十", "卜", "卩", "厂", "厶", "又", "マ", "九", "ユ", "乃", "𠂉"], + 3: ["⻌", "口", "囗", "土", "士", "夂", "夕", "大", "女", "子", "宀", "寸", "小", "⺌", "尢", "尸", "屮", "山", "川", "巛", "工", "已", "巾", "干", "幺", "广", "廴", "廾", "弋", "弓", "ヨ", "彑", "彡", "彳", "⺖", "⺘", "⺡", "⺨", "⺾", "⻏", "⻖", "也", "亡", "及", "久"], + 4: ["⺹", "心", "戈", "戸", "手", "支", "攵", "文", "斗", "斤", "方", "无", "日", "曰", "月", "木", "欠", "止", "歹", "殳", "比", "毛", "氏", "气", "水", "火", "⺣", "爪", "父", "爻", "爿", "片", "牛", "犬", "⺭", "王", "元", "井", "勿", "尤", "五", "屯", "巴", "毋"], + 5: ["玄", "瓦", "甘", "生", "用", "田", "疋", "疒", "癶", "白", "皮", "皿", "目", "矛", "矢", "石", "示", "禸", "禾", "穴", "立", "⻂", "世", "巨", "冊", "母", "⺲", "牙"], + 6: ["瓜", "竹", "米", "糸", "缶", "羊", "羽", "而", "耒", "耳", "聿", "肉", "自", "至", "臼", "舌", "舟", "艮", "色", "虍", "虫", "血", "行", "衣", "西"], + 7: ["臣", "見", "角", "言", "谷", "豆", "豕", "豸", "貝", "赤", "走", "足", "身", "車", "辛", "辰", "酉", "釆", "里", "舛", "麦"], + 8: ["金", "長", "門", "隶", "隹", "雨", "青", "非", "奄", "岡", "免", "斉"], + 9: ["面", "革", "韭", "音", "頁", "風", "飛", "食", "首", "香", "品"], + 10: ["馬", "骨", "高", "髟", "鬥", "鬯", "鬲", "鬼", "竜", "韋"], + 11: ["魚", "鳥", "鹵", "鹿", "麻", "亀", "啇", "黄", "黒"], + 12: ["黍", "黹", "無", "歯"], + 13: ["黽", "鼎", "鼓", "鼠"], + 14: ["鼻", "齊"], + 17: ["龠"], +}; diff --git a/lib/packages/remove_tags.dart b/lib/packages/remove_tags.dart index 8b612d67..1221843f 100644 --- a/lib/packages/remove_tags.dart +++ b/lib/packages/remove_tags.dart @@ -1,14 +1,17 @@ String removeTags(String input) => input - .replaceAll(RegExp(r"(?<= |^)#[A-Za-z0-9-]+"), "") + .replaceAll(RegExp("(?<= |^)#[A-Za-z0-9-]+"), "") .trim() .replaceAll(RegExp(r"\s+"), " ") - .replaceFirst(RegExp(" \"\$"), "\""); + .replaceFirst(RegExp(' "\$'), '"'); String removeTypeTags(String input) => input .replaceAll( - RegExp(r'(?<= |^)#(words?|kanji|names?|sentences?)(?=\s|"|$)', - caseSensitive: false), - "") + RegExp( + r'(?<= |^)#(words?|kanji|names?|sentences?)(?=\s|"|$)', + caseSensitive: false, + ), + "", + ) .trim() .replaceAll(RegExp(r"\s+"), " ") - .replaceFirst(RegExp(" \"\$"), "\""); + .replaceFirst(RegExp(' "\$'), '"'); diff --git a/lib/packages/string_util.dart b/lib/packages/string_util.dart index ec34720a..f5a45ea8 100644 --- a/lib/packages/string_util.dart +++ b/lib/packages/string_util.dart @@ -1,5 +1,6 @@ /// Allows text breaking. const zeroWidthSpace = "\u200B"; + /// Prevents text breaking. const zeroWidthNoBreakSpace = "\uFEFF"; diff --git a/lib/packages/tags.dart b/lib/packages/tags.dart index 351eae9a..85d7bf92 100644 --- a/lib/packages/tags.dart +++ b/lib/packages/tags.dart @@ -289,7 +289,7 @@ const wordTags = { "Godan verb with zu ending": "v5z", "Wasei, word made in Japan": "wasei", "Literary or formal term": "litf", - } + }, }; const nameTags = { diff --git a/lib/providers/theme_provider.dart b/lib/providers/theme_provider.dart index c05110de..0e1fb401 100644 --- a/lib/providers/theme_provider.dart +++ b/lib/providers/theme_provider.dart @@ -19,7 +19,7 @@ class ThemeProvider extends ChangeNotifier { String get currentThemeString => _preferences.getString(_themeKey) ?? _defaultThemeString; - void setTheme(String name) async { + Future setTheme(String name) async { await _preferences.setString(_themeKey, name); notifyListeners(); } @@ -27,7 +27,7 @@ class ThemeProvider extends ChangeNotifier { static const _dynamicColorsKey = "DynamicColors"; bool get dynamicColors => _preferences.getBool(_dynamicColorsKey) ?? true; - void setDynamicColors(bool value) async { + Future setDynamicColors(bool value) async { await _preferences.setBool(_dynamicColorsKey, value); notifyListeners(); } diff --git a/lib/screens/kanji_details/compound_list.dart b/lib/screens/kanji_details/compound_list.dart index 1ca324bb..9d63c922 100644 --- a/lib/screens/kanji_details/compound_list.dart +++ b/lib/screens/kanji_details/compound_list.dart @@ -1,9 +1,9 @@ import "package:expansion_tile_card/expansion_tile_card.dart"; import "package:flutter/material.dart"; import "package:jsdict/jp_text.dart"; +import "package:jsdict/models/models.dart"; import "package:jsdict/packages/katakana_convert.dart"; import "package:jsdict/packages/list_extensions.dart"; -import "package:jsdict/models/models.dart"; import "package:jsdict/packages/navigation.dart"; import "package:jsdict/screens/word_details/word_details_screen.dart"; import "package:jsdict/widgets/entry_tile.dart"; @@ -20,17 +20,21 @@ class CompoundList extends StatelessWidget { shadowColor: Theme.of(context).colorScheme.shadow, title: Text("$type reading compounds"), children: compounds - .map((compound) => EntryTile( - contentPadding: - const EdgeInsets.symmetric(vertical: 4, horizontal: 16), - isLast: compound == compounds.last, - title: JpText("${compound.compound} 【${compound.reading}】"), - subtitle: Text(compound.meanings.join(", ")), - onTap: pushScreen( - context, - WordDetailsScreen.search( - "${compound.compound} ${convertKatakana(compound.reading)}")), - )) + .map( + (compound) => EntryTile( + contentPadding: + const EdgeInsets.symmetric(vertical: 4, horizontal: 16), + isLast: compound == compounds.last, + title: JpText("${compound.compound} 【${compound.reading}】"), + subtitle: Text(compound.meanings.join(", ")), + onTap: pushScreen( + context, + WordDetailsScreen.search( + "${compound.compound} ${convertKatakana(compound.reading)}", + ), + ), + ), + ) .toList() .intersperce(const Divider(height: 0)), ); diff --git a/lib/screens/kanji_details/kanji_details_screen.dart b/lib/screens/kanji_details/kanji_details_screen.dart index 6fa18973..25eea565 100644 --- a/lib/screens/kanji_details/kanji_details_screen.dart +++ b/lib/screens/kanji_details/kanji_details_screen.dart @@ -2,23 +2,22 @@ import "package:collection/collection.dart"; import "package:expansion_tile_card/expansion_tile_card.dart"; import "package:flutter/material.dart"; import "package:jsdict/jp_text.dart"; +import "package:jsdict/models/models.dart"; import "package:jsdict/packages/copy.dart"; import "package:jsdict/packages/katakana_convert.dart"; import "package:jsdict/packages/list_extensions.dart"; import "package:jsdict/packages/navigation.dart"; import "package:jsdict/packages/string_util.dart"; +import "package:jsdict/screens/kanji_details/compound_list.dart"; +import "package:jsdict/screens/kanji_details/stroke_diagram.dart"; import "package:jsdict/screens/search/result_page.dart"; -import "package:jsdict/widgets/action_dialog.dart"; -import "package:jsdict/widgets/link_popup.dart"; -import "package:jsdict/models/models.dart"; import "package:jsdict/singletons.dart"; +import "package:jsdict/widgets/action_dialog.dart"; import "package:jsdict/widgets/info_chips.dart"; +import "package:jsdict/widgets/link_popup.dart"; import "package:jsdict/widgets/link_span.dart"; import "package:jsdict/widgets/loader.dart"; -import "compound_list.dart"; -import "stroke_diagram.dart"; - class KanjiDetailsScreen extends StatelessWidget { const KanjiDetailsScreen(Kanji this.kanji, {super.key}) : kanjiId = null; const KanjiDetailsScreen.id(String this.kanjiId, {super.key}) : kanji = null; @@ -80,8 +79,10 @@ class _KanjiContentWidget extends StatelessWidget { child: Container( alignment: Alignment.center, padding: const EdgeInsets.symmetric(vertical: 8), - child: Text(kanji.kanji, - style: const TextStyle(fontSize: 40).jp()), + child: Text( + kanji.kanji, + style: const TextStyle(fontSize: 40).jp(), + ), ), ), Wrap( @@ -89,8 +90,10 @@ class _KanjiContentWidget extends StatelessWidget { children: [ InfoChip("${kanji.strokeCount} strokes", color: Colors.green), if (kanji.jlptLevel != JLPTLevel.none) - InfoChip("JLPT ${kanji.jlptLevel.toString()}", - color: Colors.blue), + InfoChip( + "JLPT ${kanji.jlptLevel}", + color: Colors.blue, + ), if (kanji.type != null) InfoChip(kanji.type.toString(), color: Colors.blue), ], @@ -110,20 +113,22 @@ class _KanjiContentWidget extends StatelessWidget { valueListenable: radicalValue, builder: (_, __, ___) => radical != null ? JpText( - "Radical: ${radical!.meanings.join(', ')} ${radical!.character}") + "Radical: ${radical!.meanings.join(', ')} ${radical!.character}", + ) : const SizedBox(), ), ], ), ), const Divider(), - kanji.details != null - ? _KanjiDetailsWidget(kanji, kanji.details!) - : LoaderWidget( - onLoad: () => _future, - handler: (kanjiDetails) => - _KanjiDetailsWidget(kanji, kanjiDetails.details!), - ), + if (kanji.details != null) + _KanjiDetailsWidget(kanji, kanji.details!) + else + LoaderWidget( + onLoad: () => _future, + handler: (kanjiDetails) => + _KanjiDetailsWidget(kanji, kanjiDetails.details!), + ), ], ), ), @@ -147,19 +152,32 @@ class _ReadingsWidget extends StatelessWidget { return RichText( text: TextSpan( - children: [ - TextSpan( - text: "$name: ", style: TextStyle(color: textColor).jp()) - ] + - readings - .map((reading) => LinkSpan(context, - text: reading.noBreak, - onTap: pushScreen( - context, ResultPageScreen(query(reading))))) - .toList() - .intersperce(TextSpan( - text: "、 ", style: TextStyle(color: textColor).jp())), - style: TextStyle(color: textColor, height: 1.5).jp()), + children: [ + TextSpan( + text: "$name: ", + style: TextStyle(color: textColor).jp(), + ), + ] + + readings + .map( + (reading) => LinkSpan( + context, + text: reading.noBreak, + onTap: pushScreen( + context, + ResultPageScreen(query(reading)), + ), + ), + ) + .toList() + .intersperce( + TextSpan( + text: "、 ", + style: TextStyle(color: textColor).jp(), + ), + ), + style: TextStyle(color: textColor, height: 1.5).jp(), + ), ); } } @@ -176,28 +194,33 @@ class _KanjiDetailsWidget extends StatelessWidget { children: [ if (kanjiDetails.parts.length > 1) ...[ Wrap( - alignment: WrapAlignment.center, - children: kanjiDetails.parts - .whereNot((part) => part == kanji.kanji) - .map((part) => Card( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4)), - child: InkWell( - borderRadius: BorderRadius.circular(4), - onTap: - pushScreen(context, KanjiDetailsScreen.id(part)), - onLongPress: () => showActionDialog(context, [ - ActionTile.url(Kanji.createUrl(part)), - ActionTile.text("Kanji", part), - ]), - child: Padding( - padding: const EdgeInsets.all(8), - child: Text(part, - style: const TextStyle(fontSize: 20).jp()), - ), + alignment: WrapAlignment.center, + children: kanjiDetails.parts + .whereNot((part) => part == kanji.kanji) + .map( + (part) => Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + ), + child: InkWell( + borderRadius: BorderRadius.circular(4), + onTap: pushScreen(context, KanjiDetailsScreen.id(part)), + onLongPress: () => showActionDialog(context, [ + ActionTile.url(Kanji.createUrl(part)), + ActionTile.text("Kanji", part), + ]), + child: Padding( + padding: const EdgeInsets.all(8), + child: Text( + part, + style: const TextStyle(fontSize: 20).jp(), ), - )) - .toList()), + ), + ), + ), + ) + .toList(), + ), const Divider(), ], if (kanjiDetails.variants.isNotEmpty) @@ -233,20 +256,27 @@ class _VariantsWidget extends StatelessWidget { .map( (variant) => Card( shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4)), + borderRadius: BorderRadius.circular(4), + ), child: InkWell( borderRadius: BorderRadius.circular(4), onTap: pushScreen( - context, KanjiDetailsScreen.id(variant)), + context, + KanjiDetailsScreen.id(variant), + ), onLongPress: () => showActionDialog(context, [ ActionTile.url(Kanji.createUrl(variant)), ActionTile.text("Kanji", variant), ]), child: Padding( padding: const EdgeInsets.symmetric( - vertical: 8, horizontal: 12), - child: Text(variant, - style: const TextStyle(fontSize: 20).jp()), + vertical: 8, + horizontal: 12, + ), + child: Text( + variant, + style: const TextStyle(fontSize: 20).jp(), + ), ), ), ), @@ -255,7 +285,7 @@ class _VariantsWidget extends StatelessWidget { ), ), ], - ) + ), ], ); } diff --git a/lib/screens/kanji_details/stroke_diagram.dart b/lib/screens/kanji_details/stroke_diagram.dart index eda1a813..c9e55b38 100644 --- a/lib/screens/kanji_details/stroke_diagram.dart +++ b/lib/screens/kanji_details/stroke_diagram.dart @@ -15,7 +15,7 @@ class StrokeDiagramWidget extends StatelessWidget { Future getData() async { try { return await rootBundle.loadString("assets/kanjivg/data/$kanjiCode.svg"); - } on FlutterError { + } catch (_) { // asset not found means that KanjiVg doesn't have data for the kanji return ""; } @@ -24,27 +24,32 @@ class StrokeDiagramWidget extends StatelessWidget { @override Widget build(BuildContext context) { return LoaderWidget( - onLoad: getData, - handler: (data) { - if (data.isEmpty) { - return const SizedBox(); - } + onLoad: getData, + handler: (data) { + if (data.isEmpty) { + return const SizedBox(); + } - return ExpansionTileCard( - shadowColor: Theme.of(context).colorScheme.shadow, - title: const Text("Stroke Order Diagram"), - children: [ - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: BrightnessBuilder(builder: (context, brightness) { - return SvgPicture.string( - KanjiDiagram(darkTheme: brightness == Brightness.dark) - .create(data), - height: 90); - })), - ], - ); - }); + return ExpansionTileCard( + shadowColor: Theme.of(context).colorScheme.shadow, + title: const Text("Stroke Order Diagram"), + children: [ + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: BrightnessBuilder( + builder: (context, brightness) { + return SvgPicture.string( + KanjiDiagram(darkTheme: brightness == Brightness.dark) + .create(data), + height: 90, + ); + }, + ), + ), + ], + ); + }, + ); } } @@ -67,12 +72,18 @@ class BrightnessBuilder extends StatelessWidget { @override Widget build(BuildContext context) { - return Consumer(builder: (context, themeProvider, _) { - return MediaQuery( + return Consumer( + builder: (context, themeProvider, _) { + return MediaQuery( data: const MediaQueryData(), child: Builder( - builder: (context) => builder(context, - getBrightness(context, themeProvider.currentTheme)))); - }); + builder: (context) => builder( + context, + getBrightness(context, themeProvider.currentTheme), + ), + ), + ); + }, + ); } } diff --git a/lib/screens/name_details_screen.dart b/lib/screens/name_details_screen.dart index 7cf06f70..b2b7a6ad 100644 --- a/lib/screens/name_details_screen.dart +++ b/lib/screens/name_details_screen.dart @@ -2,12 +2,12 @@ import "package:flutter/material.dart"; import "package:jsdict/jp_text.dart"; import "package:jsdict/models/models.dart"; import "package:jsdict/packages/is_kanji.dart"; -import "package:jsdict/widgets/wikipedia.dart"; import "package:jsdict/singletons.dart"; import "package:jsdict/widgets/info_chips.dart"; import "package:jsdict/widgets/items/kanji_item.dart"; import "package:jsdict/widgets/link_popup.dart"; import "package:jsdict/widgets/loader.dart"; +import "package:jsdict/widgets/wikipedia.dart"; class NameDetailsScreen extends StatelessWidget { const NameDetailsScreen(this.name, {super.key}); @@ -25,7 +25,7 @@ class NameDetailsScreen extends StatelessWidget { ( "Open in Browser", "https://jisho.org/word/${name.wikipediaWord}" - ) + ), ]), ], ), @@ -35,24 +35,27 @@ class NameDetailsScreen extends StatelessWidget { child: Column( children: [ SelectionArea( - child: Container( - alignment: Alignment.center, - padding: const EdgeInsets.all(12), - child: Column( - children: [ - Text( - name.japanese + - (name.reading != null - ? "\n${name.reading}" - : ""), - style: const TextStyle(fontSize: 20).jp(), - textAlign: TextAlign.center), - const SizedBox(height: 12), - Text(name.english, - style: const TextStyle(fontSize: 18), - textAlign: TextAlign.center), - ], - ))), + child: Container( + alignment: Alignment.center, + padding: const EdgeInsets.all(12), + child: Column( + children: [ + Text( + name.japanese + + (name.reading != null ? "\n${name.reading}" : ""), + style: const TextStyle(fontSize: 20).jp(), + textAlign: TextAlign.center, + ), + const SizedBox(height: 12), + Text( + name.english, + style: const TextStyle(fontSize: 18), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), if (name.type != null) ...[ InfoChip(name.type!), const SizedBox(height: 10), diff --git a/lib/screens/search/result_page.dart b/lib/screens/search/result_page.dart index 6eefebe7..9becea5d 100644 --- a/lib/screens/search/result_page.dart +++ b/lib/screens/search/result_page.dart @@ -152,69 +152,81 @@ class _ResultPageState extends State> SearchMetaInfo( listenable: grammarInfo, builder: (context, value) => SelectableText.rich( - textAlign: TextAlign.center, - TextSpan( - style: TextStyle(color: textColor, height: 1.5).jp(), - children: [ - TextSpan(text: value.word), - const TextSpan(text: " could be an inflection of "), - LinkSpan(context, - text: value.possibleInflectionOf, - onTap: pushScreen( - context, - WordDetailsScreen.search( - value.possibleInflectionOf))), - ], - )), + textAlign: TextAlign.center, + TextSpan( + style: TextStyle(color: textColor, height: 1.5).jp(), + children: [ + TextSpan(text: value.word), + const TextSpan(text: " could be an inflection of "), + LinkSpan( + context, + text: value.possibleInflectionOf, + onTap: pushScreen( + context, + WordDetailsScreen.search( + value.possibleInflectionOf, + ), + ), + ), + ], + ), + ), ), SearchMetaInfo( listenable: correction, builder: (_, value) => SelectableText.rich( - textAlign: TextAlign.center, - TextSpan( - style: TextStyle(color: textColor, height: 1.5).jp(), - children: [ - const TextSpan(text: "Searched for "), - TextSpan( - text: value.effective, - style: const TextStyle(fontWeight: FontWeight.w600)), - const TextSpan(text: "\n"), - if (!value.noMatchesForOriginal) ...[ - const TextSpan(text: "Try searching for "), - LinkSpan(context, text: value.original, bold: true, - onTap: () { + textAlign: TextAlign.center, + TextSpan( + style: TextStyle(color: textColor, height: 1.5).jp(), + children: [ + const TextSpan(text: "Searched for "), + TextSpan( + text: value.effective, + style: const TextStyle(fontWeight: FontWeight.w600), + ), + const TextSpan(text: "\n"), + if (!value.noMatchesForOriginal) ...[ + const TextSpan(text: "Try searching for "), + LinkSpan( + context, + text: value.original, + bold: true, + onTap: () { queryProvider.searchController.text = value.original; queryProvider.updateQuery(); - }), - ] else - TextSpan(text: "No matches for ${value.original}"), - ], - )), + }, + ), + ] else + TextSpan(text: "No matches for ${value.original}"), + ], + ), + ), ), SliverPadding( padding: const EdgeInsets.all(8.0), sliver: PagedSliverList( pagingController: pagingController, builderDelegate: PagedChildBuilderDelegate( - itemBuilder: (context, item, index) => _createItem(item), - firstPageErrorIndicatorBuilder: (context) => ErrorIndicator( - (pagingController.error.$1 as Object), - stackTrace: (pagingController.error.$2 as StackTrace), - onRetry: pagingController.refresh, - ), - noItemsFoundIndicatorBuilder: (context) { - return Container( - alignment: Alignment.topCenter, - margin: const EdgeInsets.all(16), - child: Text( - noMatchesFor.isNotEmpty - ? "No matches for:\n${noMatchesFor.join("\n")}" - : "No matches found", - textAlign: TextAlign.center, - style: const TextStyle(height: 1.75), - ), - ); - }), + itemBuilder: (context, item, index) => _createItem(item), + firstPageErrorIndicatorBuilder: (context) => ErrorIndicator( + (pagingController.error as (Object, StackTrace)).$1, + stackTrace: (pagingController.error as (Object, StackTrace)).$2, + onRetry: pagingController.refresh, + ), + noItemsFoundIndicatorBuilder: (context) { + return Container( + alignment: Alignment.topCenter, + margin: const EdgeInsets.all(16), + child: Text( + noMatchesFor.isNotEmpty + ? "No matches for:\n${noMatchesFor.join("\n")}" + : "No matches found", + textAlign: TextAlign.center, + style: const TextStyle(height: 1.75), + ), + ); + }, + ), ), ), ], diff --git a/lib/screens/search/search_screen.dart b/lib/screens/search/search_screen.dart index 2faf96ef..3055e353 100644 --- a/lib/screens/search/search_screen.dart +++ b/lib/screens/search/search_screen.dart @@ -64,19 +64,19 @@ class _SearchScreenState extends State focusNode: _searchFocusNode, controller: searchController, onSubmitted: (_) => queryProvider.updateQuery(), - autofocus: false, decoration: InputDecoration( - prefixIcon: const Icon(Icons.search), - border: InputBorder.none, - hintText: "Search...", - suffixIcon: IconButton( - icon: const Icon(Icons.clear), - onPressed: () { - _searchFocusNode.requestFocus(); - searchController.clear(); - }, - tooltip: "Clear", - )), + prefixIcon: const Icon(Icons.search), + border: InputBorder.none, + hintText: "Search...", + suffixIcon: IconButton( + icon: const Icon(Icons.clear), + onPressed: () { + _searchFocusNode.requestFocus(); + searchController.clear(); + }, + tooltip: "Clear", + ), + ), ), actions: [ IconButton( @@ -91,25 +91,29 @@ class _SearchScreenState extends State ), ], bottom: TabBar( - controller: _tabController, - isScrollable: true, - tabAlignment: TabAlignment.center, - tabs: const [ - Tab(text: "Words"), - Tab(text: "Kanji"), - Tab(text: "Names"), - Tab(text: "Sentences"), - ]), + controller: _tabController, + isScrollable: true, + tabAlignment: TabAlignment.center, + tabs: const [ + Tab(text: "Words"), + Tab(text: "Kanji"), + Tab(text: "Names"), + Tab(text: "Sentences"), + ], + ), ), body: Consumer( builder: (_, provider, __) => provider.query.isEmpty ? SearchScreen.placeholder - : TabBarView(controller: _tabController, children: [ - ResultPage(provider.query, key: UniqueKey()), - ResultPage(provider.query, key: UniqueKey()), - ResultPage(provider.query, key: UniqueKey()), - ResultPage(provider.query, key: UniqueKey()), - ]), + : TabBarView( + controller: _tabController, + children: [ + ResultPage(provider.query, key: UniqueKey()), + ResultPage(provider.query, key: UniqueKey()), + ResultPage(provider.query, key: UniqueKey()), + ResultPage(provider.query, key: UniqueKey()), + ], + ), ), ); } diff --git a/lib/screens/search_options/radical_search_screen.dart b/lib/screens/search_options/radical_search_screen.dart index 24761397..673daf32 100644 --- a/lib/screens/search_options/radical_search_screen.dart +++ b/lib/screens/search_options/radical_search_screen.dart @@ -48,8 +48,8 @@ class _RadicalSearchState extends State<_RadicalSearch> { } void _update(List newSelectedRadicals) { - final newMatchingKanji = RadicalSearch.kanjiByRadicals(selectedRadicals); - final newValidRadicals = RadicalSearch.validRadicals(newMatchingKanji); + final newMatchingKanji = kanjiByRadicals(selectedRadicals); + final newValidRadicals = findValidRadicals(newMatchingKanji); setState(() { selectedRadicals = newSelectedRadicals; @@ -65,7 +65,6 @@ class _RadicalSearchState extends State<_RadicalSearch> { return Column( children: [ Expanded( - flex: 1, child: _KanjiSelection( matchingKanji, onSelect: (kanji) { @@ -79,7 +78,11 @@ class _RadicalSearchState extends State<_RadicalSearch> { Expanded( flex: 3, child: _RadicalSelection( - selectedRadicals, validRadicals, selectRadical, deselectRadical), + selectedRadicals, + validRadicals, + selectRadical, + deselectRadical, + ), ), ], ); @@ -87,8 +90,12 @@ class _RadicalSearchState extends State<_RadicalSearch> { } class _RadicalSelection extends StatelessWidget { - const _RadicalSelection(this.selectedRadicals, this.validRadicals, - this.onSelect, this.onDeselect); + const _RadicalSelection( + this.selectedRadicals, + this.validRadicals, + this.onSelect, + this.onDeselect, + ); final List selectedRadicals; final List validRadicals; @@ -106,8 +113,10 @@ class _RadicalSelection extends StatelessWidget { return SingleChildScrollView( child: Center( child: Wrap( - children: List.from(RadicalSearch.radicalsByStrokes.keys - .map((strokeCount) => [ + children: List.from( + radicalsByStrokeCount.keys + .map( + (strokeCount) => [ _CustomButton( strokeCount.toString(), backgroundColor: strokeIndicatorColor, @@ -118,8 +127,7 @@ class _RadicalSelection extends StatelessWidget { ), padding: 3, ), - ...RadicalSearch.radicalsByStrokes[strokeCount]! - .map((radical) { + ...radicalsByStrokeCount[strokeCount]!.map((radical) { final isSelected = selectedRadicals.contains(radical); final isValid = validRadicals.isEmpty || validRadicals.contains(radical); @@ -128,17 +136,20 @@ class _RadicalSelection extends StatelessWidget { radical, onPressed: isSelected ? () => onDeselect(radical) - : !isValid - ? null - : () => onSelect(radical), + : isValid + ? () => onSelect(radical) + : null, backgroundColor: isSelected ? selectedColor : null, textStyle: TextStyle( - fontSize: 20, - color: isValid ? textColor : disabledColor), + fontSize: 20, + color: isValid ? textColor : disabledColor, + ), ); }), - ]) - .flattened), + ], + ) + .flattened, + ), ), ), ); @@ -146,8 +157,11 @@ class _RadicalSelection extends StatelessWidget { } class _KanjiSelection extends StatelessWidget { - const _KanjiSelection(this.matchingKanji, - {required this.onSelect, required this.onReset}); + const _KanjiSelection( + this.matchingKanji, { + required this.onSelect, + required this.onReset, + }); final List matchingKanji; final Function(String) onSelect; @@ -176,16 +190,16 @@ class _KanjiSelection extends StatelessWidget { iconColor: textColor, onPressed: onReset, ), - ...matchingKanji - .take(displayLimit) - .map((kanji) => _CustomButton( - kanji, - onPressed: () => onSelect(kanji), - backgroundColor: backgroundColor, - textStyle: - TextStyle(fontSize: 20, color: textColor), - padding: 1.5, - )), + ...matchingKanji.take(displayLimit).map( + (kanji) => _CustomButton( + kanji, + onPressed: () => onSelect(kanji), + backgroundColor: backgroundColor, + textStyle: + TextStyle(fontSize: 20, color: textColor), + padding: 1.5, + ), + ), ], ), ), @@ -195,9 +209,13 @@ class _KanjiSelection extends StatelessWidget { } class _CustomButton extends StatelessWidget { - const _CustomButton(this.text, - {this.onPressed, this.textStyle, this.backgroundColor, this.padding = 0}) - : iconData = null, + const _CustomButton( + this.text, { + this.onPressed, + this.textStyle, + this.backgroundColor, + this.padding = 0, + }) : iconData = null, iconColor = null, iconSize = 0; @@ -213,7 +231,6 @@ class _CustomButton extends StatelessWidget { final Function()? onPressed; final Color? backgroundColor; - final double size = 32; final double padding; final String text; @@ -223,7 +240,8 @@ class _CustomButton extends StatelessWidget { final double iconSize; final Color? iconColor; - double get _size => size - (padding * 2); + static const _size = 32; + double get size => _size - (padding * 2); @override Widget build(BuildContext context) { @@ -236,8 +254,8 @@ class _CustomButton extends StatelessWidget { padding: EdgeInsets.zero, tapTargetSize: MaterialTapTargetSize.shrinkWrap, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), - fixedSize: Size(_size, _size), - minimumSize: const Size(0, 0), + fixedSize: Size(size, size), + minimumSize: Size.zero, ), child: iconData != null ? Icon(iconData, size: iconSize, color: iconColor) diff --git a/lib/screens/search_options/search_options_screen.dart b/lib/screens/search_options/search_options_screen.dart index 2f42542c..878e0e07 100644 --- a/lib/screens/search_options/search_options_screen.dart +++ b/lib/screens/search_options/search_options_screen.dart @@ -3,8 +3,11 @@ import "package:jsdict/jp_text.dart"; import "package:jsdict/providers/query_provider.dart"; class SearchOptionsScreen extends StatelessWidget { - const SearchOptionsScreen( - {super.key, required this.body, this.floatingActionButton}); + const SearchOptionsScreen({ + super.key, + required this.body, + this.floatingActionButton, + }); final Widget body; final Widget? floatingActionButton; @@ -23,15 +26,15 @@ class SearchOptionsScreen extends StatelessWidget { title: TextField( style: jpTextStyle, controller: searchController, - autofocus: false, decoration: InputDecoration( - border: InputBorder.none, - hintText: "Search...", - suffixIcon: IconButton( - icon: const Icon(Icons.clear), - onPressed: searchController.clear, - tooltip: "Clear", - )), + border: InputBorder.none, + hintText: "Search...", + suffixIcon: IconButton( + icon: const Icon(Icons.clear), + onPressed: searchController.clear, + tooltip: "Clear", + ), + ), ), ), body: body, diff --git a/lib/screens/search_options/tag_selection_screen.dart b/lib/screens/search_options/tag_selection_screen.dart index bf57e170..f2f40d5b 100644 --- a/lib/screens/search_options/tag_selection_screen.dart +++ b/lib/screens/search_options/tag_selection_screen.dart @@ -1,13 +1,15 @@ import "package:flutter/material.dart"; +import "package:jsdict/packages/tags.dart"; import "package:jsdict/providers/query_provider.dart"; import "package:jsdict/screens/search_options/search_options_screen.dart"; -import "package:jsdict/packages/tags.dart"; import "package:jsdict/widgets/info_chips.dart"; class TagSelectionScreen extends SearchOptionsScreen { const TagSelectionScreen({super.key}) : super( - body: const _TagSelection(), floatingActionButton: const _TagFAB()); + body: const _TagSelection(), + floatingActionButton: const _TagFAB(), + ); } class _TagFAB extends StatelessWidget { @@ -33,15 +35,21 @@ class _TagSelection extends StatelessWidget { return SingleChildScrollView( child: Column( children: wordTags.entries - .map((entry) => ListTile( - title: Text(entry.key), - subtitle: Wrap( - children: entry.value.entries - .map((tagEntry) => InfoChip(tagEntry.key, - onTap: () => queryProvider.addTag(tagEntry.value))) - .toList(), - ), - )) + .map( + (entry) => ListTile( + title: Text(entry.key), + subtitle: Wrap( + children: entry.value.entries + .map( + (tagEntry) => InfoChip( + tagEntry.key, + onTap: () => queryProvider.addTag(tagEntry.value), + ), + ) + .toList(), + ), + ), + ) .toList(), ), ); diff --git a/lib/screens/sentence_details_screen.dart b/lib/screens/sentence_details_screen.dart index f9bdbf99..feab5b29 100644 --- a/lib/screens/sentence_details_screen.dart +++ b/lib/screens/sentence_details_screen.dart @@ -1,11 +1,11 @@ import "package:flutter/material.dart"; import "package:jsdict/jp_text.dart"; -import "package:jsdict/widgets/copyable_furigana_text.dart"; -import "package:jsdict/widgets/link_popup.dart"; import "package:jsdict/models/models.dart"; import "package:jsdict/singletons.dart"; +import "package:jsdict/widgets/copyable_furigana_text.dart"; import "package:jsdict/widgets/copyright_text.dart"; import "package:jsdict/widgets/items/kanji_item.dart"; +import "package:jsdict/widgets/link_popup.dart"; import "package:jsdict/widgets/loader.dart"; class SentenceDetailsScreen extends StatelessWidget { @@ -35,7 +35,8 @@ class SentenceDetailsScreen extends StatelessWidget { ? _SentenceDetails(sentence!) : LoaderWidget( onLoad: () => getClient().sentenceDetails(sentenceId!), - handler: _SentenceDetails.new), + handler: _SentenceDetails.new, + ), ); } } @@ -53,27 +54,33 @@ class _SentenceDetails extends StatelessWidget { child: Column( children: [ Container( - alignment: Alignment.center, - padding: const EdgeInsets.all(12), - child: Column( - children: [ - CopyableFuriganaText(sentence.japanese, - style: const TextStyle(fontSize: 18).jp(), - rubyAlign: CrossAxisAlignment.start, - wrapAlign: TextAlign.start), - const SizedBox(height: 20), - SelectableText(sentence.english, - style: const TextStyle(fontSize: 18)), - const SizedBox(height: 20), - if (!sentence.isExample) CopyrightText(sentence.copyright!), - ], - )), - sentence.kanji != null - ? KanjiItemList(sentence.kanji!) - : LoaderWidget( - onLoad: () => - getClient().search(sentence.japanese.text), - handler: (response) => KanjiItemList(response.results)), + alignment: Alignment.center, + padding: const EdgeInsets.all(12), + child: Column( + children: [ + CopyableFuriganaText( + sentence.japanese, + style: const TextStyle(fontSize: 18).jp(), + rubyAlign: CrossAxisAlignment.start, + wrapAlign: TextAlign.start, + ), + const SizedBox(height: 20), + SelectableText( + sentence.english, + style: const TextStyle(fontSize: 18), + ), + const SizedBox(height: 20), + if (!sentence.isExample) CopyrightText(sentence.copyright!), + ], + ), + ), + if (sentence.kanji != null) + KanjiItemList(sentence.kanji!) + else + LoaderWidget( + onLoad: () => getClient().search(sentence.japanese.text), + handler: (response) => KanjiItemList(response.results), + ), ], ), ), diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index f521dfa9..ff0276b3 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -10,54 +10,60 @@ class SettingScreen extends StatelessWidget { @override Widget build(BuildContext context) { return DynamicColorBuilder( - builder: (dynamicColorScheme, _) => Scaffold( - appBar: AppBar(title: const Text("Settings")), - body: Column( - children: [ - ListTile( - leading: const Icon(Icons.water_drop, size: 32.0), - title: const Text("Theme"), - trailing: Consumer( - builder: (context, provider, _) { - return DropdownButton( - value: provider.currentThemeString, - items: ThemeProvider.themes - .map((theme) => DropdownMenuItem( - value: theme, child: Text(theme))) - .toList(), - onChanged: (value) => provider.setTheme(value!), - ); - }), - ), - if (dynamicColorScheme != null) - ListTile( - leading: const Icon(Icons.format_color_fill, size: 32.0), - title: const Text("Dynamic Colors"), - trailing: Consumer( - builder: (context, provider, _) => Switch( - value: provider.dynamicColors, - onChanged: provider.setDynamicColors, - ), - ), - ), - const Divider(), - ListTile( - onTap: () async { - final packageInfo = await PackageInfo.fromPlatform(); - - if (context.mounted) { - showAboutDialog( - context: context, - applicationVersion: packageInfo.version, - applicationLegalese: "Licensed under GPLv3.", - ); - } - }, - leading: const Icon(Icons.info, size: 32.0), - title: const Text("About"), + builder: (dynamicColorScheme, _) => Scaffold( + appBar: AppBar(title: const Text("Settings")), + body: Column( + children: [ + ListTile( + leading: const Icon(Icons.water_drop, size: 32.0), + title: const Text("Theme"), + trailing: Consumer( + builder: (context, provider, _) { + return DropdownButton( + value: provider.currentThemeString, + items: ThemeProvider.themes + .map( + (theme) => DropdownMenuItem( + value: theme, + child: Text(theme), + ), + ) + .toList(), + onChanged: (value) => provider.setTheme(value!), + ); + }, + ), + ), + if (dynamicColorScheme != null) + ListTile( + leading: const Icon(Icons.format_color_fill, size: 32.0), + title: const Text("Dynamic Colors"), + trailing: Consumer( + builder: (context, provider, _) => Switch( + value: provider.dynamicColors, + onChanged: provider.setDynamicColors, ), - ], + ), ), - )); + const Divider(), + ListTile( + onTap: () async { + final packageInfo = await PackageInfo.fromPlatform(); + + if (context.mounted) { + showAboutDialog( + context: context, + applicationVersion: packageInfo.version, + applicationLegalese: "Licensed under GPLv3.", + ); + } + }, + leading: const Icon(Icons.info, size: 32.0), + title: const Text("About"), + ), + ], + ), + ), + ); } } diff --git a/lib/screens/word_details/definition_tile.dart b/lib/screens/word_details/definition_tile.dart index 5794920f..ff1e0549 100644 --- a/lib/screens/word_details/definition_tile.dart +++ b/lib/screens/word_details/definition_tile.dart @@ -9,8 +9,12 @@ import "package:jsdict/widgets/entry_tile.dart"; import "package:jsdict/widgets/link_span.dart"; class DefinitionTile extends StatelessWidget { - const DefinitionTile(this.definition, - {super.key, this.textColor, this.isLast = false}); + const DefinitionTile( + this.definition, { + super.key, + this.textColor, + this.isLast = false, + }); final Definition definition; final Color? textColor; @@ -23,7 +27,9 @@ class DefinitionTile extends StatelessWidget { isLast: isLast, onTap: definition.exampleSentence != null ? pushScreen( - context, SentenceDetailsScreen(definition.exampleSentence!)) + context, + SentenceDetailsScreen(definition.exampleSentence!), + ) : null, title: Text(definition.meanings.join("; ")), subtitle: Column( @@ -39,21 +45,28 @@ class DefinitionTile extends StatelessWidget { } Widget seeAlsoText(BuildContext context, List words) { - return SelectableText.rich(TextSpan(children: [ - TextSpan(text: "See also ", style: TextStyle(color: textColor)), - ...words - .map((word) => LinkSpan( - context, - // remove reading - text: word.split(" ").first, - onTap: pushScreen( + return SelectableText.rich( + TextSpan( + children: [ + TextSpan(text: "See also ", style: TextStyle(color: textColor)), + ...words + .map( + (word) => LinkSpan( context, - WordDetailsScreen.search(word), + // remove reading + text: word.split(" ").first, + onTap: pushScreen( + context, + WordDetailsScreen.search(word), + ), ), - )) - .toList() - .intersperce( - TextSpan(text: ", ", style: TextStyle(color: textColor))), - ])); + ) + .toList() + .intersperce( + TextSpan(text: ", ", style: TextStyle(color: textColor)), + ), + ], + ), + ); } } diff --git a/lib/screens/word_details/inflection_table.dart b/lib/screens/word_details/inflection_table.dart index d3f5eb7a..d2801a58 100644 --- a/lib/screens/word_details/inflection_table.dart +++ b/lib/screens/word_details/inflection_table.dart @@ -1,8 +1,8 @@ import "package:flutter/material.dart"; import "package:jsdict/jp_text.dart"; +import "package:jsdict/models/models.dart"; import "package:jsdict/packages/furigana_ruby.dart"; import "package:jsdict/packages/inflection.dart"; -import "package:jsdict/models/models.dart"; import "package:ruby_text/ruby_text.dart"; class InflectionTable extends StatelessWidget { @@ -11,13 +11,17 @@ class InflectionTable extends StatelessWidget { final InflectionData data; Widget _headerCell([String? text]) => _cell( - Text(text ?? "", style: const TextStyle(fontWeight: FontWeight.w500))); + Text(text ?? "", style: const TextStyle(fontWeight: FontWeight.w500)), + ); Widget _furiganaCell(Furigana furigana) => _cell( furigana.hasFurigana ? RubyText(furigana.rubyData, wrapAlign: TextAlign.start) - : Text(furigana.text, - style: jpTextStyle, textAlign: TextAlign.start), + : Text( + furigana.text, + style: jpTextStyle, + textAlign: TextAlign.start, + ), ); Widget _cell(Widget child) => Padding( @@ -42,16 +46,22 @@ class InflectionTable extends StatelessWidget { }, defaultVerticalAlignment: TableCellVerticalAlignment.middle, children: [ - TableRow(children: [ - _headerCell(), - _headerCell("Affermative"), - _headerCell("Negative"), - ]), - ...data.toMap().entries.map((entry) => TableRow(children: [ - _headerCell(entry.key), - _furiganaCell(entry.value.affermative), - _furiganaCell(entry.value.negative), - ])), + TableRow( + children: [ + _headerCell(), + _headerCell("Affermative"), + _headerCell("Negative"), + ], + ), + ...data.toMap().entries.map( + (entry) => TableRow( + children: [ + _headerCell(entry.key), + _furiganaCell(entry.value.affermative), + _furiganaCell(entry.value.negative), + ], + ), + ), ], ), ); diff --git a/lib/screens/word_details/word_details_screen.dart b/lib/screens/word_details/word_details_screen.dart index 996b1029..0d0a4e92 100644 --- a/lib/screens/word_details/word_details_screen.dart +++ b/lib/screens/word_details/word_details_screen.dart @@ -2,20 +2,19 @@ import "package:audioplayers/audioplayers.dart"; import "package:expansion_tile_card/expansion_tile_card.dart"; import "package:flutter/material.dart"; import "package:jsdict/jp_text.dart"; +import "package:jsdict/models/models.dart"; import "package:jsdict/packages/list_extensions.dart"; import "package:jsdict/packages/navigation.dart"; +import "package:jsdict/screens/word_details/definition_tile.dart"; +import "package:jsdict/screens/word_details/inflection_table.dart"; +import "package:jsdict/singletons.dart"; import "package:jsdict/widgets/copyable_furigana_text.dart"; -import "package:jsdict/widgets/wikipedia.dart"; import "package:jsdict/widgets/entry_tile.dart"; -import "package:jsdict/models/models.dart"; -import "package:jsdict/singletons.dart"; import "package:jsdict/widgets/info_chips.dart"; import "package:jsdict/widgets/items/kanji_item.dart"; import "package:jsdict/widgets/link_popup.dart"; import "package:jsdict/widgets/loader.dart"; - -import "definition_tile.dart"; -import "inflection_table.dart"; +import "package:jsdict/widgets/wikipedia.dart"; class WordDetailsScreen extends StatelessWidget { WordDetailsScreen(String this.wordInput, {super.key}) @@ -67,13 +66,14 @@ class WordDetailsScreen extends StatelessWidget { ? IconButton( tooltip: "Play Audio", onPressed: () => AudioPlayer().play( - UrlSource(audioUrl!), - mode: PlayerMode.lowLatency, - ctx: AudioContextConfig( - focus: AudioContextConfigFocus.duckOthers, - ).build(), - ), - icon: const Icon(Icons.play_arrow)) + UrlSource(audioUrl!), + mode: PlayerMode.lowLatency, + ctx: AudioContextConfig( + focus: AudioContextConfigFocus.duckOthers, + ).build(), + ), + icon: const Icon(Icons.play_arrow), + ) : const SizedBox(), ), ValueListenableBuilder( @@ -126,11 +126,16 @@ class _WordContentWidget extends StatelessWidget { if (word.commonWord) const InfoChip("Common", color: Colors.green), if (word.jlptLevel != JLPTLevel.none) - InfoChip("JLPT ${word.jlptLevel.toString()}", - color: Colors.blue), - ...word.wanikaniLevels.map((wanikaniLevel) => InfoChip( + InfoChip( + "JLPT ${word.jlptLevel}", + color: Colors.blue, + ), + ...word.wanikaniLevels.map( + (wanikaniLevel) => InfoChip( "WaniKani Lv. $wanikaniLevel", - color: Colors.blue)), + color: Colors.blue, + ), + ), ], ), const SizedBox(height: 16), @@ -141,16 +146,19 @@ class _WordContentWidget extends StatelessWidget { title: const Text("Definitions"), children: [ SelectionArea( - child: Column( - children: word.definitions - .map((definition) => DefinitionTile( + child: Column( + children: word.definitions + .map( + (definition) => DefinitionTile( definition, textColor: textColor, isLast: definition == word.definitions.last, - )) - .toList() - .intersperce(const Divider(height: 0)), - )) + ), + ) + .toList() + .intersperce(const Divider(height: 0)), + ), + ), ], ), if (word.inflectionData != null) @@ -164,13 +172,17 @@ class _WordContentWidget extends StatelessWidget { shadowColor: shadowColor, title: const Text("Collocations"), children: word.collocations - .map((collocation) => EntryTile( - isLast: collocation == word.collocations.last, - title: JpText(collocation.word), - subtitle: Text(collocation.meaning), - onTap: pushScreen(context, - WordDetailsScreen.search(collocation.word)), - )) + .map( + (collocation) => EntryTile( + isLast: collocation == word.collocations.last, + title: JpText(collocation.word), + subtitle: Text(collocation.meaning), + onTap: pushScreen( + context, + WordDetailsScreen.search(collocation.word), + ), + ), + ) .toList() .intersperce(const Divider(height: 0)), ), @@ -181,23 +193,33 @@ class _WordContentWidget extends StatelessWidget { children: [ Container( padding: const EdgeInsets.symmetric( - horizontal: 12, vertical: 8), + horizontal: 12, + vertical: 8, + ), alignment: Alignment.centerLeft, child: Wrap( - alignment: WrapAlignment.start, - spacing: 2, - runSpacing: 8, - children: word.otherForms - .map((otherForm) => Container( - margin: const EdgeInsets.symmetric( - horizontal: 6), - child: CopyableFuriganaText([ - FuriganaPart( - otherForm.form, otherForm.reading) - ], style: const TextStyle(fontSize: 16)), - )) - .toList()), - ) + spacing: 2, + runSpacing: 8, + children: word.otherForms + .map( + (otherForm) => Container( + margin: const EdgeInsets.symmetric( + horizontal: 6, + ), + child: CopyableFuriganaText( + [ + FuriganaPart( + otherForm.form, + otherForm.reading, + ), + ], + style: const TextStyle(fontSize: 16), + ), + ), + ) + .toList(), + ), + ), ], ), if (word.notes.isNotEmpty) @@ -210,7 +232,9 @@ class _WordContentWidget extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.symmetric( - horizontal: 16, vertical: 8), + horizontal: 16, + vertical: 8, + ), child: JpText(word.notes.deduplicate().join("\n")), ), ], diff --git a/lib/singletons.dart b/lib/singletons.dart index c02651c2..039f7321 100644 --- a/lib/singletons.dart +++ b/lib/singletons.dart @@ -7,7 +7,8 @@ Future registerSingletons() { WidgetsFlutterBinding.ensureInitialized(); GetIt.I.registerLazySingleton(() => JishoClient()); GetIt.I.registerSingletonAsync( - () => SharedPreferences.getInstance()); + () => SharedPreferences.getInstance(), + ); return GetIt.I.allReady(); } diff --git a/lib/widgets/action_dialog.dart b/lib/widgets/action_dialog.dart index d3ccab8e..3c1f1094 100644 --- a/lib/widgets/action_dialog.dart +++ b/lib/widgets/action_dialog.dart @@ -21,7 +21,6 @@ class ActionDialog extends StatelessWidget { padding: const EdgeInsets.only(bottom: 8), child: Column( mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, children: actions, ), ); @@ -48,7 +47,9 @@ class ActionTile extends StatelessWidget { launchUrl(Uri.parse(data), mode: LaunchMode.externalApplication); void Function() pop( - BuildContext context, void Function(BuildContext context) callback) => + BuildContext context, + void Function(BuildContext context) callback, + ) => () { Navigator.of(context).pop(); callback(context); @@ -67,17 +68,20 @@ class ActionTile extends StatelessWidget { children: [ if (isURL) IconButton( - tooltip: "Open in Browser", - onPressed: pop(context, onOpenBrowser), - icon: const Icon(Icons.open_in_browser)), + tooltip: "Open in Browser", + onPressed: pop(context, onOpenBrowser), + icon: const Icon(Icons.open_in_browser), + ), IconButton( - tooltip: "Copy", - onPressed: pop(context, onCopy), - icon: const Icon(Icons.copy)), + tooltip: "Copy", + onPressed: pop(context, onCopy), + icon: const Icon(Icons.copy), + ), IconButton( - tooltip: "Share", - onPressed: pop(context, onShare), - icon: const Icon(Icons.share)), + tooltip: "Share", + onPressed: pop(context, onShare), + icon: const Icon(Icons.share), + ), ], ), ); diff --git a/lib/widgets/copyright_text.dart b/lib/widgets/copyright_text.dart index 2c1f859c..57384e3c 100644 --- a/lib/widgets/copyright_text.dart +++ b/lib/widgets/copyright_text.dart @@ -13,19 +13,22 @@ class CopyrightText extends StatelessWidget { final textColor = Theme.of(context).textTheme.bodyLarge!.color; return RichText( - text: TextSpan( - children: [ - TextSpan(text: "— ", style: TextStyle(color: textColor)), - LinkSpan( - context, - text: copyright.name, - underline: false, - onTap: () { - launchUrl(Uri.parse(copyright.url), - mode: LaunchMode.externalApplication); - }, - ), - ], - )); + text: TextSpan( + children: [ + TextSpan(text: "— ", style: TextStyle(color: textColor)), + LinkSpan( + context, + text: copyright.name, + underline: false, + onTap: () { + launchUrl( + Uri.parse(copyright.url), + mode: LaunchMode.externalApplication, + ); + }, + ), + ], + ), + ); } } diff --git a/lib/widgets/entry_tile.dart b/lib/widgets/entry_tile.dart index eca358c9..db33a9f4 100644 --- a/lib/widgets/entry_tile.dart +++ b/lib/widgets/entry_tile.dart @@ -1,13 +1,16 @@ import "package:flutter/material.dart"; /// Custom ListTile -/// +/// /// Adds a trailing right-arrow if [onTap] is not null. /// Rounds the bottom corners if [isLast] is true. class EntryTile extends ListTile { static const _roundedBottomBorder = RoundedRectangleBorder( - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(8), bottomRight: Radius.circular(8))); + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(8), + bottomRight: Radius.circular(8), + ), + ); const EntryTile({ super.key, @@ -45,7 +48,8 @@ class EntryTile extends ListTile { super.titleAlignment, bool isLast = false, }) : super( - trailing: - onTap != null ? const Icon(Icons.keyboard_arrow_right) : null, - shape: isLast ? _roundedBottomBorder : null); + trailing: + onTap != null ? const Icon(Icons.keyboard_arrow_right) : null, + shape: isLast ? _roundedBottomBorder : null, + ); } diff --git a/lib/widgets/error_indicator.dart b/lib/widgets/error_indicator.dart index 0f20dba8..edba79e5 100644 --- a/lib/widgets/error_indicator.dart +++ b/lib/widgets/error_indicator.dart @@ -55,11 +55,15 @@ class ErrorIndicator extends StatelessWidget { } } -void showErrorInfoDialog(BuildContext context, Object error, - {StackTrace? stackTrace}) => +void showErrorInfoDialog( + BuildContext context, + Object error, { + StackTrace? stackTrace, +}) => showDialog( - context: context, - builder: (context) => ErrorInfoDialog(error, stackTrace: stackTrace)); + context: context, + builder: (context) => ErrorInfoDialog(error, stackTrace: stackTrace), + ); class ErrorInfoDialog extends StatelessWidget { const ErrorInfoDialog(this.error, {super.key, this.stackTrace}); @@ -76,7 +80,9 @@ class ErrorInfoDialog extends StatelessWidget { style: style, children: [ TextSpan( - text: title, style: const TextStyle(fontWeight: FontWeight.bold)), + text: title, + style: const TextStyle(fontWeight: FontWeight.bold), + ), TextSpan(text: info), ], ), @@ -85,7 +91,7 @@ class ErrorInfoDialog extends StatelessWidget { void _copyError(BuildContext context) { final copyText = - "Type: $_errorType \nMessage: $_errorMessage \nStack trace:\n```\n${stackTrace.toString()}```"; + "Type: $_errorType \nMessage: $_errorMessage \nStack trace:\n```\n$stackTrace```"; Clipboard.setData(ClipboardData(text: copyText)).then((_) { if (!context.mounted) { diff --git a/lib/widgets/info_chips.dart b/lib/widgets/info_chips.dart index 761f8d39..a33447cc 100644 --- a/lib/widgets/info_chips.dart +++ b/lib/widgets/info_chips.dart @@ -15,12 +15,12 @@ class InfoChip extends StatelessWidget { @override Widget build(BuildContext context) { return DynamicColorBuilder( - builder: (dynamicColorScheme, _) => - Consumer(builder: (context, themeProvider, _) { - final dynamicColorsDisabled = - dynamicColorScheme == null || !themeProvider.dynamicColors; + builder: (dynamicColorScheme, _) => Consumer( + builder: (context, themeProvider, _) { + final dynamicColorsDisabled = + dynamicColorScheme == null || !themeProvider.dynamicColors; - return Card( + return Card( surfaceTintColor: dynamicColorsDisabled ? color : null, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(48)), @@ -40,8 +40,10 @@ class InfoChip extends StatelessWidget { ) : JpText(text), ), - )); - }), + ), + ); + }, + ), ); } } diff --git a/lib/widgets/items/item_card.dart b/lib/widgets/items/item_card.dart index 15da7706..13c5bfc6 100644 --- a/lib/widgets/items/item_card.dart +++ b/lib/widgets/items/item_card.dart @@ -1,8 +1,12 @@ import "package:flutter/material.dart"; class ItemCard extends StatelessWidget { - const ItemCard( - {super.key, required this.child, this.onTap, this.onLongPress}); + const ItemCard({ + super.key, + required this.child, + this.onTap, + this.onLongPress, + }); final Widget child; final Function()? onTap; diff --git a/lib/widgets/items/kanji_item.dart b/lib/widgets/items/kanji_item.dart index 4c85771f..e8e72111 100644 --- a/lib/widgets/items/kanji_item.dart +++ b/lib/widgets/items/kanji_item.dart @@ -5,8 +5,7 @@ import "package:jsdict/packages/navigation.dart"; import "package:jsdict/packages/string_util.dart"; import "package:jsdict/screens/kanji_details/kanji_details_screen.dart"; import "package:jsdict/widgets/action_dialog.dart"; - -import "item_card.dart"; +import "package:jsdict/widgets/items/item_card.dart"; class KanjiItem extends StatelessWidget { const KanjiItem({super.key, required this.kanji}); @@ -16,37 +15,44 @@ class KanjiItem extends StatelessWidget { @override Widget build(BuildContext context) { return ItemCard( - onTap: pushScreen(context, KanjiDetailsScreen(kanji)), - onLongPress: () => showActionDialog(context, [ - ActionTile.url(kanji.url), - ActionTile.text("Kanji", kanji.kanji), - ActionTile.text("Meanings", kanji.meanings.join(", ")), - ]), - child: ListTile( - contentPadding: - const EdgeInsets.symmetric(vertical: 8.0, horizontal: 22.0), - leading: JpText(kanji.kanji, style: const TextStyle(fontSize: 35)), - title: Text(kanji.meanings.join(", "), - style: - const TextStyle(fontSize: 15, fontWeight: FontWeight.w500)), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - if (kanji.kunReadings.isNotEmpty) - JpText( - "Kun: ${kanji.kunReadings.join("、$zeroWidthSpace").noBreak}"), - if (kanji.onReadings.isNotEmpty) - JpText( - "On: ${kanji.onReadings.join("、$zeroWidthSpace").noBreak}"), - JpText([ - "${kanji.strokeCount} strokes", - if (kanji.jlptLevel != JLPTLevel.none) - "JLPT ${kanji.jlptLevel}", - if (kanji.type != null) kanji.type.toString(), - ].join(", ")), - ], - ))); + onTap: pushScreen(context, KanjiDetailsScreen(kanji)), + onLongPress: () => showActionDialog(context, [ + ActionTile.url(kanji.url), + ActionTile.text("Kanji", kanji.kanji), + ActionTile.text("Meanings", kanji.meanings.join(", ")), + ]), + child: ListTile( + contentPadding: + const EdgeInsets.symmetric(vertical: 8.0, horizontal: 22.0), + leading: JpText(kanji.kanji, style: const TextStyle(fontSize: 35)), + title: Text( + kanji.meanings.join(", "), + style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + if (kanji.kunReadings.isNotEmpty) + JpText( + "Kun: ${kanji.kunReadings.join("、$zeroWidthSpace").noBreak}", + ), + if (kanji.onReadings.isNotEmpty) + JpText( + "On: ${kanji.onReadings.join("、$zeroWidthSpace").noBreak}", + ), + JpText( + [ + "${kanji.strokeCount} strokes", + if (kanji.jlptLevel != JLPTLevel.none) + "JLPT ${kanji.jlptLevel}", + if (kanji.type != null) kanji.type.toString(), + ].join(", "), + ), + ], + ), + ), + ); } } @@ -58,9 +64,10 @@ class KanjiItemList extends StatelessWidget { @override Widget build(BuildContext context) { return ListView.builder( - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: kanjiList.length, - itemBuilder: (_, index) => KanjiItem(kanji: kanjiList[index])); + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: kanjiList.length, + itemBuilder: (_, index) => KanjiItem(kanji: kanjiList[index]), + ); } } diff --git a/lib/widgets/items/name_item.dart b/lib/widgets/items/name_item.dart index e1e058a6..da5b567c 100644 --- a/lib/widgets/items/name_item.dart +++ b/lib/widgets/items/name_item.dart @@ -14,20 +14,22 @@ class NameItem extends StatelessWidget { @override Widget build(BuildContext context) { return ItemCard( - onTap: pushScreen(context, NameDetailsScreen(name)), - onLongPress: () => showActionDialog(context, [ - if (name.wikipediaWord != null) - ActionTile.url( - "https://jisho.org/word/${Uri.encodeComponent(name.wikipediaWord!)}"), - ActionTile.text("Name", name.japanese), - if (name.reading != null) - ActionTile.text("Reading", name.reading!), - ActionTile.text("English", name.english), - ]), - child: ListTile( - contentPadding: - const EdgeInsets.symmetric(vertical: 8.0, horizontal: 22.0), - title: JpText(name.toString()), - subtitle: Text(name.english))); + onTap: pushScreen(context, NameDetailsScreen(name)), + onLongPress: () => showActionDialog(context, [ + if (name.wikipediaWord != null) + ActionTile.url( + "https://jisho.org/word/${Uri.encodeComponent(name.wikipediaWord!)}", + ), + ActionTile.text("Name", name.japanese), + if (name.reading != null) ActionTile.text("Reading", name.reading!), + ActionTile.text("English", name.english), + ]), + child: ListTile( + contentPadding: + const EdgeInsets.symmetric(vertical: 8.0, horizontal: 22.0), + title: JpText(name.toString()), + subtitle: Text(name.english), + ), + ); } } diff --git a/lib/widgets/items/sentence_item.dart b/lib/widgets/items/sentence_item.dart index 8190ba67..aa1451cb 100644 --- a/lib/widgets/items/sentence_item.dart +++ b/lib/widgets/items/sentence_item.dart @@ -6,10 +6,9 @@ import "package:jsdict/packages/navigation.dart"; import "package:jsdict/screens/sentence_details_screen.dart"; import "package:jsdict/widgets/action_dialog.dart"; import "package:jsdict/widgets/copyright_text.dart"; +import "package:jsdict/widgets/items/item_card.dart"; import "package:ruby_text/ruby_text.dart"; -import "item_card.dart"; - class SentenceItem extends StatelessWidget { const SentenceItem({super.key, required this.sentence}); @@ -18,29 +17,33 @@ class SentenceItem extends StatelessWidget { @override Widget build(BuildContext context) { return ItemCard( - onTap: pushScreen(context, SentenceDetailsScreen(sentence)), - onLongPress: () => showActionDialog(context, [ - ActionTile.url(sentence.url), - ActionTile.text("Japanese", sentence.japanese.text), - ActionTile.text("English", sentence.english), - ]), - child: ListTile( - contentPadding: - const EdgeInsets.symmetric(vertical: 8.0, horizontal: 22.0), - title: RubyText(sentence.japanese.rubyData, - style: jpTextStyle, - rubyAlign: CrossAxisAlignment.start, - wrapAlign: TextAlign.start), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text(sentence.english), - if (sentence.copyright != null) ...[ - const SizedBox(height: 4), - CopyrightText(sentence.copyright!), - ] - ], - ))); + onTap: pushScreen(context, SentenceDetailsScreen(sentence)), + onLongPress: () => showActionDialog(context, [ + ActionTile.url(sentence.url), + ActionTile.text("Japanese", sentence.japanese.text), + ActionTile.text("English", sentence.english), + ]), + child: ListTile( + contentPadding: + const EdgeInsets.symmetric(vertical: 8.0, horizontal: 22.0), + title: RubyText( + sentence.japanese.rubyData, + style: jpTextStyle, + rubyAlign: CrossAxisAlignment.start, + wrapAlign: TextAlign.start, + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text(sentence.english), + if (sentence.copyright != null) ...[ + const SizedBox(height: 4), + CopyrightText(sentence.copyright!), + ], + ], + ), + ), + ); } } diff --git a/lib/widgets/items/word_item.dart b/lib/widgets/items/word_item.dart index a7028fa3..d0c8e63a 100644 --- a/lib/widgets/items/word_item.dart +++ b/lib/widgets/items/word_item.dart @@ -6,10 +6,9 @@ import "package:jsdict/packages/list_extensions.dart"; import "package:jsdict/packages/navigation.dart"; import "package:jsdict/screens/word_details/word_details_screen.dart"; import "package:jsdict/widgets/action_dialog.dart"; +import "package:jsdict/widgets/items/item_card.dart"; import "package:ruby_text/ruby_text.dart"; -import "item_card.dart"; - class WordItem extends StatelessWidget { const WordItem({super.key, required this.word}); @@ -28,32 +27,38 @@ class WordItem extends StatelessWidget { ActionTile.text("Reading", word.word.reading), ]), child: ListTile( - contentPadding: - const EdgeInsets.symmetric(vertical: 8.0, horizontal: 22.0), - title: RubyText( - word.word.rubyData, - style: const TextStyle(fontSize: 18).jp(), - rubyStyle: const TextStyle(fontSize: 10).jp(), - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: word.definitions - .map((e) => e.meanings.join("; ")) - .toList() - .deduplicateCaseInsensitive() - .asMap() - .entries - .map((entry) => RichText( - text: TextSpan(children: [ + contentPadding: + const EdgeInsets.symmetric(vertical: 8.0, horizontal: 22.0), + title: RubyText( + word.word.rubyData, + style: const TextStyle(fontSize: 18).jp(), + rubyStyle: const TextStyle(fontSize: 10).jp(), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: word.definitions + .map((e) => e.meanings.join("; ")) + .toList() + .deduplicateCaseInsensitive() + .asMap() + .entries + .map( + (entry) => RichText( + text: TextSpan( + children: [ TextSpan( - text: "${entry.key + 1}. ", - style: - textStyle.copyWith(fontWeight: FontWeight.w300)), + text: "${entry.key + 1}. ", + style: textStyle.copyWith(fontWeight: FontWeight.w300), + ), TextSpan(text: entry.value, style: textStyle), - ]))) - .toList(), - )), + ], + ), + ), + ) + .toList(), + ), + ), ); } } diff --git a/lib/widgets/link_popup.dart b/lib/widgets/link_popup.dart index 02ef8f06..d414e588 100644 --- a/lib/widgets/link_popup.dart +++ b/lib/widgets/link_popup.dart @@ -10,11 +10,15 @@ class LinkPopupButton extends StatelessWidget { Widget build(BuildContext context) { return PopupMenuButton( itemBuilder: (context) => data - .map((entry) => PopupMenuItem( - child: Text(entry.$1), - onTap: () => launchUrl(Uri.parse(entry.$2), - mode: LaunchMode.externalApplication), - )) + .map( + (entry) => PopupMenuItem( + child: Text(entry.$1), + onTap: () => launchUrl( + Uri.parse(entry.$2), + mode: LaunchMode.externalApplication, + ), + ), + ) .toList(), ); } diff --git a/lib/widgets/link_span.dart b/lib/widgets/link_span.dart index 0e7bd8b0..f8816c14 100644 --- a/lib/widgets/link_span.dart +++ b/lib/widgets/link_span.dart @@ -3,17 +3,19 @@ import "package:flutter/material.dart"; import "package:jsdict/jp_text.dart"; class LinkSpan extends TextSpan { - LinkSpan(BuildContext context, - {required String text, - required Function() onTap, - bool bold = false, - bool underline = true}) - : super( - text: text, - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - decoration: underline ? TextDecoration.underline : null, - fontWeight: bold ? FontWeight.w600 : null) - .jp(), - recognizer: TapGestureRecognizer()..onTap = onTap); + LinkSpan( + BuildContext context, { + required String text, + required Function() onTap, + bool bold = false, + bool underline = true, + }) : super( + text: text, + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + decoration: underline ? TextDecoration.underline : null, + fontWeight: bold ? FontWeight.w600 : null, + ).jp(), + recognizer: TapGestureRecognizer()..onTap = onTap, + ); } diff --git a/lib/widgets/loader.dart b/lib/widgets/loader.dart index 2a94af3e..9dfb6e9d 100644 --- a/lib/widgets/loader.dart +++ b/lib/widgets/loader.dart @@ -1,13 +1,14 @@ import "package:flutter/material.dart"; -import "error_indicator.dart"; +import "package:jsdict/widgets/error_indicator.dart"; class LoaderWidget extends StatefulWidget { - const LoaderWidget( - {super.key, - required this.onLoad, - required this.handler, - this.placeholder = const Text("")}); + const LoaderWidget({ + super.key, + required this.onLoad, + required this.handler, + this.placeholder = const Text(""), + }); final Future Function() onLoad; final Widget Function(T data) handler; @@ -35,33 +36,35 @@ class _LoaderWidgetState extends State> { @override Widget build(BuildContext context) { return FutureBuilder( - future: _future, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return Center( - child: Container( - margin: const EdgeInsets.all(20.0), - child: const CircularProgressIndicator()), - ); - } + future: _future, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Center( + child: Container( + margin: const EdgeInsets.all(20.0), + child: const CircularProgressIndicator(), + ), + ); + } - if (snapshot.connectionState == ConnectionState.none) { - return widget.placeholder; - } + if (snapshot.connectionState == ConnectionState.none) { + return widget.placeholder; + } - if (snapshot.hasError) { - return ErrorIndicator( - snapshot.error!, - stackTrace: snapshot.stackTrace, - onRetry: _retry, - ); - } + if (snapshot.hasError) { + return ErrorIndicator( + snapshot.error!, + stackTrace: snapshot.stackTrace, + onRetry: _retry, + ); + } - if (snapshot.hasData && snapshot.data != null) { - return widget.handler(snapshot.data as T); - } + if (snapshot.hasData && snapshot.data != null) { + return widget.handler(snapshot.data as T); + } - throw AssertionError(); - }); + throw AssertionError(); + }, + ); } } diff --git a/lib/widgets/wikipedia.dart b/lib/widgets/wikipedia.dart index 8a0f0b29..bfdd2db9 100644 --- a/lib/widgets/wikipedia.dart +++ b/lib/widgets/wikipedia.dart @@ -14,8 +14,9 @@ class WikipediaWidget extends StatelessWidget { child: Text(id), onPressed: () { launchUrl( - Uri.parse(page.url.replaceFirst(RegExp(r"[\?&]oldid=\d+"), "")), - mode: LaunchMode.externalApplication); + Uri.parse(page.url.replaceFirst(RegExp(r"[\?&]oldid=\d+"), "")), + mode: LaunchMode.externalApplication, + ); }, ), ); @@ -41,10 +42,16 @@ class WikipediaWidget extends StatelessWidget { children: [ if (wikipedia.wikipediaEnglish != null) link( - wikipedia.wikipediaEnglish!, "Wikipedia English", "EN"), + wikipedia.wikipediaEnglish!, + "Wikipedia English", + "EN", + ), if (wikipedia.wikipediaJapanese != null) - link(wikipedia.wikipediaJapanese!, "Wikipedia Japanese", - "JP"), + link( + wikipedia.wikipediaJapanese!, + "Wikipedia Japanese", + "JP", + ), if (wikipedia.dbpedia != null) link(wikipedia.dbpedia!, "DBpedia", "DB"), ], diff --git a/pubspec.lock b/pubspec.lock index cffc1b39..cd05b1c6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -469,6 +469,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + lint: + dependency: "direct main" + description: + name: lint + sha256: d758a5211fce7fd3f5e316f804daefecdc34c7e53559716125e6da7388ae8565 + url: "https://pub.dev" + source: hosted + version: "2.3.0" lints: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 5f2b96fc..65172d49 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,38 +8,37 @@ environment: sdk: '>=3.3.0 <4.0.0' dependencies: + app_links: ^6.3.2 + audioplayers: ^6.0.0 + collection: ^1.17.0 + dynamic_color: ^1.6.5 + expandable_text: ^2.3.0 + expansion_tile_card: ^3.0.0 flutter: sdk: flutter - + flutter_svg: ^2.0.9 + get_it: ^8.0.0 html: ^0.15.3 http: ^1.0.0 + infinite_scroll_pagination: ^4.0.0 + lint: ^2.3.0 + package_info_plus: ^8.0.2 + provider: ^6.0.5 + receive_sharing_intent: ^1.6.8 ruby_text: git: # fork adds extra alignment options url: https://github.com/petlyh/RubyText - ref: 492b148c9fddfbbc03658063efc76f51d1e851c1 - url_launcher: ^6.1.10 - get_it: ^8.0.0 - collection: ^1.17.0 - expansion_tile_card: ^3.0.0 - infinite_scroll_pagination: ^4.0.0 - provider: ^6.0.5 - expandable_text: ^2.3.0 - dynamic_color: ^1.6.5 + ref: 492b148c9fddfbbc03658063efc76f51d1e851c1 + share_plus: ^10.0.2 shared_preferences: ^2.1.2 - audioplayers: ^6.0.0 - package_info_plus: ^8.0.2 + url_launcher: ^6.1.10 xml: ^6.3.0 - flutter_svg: ^2.0.9 - share_plus: ^10.0.2 - receive_sharing_intent: ^1.6.8 - app_links: ^6.3.2 dev_dependencies: + flutter_launcher_icons: ^0.13.1 + flutter_lints: ^4.0.0 flutter_test: sdk: flutter - - flutter_lints: ^4.0.0 - flutter_launcher_icons: ^0.13.1 test: ^1.24.9 flutter_launcher_icons: diff --git a/test/client/kanji_test.dart b/test/client/kanji_test.dart index 94935c05..13db64ef 100644 --- a/test/client/kanji_test.dart +++ b/test/client/kanji_test.dart @@ -13,7 +13,7 @@ void main() { expect(kanji.kunReadings, ["ゆめ", "ゆめ.みる", "くら.い"]); expect(kanji.onReadings, ["ム", "ボウ"]); expect(kanji.type, isA()); - expect((kanji.type as Jouyou).grade, 5); + expect((kanji.type! as Jouyou).grade, 5); expect(kanji.strokeCount, 13); expect(kanji.jlptLevel, JLPTLevel.n3); diff --git a/test/client/search_meta_info_test.dart b/test/client/search_meta_info_test.dart index 78006c37..5f32b289 100644 --- a/test/client/search_meta_info_test.dart +++ b/test/client/search_meta_info_test.dart @@ -26,8 +26,10 @@ void main() { expect(response.grammarInfo, isNotNull); expect(response.grammarInfo!.word, "ありふれた"); expect(response.grammarInfo!.possibleInflectionOf, "ありふれる"); - expect(response.grammarInfo!.formInfos, - ["Ta-form. It indicates the past tense of the verb."]); + expect( + response.grammarInfo!.formInfos, + ["Ta-form. It indicates the past tense of the verb."], + ); }); test("year converion", () async { diff --git a/test/client/sentence_test.dart b/test/client/sentence_test.dart index a8fa2064..d9378747 100644 --- a/test/client/sentence_test.dart +++ b/test/client/sentence_test.dart @@ -12,13 +12,17 @@ void main() { final sentence = response.results.first; expect(sentence.japanese.text, equals("わたしは国の富が公平に分配される社会を夢見ている。")); - expect(sentence.english, - equals("I dream of a society whose wealth is distributed fairly.")); + expect( + sentence.english, + equals("I dream of a society whose wealth is distributed fairly."), + ); expect(sentence.copyright, isNotNull); expect(sentence.copyright?.name, equals("Tatoeba")); - expect(sentence.copyright?.url, - equals("http://tatoeba.org/eng/sentences/show/76360")); + expect( + sentence.copyright?.url, + equals("http://tatoeba.org/eng/sentences/show/76360"), + ); }); test("sentence details", () async { @@ -27,16 +31,20 @@ void main() { expect(sentence.japanese.text, equals("ここ数年は芸術鑑賞が趣味で、暇を見つけては美術館に足を運んでいる。")); expect( - sentence.english, - equals( - "For the past few years, I have pursued my appreciation of art by visiting art museums whenever I find spare time.")); + sentence.english, + equals( + "For the past few years, I have pursued my appreciation of art by visiting art museums whenever I find spare time.", + ), + ); expect(sentence.copyright, isNotNull); expect(sentence.copyright?.name, equals("Jreibun")); expect( - sentence.copyright?.url, - equals( - "http://www.tufs.ac.jp/ts/personal/SUZUKI_Tomomi/jreibun/index-jreibun.html")); + sentence.copyright?.url, + equals( + "http://www.tufs.ac.jp/ts/personal/SUZUKI_Tomomi/jreibun/index-jreibun.html", + ), + ); expect(sentence.kanji, isNotNull); expect(sentence.kanji, hasLength(14)); diff --git a/test/client/word_test.dart b/test/client/word_test.dart index ebbb5ead..7739dfc7 100644 --- a/test/client/word_test.dart +++ b/test/client/word_test.dart @@ -22,8 +22,10 @@ void main() { expect(word.collocations, hasLength(16)); expect(word.collocations.first.word, "見るに堪えない"); - expect(word.collocations.first.meaning, - "so miserable that it is painful to look at"); + expect( + word.collocations.first.meaning, + "so miserable that it is painful to look at", + ); expect(word.definitions, hasLength(6)); expect(word.definitions.first.meanings, contains("to observe")); @@ -49,7 +51,9 @@ void main() { expect(sentence, isNotNull); expect(sentence?.japanese.text, equals("彼は池で溺れそうになっている子どもを助けた。")); expect( - sentence?.english, equals("He saved a child from drowning in a pond.")); + sentence?.english, + equals("He saved a child from drowning in a pond."), + ); }); test("notes", () async { diff --git a/test/furigana_test.dart b/test/furigana_test.dart index 730bddd3..85c54ed5 100644 --- a/test/furigana_test.dart +++ b/test/furigana_test.dart @@ -3,7 +3,10 @@ import "package:jsdict/packages/jisho_client/jisho_client.dart"; import "package:test/test.dart"; Future Function() _furiganaTest( - String wordId, String text, String reading) { + String wordId, + String text, + String reading, +) { return () async { final wordDetails = await JishoClient().wordDetails(wordId); expect(wordDetails.word.text, text); diff --git a/test/inflection_test.dart b/test/inflection_test.dart index 14ed4c9c..26295598 100644 --- a/test/inflection_test.dart +++ b/test/inflection_test.dart @@ -20,224 +20,239 @@ void main() { }); test( - "I-adjective", - () => _testStringData("寒い", "adj-i", { - "Non-past": ("寒い", "寒くない"), - "Past": ("寒かった", "寒くなかった"), - })); + "I-adjective", + () => _testStringData("寒い", "adj-i", { + "Non-past": ("寒い", "寒くない"), + "Past": ("寒かった", "寒くなかった"), + }), + ); test( - "Ichidan verb", - () => _testStringData("上げる", "v1", { - "Non-past": ("上げる", "上げない"), - "Non-past, polite": ("上げます", "上げません"), - "Past": ("上げた", "上げなかった"), - "Past, polite": ("上げました", "上げませんでした"), - "Te-form": ("上げて", "上げなくて"), - "Potential": ("上げられる", "上げられない"), - "Passive": ("上げられる", "上げられない"), - "Causative": ("上げさせる", "上げさせない"), - "Causative, Passive": ("上げさせられる", "上げさせられない"), - "Imperative": ("上げろ", "上げるな"), - })); + "Ichidan verb", + () => _testStringData("上げる", "v1", { + "Non-past": ("上げる", "上げない"), + "Non-past, polite": ("上げます", "上げません"), + "Past": ("上げた", "上げなかった"), + "Past, polite": ("上げました", "上げませんでした"), + "Te-form": ("上げて", "上げなくて"), + "Potential": ("上げられる", "上げられない"), + "Passive": ("上げられる", "上げられない"), + "Causative": ("上げさせる", "上げさせない"), + "Causative, Passive": ("上げさせられる", "上げさせられない"), + "Imperative": ("上げろ", "上げるな"), + }), + ); test( - "Godan verb with u ending", - () => _testStringData("歌う", "v5u", { - "Non-past": ("歌う", "歌わない"), - "Non-past, polite": ("歌います", "歌いません"), - "Past": ("歌った", "歌わなかった"), - "Past, polite": ("歌いました", "歌いませんでした"), - "Te-form": ("歌って", "歌わなくて"), - "Potential": ("歌える", "歌えない"), - "Passive": ("歌われる", "歌われない"), - "Causative": ("歌わせる", "歌わせない"), - "Causative, Passive": ("歌わせられる", "歌わせられない"), - "Imperative": ("歌え", "歌うな"), - })); + "Godan verb with u ending", + () => _testStringData("歌う", "v5u", { + "Non-past": ("歌う", "歌わない"), + "Non-past, polite": ("歌います", "歌いません"), + "Past": ("歌った", "歌わなかった"), + "Past, polite": ("歌いました", "歌いませんでした"), + "Te-form": ("歌って", "歌わなくて"), + "Potential": ("歌える", "歌えない"), + "Passive": ("歌われる", "歌われない"), + "Causative": ("歌わせる", "歌わせない"), + "Causative, Passive": ("歌わせられる", "歌わせられない"), + "Imperative": ("歌え", "歌うな"), + }), + ); test( - "Godan verb with mu ending", - () => _testStringData("占む", "v5m", { - "Non-past": ("占む", "占まない"), - "Non-past, polite": ("占みます", "占みません"), - "Past": ("占んだ", "占まなかった"), - "Past, polite": ("占みました", "占みませんでした"), - "Te-form": ("占んで", "占まなくて"), - "Potential": ("占める", "占めない"), - "Passive": ("占まれる", "占まれない"), - "Causative": ("占ませる", "占ませない"), - "Causative, Passive": ("占ませられる", "占ませられない"), - "Imperative": ("占め", "占むな"), - })); + "Godan verb with mu ending", + () => _testStringData("占む", "v5m", { + "Non-past": ("占む", "占まない"), + "Non-past, polite": ("占みます", "占みません"), + "Past": ("占んだ", "占まなかった"), + "Past, polite": ("占みました", "占みませんでした"), + "Te-form": ("占んで", "占まなくて"), + "Potential": ("占める", "占めない"), + "Passive": ("占まれる", "占まれない"), + "Causative": ("占ませる", "占ませない"), + "Causative, Passive": ("占ませられる", "占ませられない"), + "Imperative": ("占め", "占むな"), + }), + ); test( - "Godan verb with su ending", - () => _testStringData("渡す", "v5s", { - "Non-past": ("渡す", "渡さない"), - "Non-past, polite": ("渡します", "渡しません"), - "Past": ("渡した", "渡さなかった"), - "Past, polite": ("渡しました", "渡しませんでした"), - "Te-form": ("渡して", "渡さなくて"), - "Potential": ("渡せる", "渡せない"), - "Passive": ("渡される", "渡されない"), - "Causative": ("渡させる", "渡させない"), - "Causative, Passive": ("渡させられる", "渡させられない"), - "Imperative": ("渡せ", "渡すな"), - })); + "Godan verb with su ending", + () => _testStringData("渡す", "v5s", { + "Non-past": ("渡す", "渡さない"), + "Non-past, polite": ("渡します", "渡しません"), + "Past": ("渡した", "渡さなかった"), + "Past, polite": ("渡しました", "渡しませんでした"), + "Te-form": ("渡して", "渡さなくて"), + "Potential": ("渡せる", "渡せない"), + "Passive": ("渡される", "渡されない"), + "Causative": ("渡させる", "渡させない"), + "Causative, Passive": ("渡させられる", "渡させられない"), + "Imperative": ("渡せ", "渡すな"), + }), + ); test( - "Godan verb with tsu ending", - () => _testStringData("待つ", "v5t", { - "Non-past": ("待つ", "待たない"), - "Non-past, polite": ("待ちます", "待ちません"), - "Past": ("待った", "待たなかった"), - "Past, polite": ("待ちました", "待ちませんでした"), - "Te-form": ("待って", "待たなくて"), - "Potential": ("待てる", "待てない"), - "Passive": ("待たれる", "待たれない"), - "Causative": ("待たせる", "待たせない"), - "Causative, Passive": ("待たせられる", "待たせられない"), - "Imperative": ("待て", "待つな"), - })); + "Godan verb with tsu ending", + () => _testStringData("待つ", "v5t", { + "Non-past": ("待つ", "待たない"), + "Non-past, polite": ("待ちます", "待ちません"), + "Past": ("待った", "待たなかった"), + "Past, polite": ("待ちました", "待ちませんでした"), + "Te-form": ("待って", "待たなくて"), + "Potential": ("待てる", "待てない"), + "Passive": ("待たれる", "待たれない"), + "Causative": ("待たせる", "待たせない"), + "Causative, Passive": ("待たせられる", "待たせられない"), + "Imperative": ("待て", "待つな"), + }), + ); test( - "Godan verb with nu ending", - () => _testStringData("死ぬ", "v5n", { - "Non-past": ("死ぬ", "死なない"), - "Non-past, polite": ("死にます", "死にません"), - "Past": ("死んだ", "死ななかった"), - "Past, polite": ("死にました", "死にませんでした"), - "Te-form": ("死んで", "死ななくて"), - "Potential": ("死ねる", "死ねない"), - "Passive": ("死なれる", "死なれない"), - "Causative": ("死なせる", "死なせない"), - "Causative, Passive": ("死なせられる", "死なせられない"), - "Imperative": ("死ね", "死ぬな"), - })); + "Godan verb with nu ending", + () => _testStringData("死ぬ", "v5n", { + "Non-past": ("死ぬ", "死なない"), + "Non-past, polite": ("死にます", "死にません"), + "Past": ("死んだ", "死ななかった"), + "Past, polite": ("死にました", "死にませんでした"), + "Te-form": ("死んで", "死ななくて"), + "Potential": ("死ねる", "死ねない"), + "Passive": ("死なれる", "死なれない"), + "Causative": ("死なせる", "死なせない"), + "Causative, Passive": ("死なせられる", "死なせられない"), + "Imperative": ("死ね", "死ぬな"), + }), + ); test( - "Godan verb with ru ending", - () => _testStringData("巡る", "v5r", { - "Non-past": ("巡る", "巡らない"), - "Non-past, polite": ("巡ります", "巡りません"), - "Past": ("巡った", "巡らなかった"), - "Past, polite": ("巡りました", "巡りませんでした"), - "Te-form": ("巡って", "巡らなくて"), - "Potential": ("巡れる", "巡れない"), - "Passive": ("巡られる", "巡られない"), - "Causative": ("巡らせる", "巡らせない"), - "Causative, Passive": ("巡らせられる", "巡らせられない"), - "Imperative": ("巡れ", "巡るな"), - })); + "Godan verb with ru ending", + () => _testStringData("巡る", "v5r", { + "Non-past": ("巡る", "巡らない"), + "Non-past, polite": ("巡ります", "巡りません"), + "Past": ("巡った", "巡らなかった"), + "Past, polite": ("巡りました", "巡りませんでした"), + "Te-form": ("巡って", "巡らなくて"), + "Potential": ("巡れる", "巡れない"), + "Passive": ("巡られる", "巡られない"), + "Causative": ("巡らせる", "巡らせない"), + "Causative, Passive": ("巡らせられる", "巡らせられない"), + "Imperative": ("巡れ", "巡るな"), + }), + ); test( - "Godan verb with bu ending", - () => _testStringData("遊ぶ", "v5b", { - "Non-past": ("遊ぶ", "遊ばない"), - "Non-past, polite": ("遊びます", "遊びません"), - "Past": ("遊んだ", "遊ばなかった"), - "Past, polite": ("遊びました", "遊びませんでした"), - "Te-form": ("遊んで", "遊ばなくて"), - "Potential": ("遊べる", "遊べない"), - "Passive": ("遊ばれる", "遊ばれない"), - "Causative": ("遊ばせる", "遊ばせない"), - "Causative, Passive": ("遊ばせられる", "遊ばせられない"), - "Imperative": ("遊べ", "遊ぶな"), - })); + "Godan verb with bu ending", + () => _testStringData("遊ぶ", "v5b", { + "Non-past": ("遊ぶ", "遊ばない"), + "Non-past, polite": ("遊びます", "遊びません"), + "Past": ("遊んだ", "遊ばなかった"), + "Past, polite": ("遊びました", "遊びませんでした"), + "Te-form": ("遊んで", "遊ばなくて"), + "Potential": ("遊べる", "遊べない"), + "Passive": ("遊ばれる", "遊ばれない"), + "Causative": ("遊ばせる", "遊ばせない"), + "Causative, Passive": ("遊ばせられる", "遊ばせられない"), + "Imperative": ("遊べ", "遊ぶな"), + }), + ); test( - "Godan verb with ku ending", - () => _testStringData("焼く", "v5k", { - "Non-past": ("焼く", "焼かない"), - "Non-past, polite": ("焼きます", "焼きません"), - "Past": ("焼いた", "焼かなかった"), - "Past, polite": ("焼きました", "焼きませんでした"), - "Te-form": ("焼いて", "焼かなくて"), - "Potential": ("焼ける", "焼けない"), - "Passive": ("焼かれる", "焼かれない"), - "Causative": ("焼かせる", "焼かせない"), - "Causative, Passive": ("焼かせられる", "焼かせられない"), - "Imperative": ("焼け", "焼くな"), - })); + "Godan verb with ku ending", + () => _testStringData("焼く", "v5k", { + "Non-past": ("焼く", "焼かない"), + "Non-past, polite": ("焼きます", "焼きません"), + "Past": ("焼いた", "焼かなかった"), + "Past, polite": ("焼きました", "焼きませんでした"), + "Te-form": ("焼いて", "焼かなくて"), + "Potential": ("焼ける", "焼けない"), + "Passive": ("焼かれる", "焼かれない"), + "Causative": ("焼かせる", "焼かせない"), + "Causative, Passive": ("焼かせられる", "焼かせられない"), + "Imperative": ("焼け", "焼くな"), + }), + ); test( - "Godan verb with gu ending", - () => _testStringData("泳ぐ", "v5g", { - "Non-past": ("泳ぐ", "泳がない"), - "Non-past, polite": ("泳ぎます", "泳ぎません"), - "Past": ("泳いだ", "泳がなかった"), - "Past, polite": ("泳ぎました", "泳ぎませんでした"), - "Te-form": ("泳いで", "泳がなくて"), - "Potential": ("泳げる", "泳げない"), - "Passive": ("泳がれる", "泳がれない"), - "Causative": ("泳がせる", "泳がせない"), - "Causative, Passive": ("泳がせられる", "泳がせられない"), - "Imperative": ("泳げ", "泳ぐな"), - })); + "Godan verb with gu ending", + () => _testStringData("泳ぐ", "v5g", { + "Non-past": ("泳ぐ", "泳がない"), + "Non-past, polite": ("泳ぎます", "泳ぎません"), + "Past": ("泳いだ", "泳がなかった"), + "Past, polite": ("泳ぎました", "泳ぎませんでした"), + "Te-form": ("泳いで", "泳がなくて"), + "Potential": ("泳げる", "泳げない"), + "Passive": ("泳がれる", "泳がれない"), + "Causative": ("泳がせる", "泳がせない"), + "Causative, Passive": ("泳がせられる", "泳がせられない"), + "Imperative": ("泳げ", "泳ぐな"), + }), + ); test( - "Godan verb - Iku/Yuku special class", - () => _testStringData("行く", "v5k-s", { - "Non-past": ("行く", "行かない"), - "Non-past, polite": ("行きます", "行きません"), - "Past": ("行った", "行かなかった"), - "Past, polite": ("行きました", "行きませんでした"), - "Te-form": ("行って", "行かなくて"), - "Potential": ("行ける", "行けない"), - "Passive": ("行かれる", "行かれない"), - "Causative": ("行かせる", "行かせない"), - "Causative, Passive": ("行かせられる", "行かせられない"), - "Imperative": ("行け", "行くな"), - })); + "Godan verb - Iku/Yuku special class", + () => _testStringData("行く", "v5k-s", { + "Non-past": ("行く", "行かない"), + "Non-past, polite": ("行きます", "行きません"), + "Past": ("行った", "行かなかった"), + "Past, polite": ("行きました", "行きませんでした"), + "Te-form": ("行って", "行かなくて"), + "Potential": ("行ける", "行けない"), + "Passive": ("行かれる", "行かれない"), + "Causative": ("行かせる", "行かせない"), + "Causative, Passive": ("行かせられる", "行かせられない"), + "Imperative": ("行け", "行くな"), + }), + ); test( - "Kuru verb", - () => _testFuriganaData("来る", "vk", { - "Non-past": ([("来", "く"), "る"], [("来", "こ"), "ない"]), - "Non-past, polite": ([("来", "き"), "ます"], [("来", "き"), "ません"]), - "Past": ([("来", "き"), "た"], [("来", "こ"), "なかった"]), - "Past, polite": ([("来", "き"), "ました"], [("来", "き"), "ませんでした"]), - "Te-form": ([("来", "き"), "て"], [("来", "こ"), "なくて"]), - "Potential": ([("来", "こ"), "られる"], [("来", "こ"), "られない"]), - "Passive": ([("来", "こ"), "られる"], [("来", "こ"), "られない"]), - "Causative": ([("来", "こ"), "させる"], [("来", "こ"), "させない"]), - "Causative, Passive": ( - [("来", "こ"), "させられる"], - [("来", "こ"), "させられない"], - ), - "Imperative": ([("来", "こ"), "い"], [("来", "く"), "るな"]), - })); + "Kuru verb", + () => _testFuriganaData("来る", "vk", { + "Non-past": ([("来", "く"), "る"], [("来", "こ"), "ない"]), + "Non-past, polite": ([("来", "き"), "ます"], [("来", "き"), "ません"]), + "Past": ([("来", "き"), "た"], [("来", "こ"), "なかった"]), + "Past, polite": ([("来", "き"), "ました"], [("来", "き"), "ませんでした"]), + "Te-form": ([("来", "き"), "て"], [("来", "こ"), "なくて"]), + "Potential": ([("来", "こ"), "られる"], [("来", "こ"), "られない"]), + "Passive": ([("来", "こ"), "られる"], [("来", "こ"), "られない"]), + "Causative": ([("来", "こ"), "させる"], [("来", "こ"), "させない"]), + "Causative, Passive": ( + [("来", "こ"), "させられる"], + [("来", "こ"), "させられない"], + ), + "Imperative": ([("来", "こ"), "い"], [("来", "く"), "るな"]), + }), + ); test( - "Suru special class", - () => _testFuriganaData("為る", "vs-i", { - "Non-past": ([("為", "す"), "る"], [("為", "し"), "ない"]), - "Past": ([("為", "し"), "た"], [("為", "し"), "なかった"]), - "Non-past, polite": ([("為", "し"), "ます"], [("為", "し"), "ません"]), - "Past, polite": ([("為", "し"), "ました"], [("為", "し"), "ませんでした"]), - "Te-form": ([("為", "し"), "て"], [("為", "し"), "なくて"]), - "Potential": (["できる"], ["できない"]), - "Passive": ([("為", "さ"), "れる"], [("為", "さ"), "れない"]), - "Causative": ([("為", "さ"), "せる"], [("為", "さ"), "せない"]), - "Causative, Passive": ([("為", "さ"), "せられる"], [("為", "さ"), "せられない"]), - "Imperative": ([("為", "し"), "ろ"], [("為", "す"), "るな"]), - })); + "Suru special class", + () => _testFuriganaData("為る", "vs-i", { + "Non-past": ([("為", "す"), "る"], [("為", "し"), "ない"]), + "Past": ([("為", "し"), "た"], [("為", "し"), "なかった"]), + "Non-past, polite": ([("為", "し"), "ます"], [("為", "し"), "ません"]), + "Past, polite": ([("為", "し"), "ました"], [("為", "し"), "ませんでした"]), + "Te-form": ([("為", "し"), "て"], [("為", "し"), "なくて"]), + "Potential": (["できる"], ["できない"]), + "Passive": ([("為", "さ"), "れる"], [("為", "さ"), "れない"]), + "Causative": ([("為", "さ"), "せる"], [("為", "さ"), "せない"]), + "Causative, Passive": ([("為", "さ"), "せられる"], [("為", "さ"), "せられない"]), + "Imperative": ([("為", "し"), "ろ"], [("為", "す"), "るな"]), + }), + ); test( - "Suru verb", - () => _testStringData("恋する", "vs-i", { - "Non-past": ("恋する", "恋しない"), - "Past": ("恋した", "恋しなかった"), - "Non-past, polite": ("恋します", "恋しません"), - "Past, polite": ("恋しました", "恋しませんでした"), - "Te-form": ("恋して", "恋しなくて"), - "Potential": ("恋できる", "恋できない"), - "Passive": ("恋される", "恋されない"), - "Causative": ("恋させる", "恋させない"), - "Causative, Passive": ("恋させられる", "恋させられない"), - "Imperative": ("恋しろ", "恋するな"), - })); + "Suru verb", + () => _testStringData("恋する", "vs-i", { + "Non-past": ("恋する", "恋しない"), + "Past": ("恋した", "恋しなかった"), + "Non-past, polite": ("恋します", "恋しません"), + "Past, polite": ("恋しました", "恋しませんでした"), + "Te-form": ("恋して", "恋しなくて"), + "Potential": ("恋できる", "恋できない"), + "Passive": ("恋される", "恋されない"), + "Causative": ("恋させる", "恋させない"), + "Causative, Passive": ("恋させられる", "恋させられない"), + "Imperative": ("恋しろ", "恋するな"), + }), + ); } void _testCodeName(String word, String code, String expectedName) => @@ -249,17 +264,25 @@ void _testStringData( Map expected, ) => _testData( - word, - code, - expected.map((key, value) => MapEntry(key, ( - affermative: value.$1.furigana, - negative: value.$2.furigana, - )))); + word, + code, + expected.map( + (key, value) => MapEntry( + key, + ( + affermative: value.$1.furigana, + negative: value.$2.furigana, + ), + ), + ), + ); Furigana _furigana(Iterable input) => input - .map((e) => e is (String, String) - ? FuriganaPart(e.$1, e.$2) - : FuriganaPart.textOnly(e.toString())) + .map( + (e) => e is (String, String) + ? FuriganaPart(e.$1, e.$2) + : FuriganaPart.textOnly(e.toString()), + ) .toList(); void _testFuriganaData( @@ -268,14 +291,18 @@ void _testFuriganaData( Map, Iterable)> expected, ) => _testData( - word, - code, - expected.map( - (key, value) => MapEntry(key, ( + word, + code, + expected.map( + (key, value) => MapEntry( + key, + ( affermative: _furigana(value.$1), negative: _furigana(value.$2), - )), - )); + ), + ), + ), + ); void _testData( String word, @@ -288,8 +315,10 @@ void _testData( for (final entry in expected.entries) { expect( - actual, containsPair(entry.key, _InflectionEntryMatcher(entry.value)), - reason: entry.key); + actual, + containsPair(entry.key, _InflectionEntryMatcher(entry.value)), + reason: entry.key, + ); } } @@ -299,7 +328,7 @@ class _InflectionEntryMatcher extends Matcher { const _InflectionEntryMatcher(this.expected); @override - bool matches(item, Map matchState) { + bool matches(dynamic item, Map matchState) { if (item is! InflectionEntry) { return false; } @@ -320,7 +349,11 @@ class _InflectionEntryMatcher extends Matcher { description.add(expected.toString()); @override - Description describeMismatch(_, Description mismatchDescription, - Map matchState, bool __) => + Description describeMismatch( + _, + Description mismatchDescription, + Map matchState, + bool __, + ) => mismatchDescription.add(matchState["actual"] as String); }