feat(app): the dictionary in the app: look words up, add them as cards
A studyset's dictionary follows from its language, as decided: the
instance's dictionary of its front's language, with meanings in its
back's (else English). Nothing to attach or detach; nothing of it shows
where there is none.
- Add material › From the dictionary: search a word (kanji, kana,
romaji, meaning) and add it as a flashcard, or add a whole level
(JLPT N5…N1, common words) at once, asked first above 500 words, its
progress shown. Words already in the studyset show as such and are
left out. Each card is a vocabulary card (headword, reading, senses
with their codes), tagged `jlpt-n5` and with its word class, and
marked as imported from the dictionary.
- Look up beside a vocabulary card's front fills the card with the word
chosen, keeping its hints, explanation and tags.
- A card keeps the dictionary entry it was made from (card schema 6:
`dictionary` {source, id}), to be found again and not added twice.
- The dictionary's credits show at its foot and in About, as its licence
asks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 01:33:12 +02:00
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
|
|
import 'package:halkyon_learn_flutter/src/core/content/rich_content.dart';
|
|
|
|
|
|
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
|
|
|
|
|
|
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
|
feat(app): kanji and kana in the dictionary, beside the words
Where the instance holds the characters of a studyset's language, the
dictionary screen has a tab for them, Kanji & kana: search by character,
reading (kana or romaji) or meaning, and whole levels to add, under their
names: the kana (hiragana, katakana), the JLPT levels and the school
grades. A character becomes a character card, its meanings (a kana's
romaji) and readings, tagged with its level (JLPT-N5), school grade
(Grade 1 … Grade 6, Secondary school) and script (Hiragana, Katakana). A
character card's Look up searches the characters, and an entry looked up
again on a card swaps the tags the one before brought, as words do.
A dictionary's entries are now words or characters (DictionaryItem), as
its kind says.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 15:19:22 +02:00
|
|
|
|
import 'package:halkyon_learn_flutter/src/feature/card/model/card_content.dart';
|
feat(app): stroke order for kanji and kana, from KanjiVG
KanjiVG's strokes come with the app (assets/kanjivg/, CC BY-SA 3.0, its
notice in the licence list): 94 gzipped files, one per 256 code points,
2.7 MB in all, a file read the first time a character of it is asked for
(StrokeRepository, app-wide beside the maps). The strokes are SVG path
data, read with path_parsing (the Flutter team's, pure Dart, already in
the build through flutter_svg), so the model can flatten them into points
without dart:ui, as checking handwriting will.
A character's sheet traces its strokes in order over those to come, with
its stroke count, meanings, readings, level and the components KanjiVG
splits it into, each a tap away. It opens from a studied character's
answer (Stroke order) and from a character found in the dictionary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 15:35:00 +02:00
|
|
|
|
import 'package:halkyon_learn_flutter/src/feature/kanji/data/stroke_repository.dart';
|
feat(app): the dictionary in the app: look words up, add them as cards
A studyset's dictionary follows from its language, as decided: the
instance's dictionary of its front's language, with meanings in its
back's (else English). Nothing to attach or detach; nothing of it shows
where there is none.
- Add material › From the dictionary: search a word (kanji, kana,
romaji, meaning) and add it as a flashcard, or add a whole level
(JLPT N5…N1, common words) at once, asked first above 500 words, its
progress shown. Words already in the studyset show as such and are
left out. Each card is a vocabulary card (headword, reading, senses
with their codes), tagged `jlpt-n5` and with its word class, and
marked as imported from the dictionary.
- Look up beside a vocabulary card's front fills the card with the word
chosen, keeping its hints, explanation and tags.
- A card keeps the dictionary entry it was made from (card schema 6:
`dictionary` {source, id}), to be found again and not added twice.
- The dictionary's credits show at its foot and in About, as its licence
asks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 01:33:12 +02:00
|
|
|
|
import 'package:halkyon_learn_flutter/src/feature/studyset/data/studyset_payload.dart';
|
|
|
|
|
|
import 'package:halkyon_learn_flutter/src/feature/studyset/model/studyset_content.dart';
|
|
|
|
|
|
import 'package:material_ui/material_ui.dart';
|
|
|
|
|
|
|
|
|
|
|
|
import '../../support/fake_card_gateway.dart';
|
|
|
|
|
|
import '../../support/fake_dictionary_gateway.dart';
|
|
|
|
|
|
import '../../support/fake_studyset_gateway.dart';
|
feat(app): stroke order for kanji and kana, from KanjiVG
KanjiVG's strokes come with the app (assets/kanjivg/, CC BY-SA 3.0, its
notice in the licence list): 94 gzipped files, one per 256 code points,
2.7 MB in all, a file read the first time a character of it is asked for
(StrokeRepository, app-wide beside the maps). The strokes are SVG path
data, read with path_parsing (the Flutter team's, pure Dart, already in
the build through flutter_svg), so the model can flatten them into points
without dart:ui, as checking handwriting will.
A character's sheet traces its strokes in order over those to come, with
its stroke count, meanings, readings, level and the components KanjiVG
splits it into, each a tap away. It opens from a studied character's
answer (Stroke order) and from a character found in the dictionary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 15:35:00 +02:00
|
|
|
|
import '../../support/loaded_strokes.dart';
|
feat(app): the dictionary in the app: look words up, add them as cards
A studyset's dictionary follows from its language, as decided: the
instance's dictionary of its front's language, with meanings in its
back's (else English). Nothing to attach or detach; nothing of it shows
where there is none.
- Add material › From the dictionary: search a word (kanji, kana,
romaji, meaning) and add it as a flashcard, or add a whole level
(JLPT N5…N1, common words) at once, asked first above 500 words, its
progress shown. Words already in the studyset show as such and are
left out. Each card is a vocabulary card (headword, reading, senses
with their codes), tagged `jlpt-n5` and with its word class, and
marked as imported from the dictionary.
- Look up beside a vocabulary card's front fills the card with the word
chosen, keeping its hints, explanation and tags.
- A card keeps the dictionary entry it was made from (card schema 6:
`dictionary` {source, id}), to be found again and not added twice.
- The dictionary's credits show at its foot and in About, as its licence
asks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 01:33:12 +02:00
|
|
|
|
import '../../support/pump_app.dart';
|
|
|
|
|
|
import '../../support/test_dependencies.dart';
|
|
|
|
|
|
|
|
|
|
|
|
final taberu = word(
|
|
|
|
|
|
'1358280',
|
|
|
|
|
|
'食べる',
|
|
|
|
|
|
reading: 'たべる',
|
|
|
|
|
|
glosses: ['to eat'],
|
|
|
|
|
|
labels: ['common', 'jlpt:n5'],
|
|
|
|
|
|
pos: ['v1'],
|
|
|
|
|
|
);
|
|
|
|
|
|
final terebi = word(
|
|
|
|
|
|
'1080510',
|
|
|
|
|
|
'テレビ',
|
|
|
|
|
|
glosses: ['television'],
|
|
|
|
|
|
labels: ['common', 'jlpt:n5'],
|
|
|
|
|
|
pos: ['n'],
|
|
|
|
|
|
);
|
|
|
|
|
|
final toukyou = word(
|
|
|
|
|
|
'1447690',
|
|
|
|
|
|
'東京',
|
|
|
|
|
|
reading: 'とうきょう',
|
|
|
|
|
|
glosses: ['Tokyo'],
|
|
|
|
|
|
labels: ['common'],
|
|
|
|
|
|
pos: ['n'],
|
|
|
|
|
|
);
|
|
|
|
|
|
|
feat(app): kanji and kana in the dictionary, beside the words
Where the instance holds the characters of a studyset's language, the
dictionary screen has a tab for them, Kanji & kana: search by character,
reading (kana or romaji) or meaning, and whole levels to add, under their
names: the kana (hiragana, katakana), the JLPT levels and the school
grades. A character becomes a character card, its meanings (a kana's
romaji) and readings, tagged with its level (JLPT-N5), school grade
(Grade 1 … Grade 6, Secondary school) and script (Hiragana, Katakana). A
character card's Look up searches the characters, and an entry looked up
again on a card swaps the tags the one before brought, as words do.
A dictionary's entries are now words or characters (DictionaryItem), as
its kind says.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 15:19:22 +02:00
|
|
|
|
final shoku = character(
|
|
|
|
|
|
'食',
|
|
|
|
|
|
meanings: ['eat', 'food'],
|
|
|
|
|
|
on: ['ショク'],
|
|
|
|
|
|
kun: ['た.べる'],
|
|
|
|
|
|
labels: ['jlpt:n5', 'grade:2'],
|
|
|
|
|
|
grade: 2,
|
|
|
|
|
|
strokes: 9,
|
|
|
|
|
|
);
|
|
|
|
|
|
final a = character('あ', labels: ['hiragana'], romaji: 'a');
|
|
|
|
|
|
|
|
|
|
|
|
/// An instance with JMdict and KANJIDIC2.
|
|
|
|
|
|
FakeDictionaryGateway withKanji() => FakeDictionaryGateway(
|
|
|
|
|
|
available: [jmdict, kanjidic],
|
|
|
|
|
|
words: [taberu, terebi, shoku, a],
|
|
|
|
|
|
);
|
|
|
|
|
|
|
feat(app): the dictionary in the app: look words up, add them as cards
A studyset's dictionary follows from its language, as decided: the
instance's dictionary of its front's language, with meanings in its
back's (else English). Nothing to attach or detach; nothing of it shows
where there is none.
- Add material › From the dictionary: search a word (kanji, kana,
romaji, meaning) and add it as a flashcard, or add a whole level
(JLPT N5…N1, common words) at once, asked first above 500 words, its
progress shown. Words already in the studyset show as such and are
left out. Each card is a vocabulary card (headword, reading, senses
with their codes), tagged `jlpt-n5` and with its word class, and
marked as imported from the dictionary.
- Look up beside a vocabulary card's front fills the card with the word
chosen, keeping its hints, explanation and tags.
- A card keeps the dictionary entry it was made from (card schema 6:
`dictionary` {source, id}), to be found again and not added twice.
- The dictionary's credits show at its foot and in About, as its licence
asks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 01:33:12 +02:00
|
|
|
|
void main() {
|
|
|
|
|
|
late FakeCardGateway cardGateway;
|
|
|
|
|
|
late CardRepository cards;
|
|
|
|
|
|
|
|
|
|
|
|
setUp(() {
|
|
|
|
|
|
cardGateway = FakeCardGateway();
|
|
|
|
|
|
cards = CardRepository(cardGateway);
|
|
|
|
|
|
});
|
|
|
|
|
|
tearDown(() => cards.dispose());
|
|
|
|
|
|
|
|
|
|
|
|
/// Lets the editors' timers run out and the screen settle.
|
|
|
|
|
|
Future<void> settle(WidgetTester tester) async {
|
|
|
|
|
|
await tester.pump(const Duration(seconds: 1));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(dictionary): characters of Chinese, Korean and Greek, written by hand; pinyin typed
Parity with Japanese's kanji and kana:
- The characters' tab is named by its language (Hanzi, Hangul & Hanja,
Alphabet), its groups too: HSK levels and the characters in common use
(Hong Kong's grades for traditional ones); Hangul and the Hanja taught
in schools; Greek's letters. Radicals for Chinese and Korean in their
own forms (讠 for 言 in simplified Chinese), none for Greek.
- A character's readings are named as its language names them: On and
Kun, Pinyin, Eum and Hun (음, 훈); a letter means its sound and name.
- How characters are written comes by language: Chinese from Make Me a
Hanzi (its medians in KanjiVG's square, Arphic Public License, a data
file beside the app, never in cards), the Hangul letters drawn by the
curator; a Korean word is written letter by letter (닭: ㄷ ㅏ ㄺ). Words
of alphabets (German, Greek) aren't written by hand.
- A Chinese word's pinyin is typed in a session, tone numbers turning
into marks (ni3 → nǐ); KRDict's levels (초급, 중급, 고급) tag Korean
words; a Korean word's pronunciation shows in brackets ([가치]).
- Smaller: Look up in the composer keeps a noun's article; the Watch
filter names its level list; hints and placeholders by language.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 12:35:17 +02:00
|
|
|
|
/// Opens the studyset [id]: `japanese` (Japanese with English meanings),
|
|
|
|
|
|
/// `german`, `greek` or `chinese`, on an instance with JMdict
|
|
|
|
|
|
/// ([dictionaries]), at
|
fix(app): the dictionary adds each word once, says when it can't, and Look up tags hold
- A word tapped twice, or added while a level is being added, makes one
card: adds run one at a time, and the word counts as in the studyset
at once. The adder reads the studyset's cards once rather than for
every word.
- Adding that fails says so, and a level shows "Making the cards…" once
its words have come.
- Finding a studyset's dictionary tells finding, none and unreachable
apart: the dictionary screen says there is none or offers Retry, and
an instance unreachable at start is asked again once a sync goes
through (Look up and From the dictionary appear then).
- Look up's tags wait on the card by name and are made when it is saved:
leaving the card at once no longer saves it untagged, a word replaced
leaves no tag behind, and a new word replaces any level or word class
on the card, pinned ones too. Look up marks words already made into
cards of the studyset.
- The tag picker's Create keeps the case typed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 12:45:18 +02:00
|
|
|
|
/// [location] (the studyset's screen unless given).
|
|
|
|
|
|
Future<void> show(
|
|
|
|
|
|
WidgetTester tester, {
|
|
|
|
|
|
String id = 'japanese',
|
|
|
|
|
|
FakeDictionaryGateway? dictionaries,
|
feat(app): stroke order for kanji and kana, from KanjiVG
KanjiVG's strokes come with the app (assets/kanjivg/, CC BY-SA 3.0, its
notice in the licence list): 94 gzipped files, one per 256 code points,
2.7 MB in all, a file read the first time a character of it is asked for
(StrokeRepository, app-wide beside the maps). The strokes are SVG path
data, read with path_parsing (the Flutter team's, pure Dart, already in
the build through flutter_svg), so the model can flatten them into points
without dart:ui, as checking handwriting will.
A character's sheet traces its strokes in order over those to come, with
its stroke count, meanings, readings, level and the components KanjiVG
splits it into, each a tap away. It opens from a studied character's
answer (Stroke order) and from a character found in the dictionary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 15:35:00 +02:00
|
|
|
|
StrokeRepository? strokes,
|
fix(app): the dictionary adds each word once, says when it can't, and Look up tags hold
- A word tapped twice, or added while a level is being added, makes one
card: adds run one at a time, and the word counts as in the studyset
at once. The adder reads the studyset's cards once rather than for
every word.
- Adding that fails says so, and a level shows "Making the cards…" once
its words have come.
- Finding a studyset's dictionary tells finding, none and unreachable
apart: the dictionary screen says there is none or offers Retry, and
an instance unreachable at start is asked again once a sync goes
through (Look up and From the dictionary appear then).
- Look up's tags wait on the card by name and are made when it is saved:
leaving the card at once no longer saves it untagged, a word replaced
leaves no tag behind, and a new word replaces any level or word class
on the card, pinned ones too. Look up marks words already made into
cards of the studyset.
- The tag picker's Create keeps the case typed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 12:45:18 +02:00
|
|
|
|
String? location,
|
|
|
|
|
|
}) async {
|
feat(app): the dictionary in the app: look words up, add them as cards
A studyset's dictionary follows from its language, as decided: the
instance's dictionary of its front's language, with meanings in its
back's (else English). Nothing to attach or detach; nothing of it shows
where there is none.
- Add material › From the dictionary: search a word (kanji, kana,
romaji, meaning) and add it as a flashcard, or add a whole level
(JLPT N5…N1, common words) at once, asked first above 500 words, its
progress shown. Words already in the studyset show as such and are
left out. Each card is a vocabulary card (headword, reading, senses
with their codes), tagged `jlpt-n5` and with its word class, and
marked as imported from the dictionary.
- Look up beside a vocabulary card's front fills the card with the word
chosen, keeping its hints, explanation and tags.
- A card keeps the dictionary entry it was made from (card schema 6:
`dictionary` {source, id}), to be found again and not added twice.
- The dictionary's credits show at its foot and in About, as its licence
asks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 01:33:12 +02:00
|
|
|
|
tester.view.physicalSize = const Size(1400, 1600);
|
|
|
|
|
|
tester.view.devicePixelRatio = 1;
|
|
|
|
|
|
addTearDown(tester.view.reset);
|
|
|
|
|
|
final studysets = FakeStudysetGateway()
|
|
|
|
|
|
..receiveRemoteChange(
|
|
|
|
|
|
'japanese',
|
|
|
|
|
|
encodeStudysetPayload(
|
|
|
|
|
|
StudysetContent(
|
|
|
|
|
|
title: 'Japanese',
|
|
|
|
|
|
description: RichContent.empty(),
|
|
|
|
|
|
frontLanguage: 'ja',
|
|
|
|
|
|
backLanguage: 'en',
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
..receiveRemoteChange(
|
|
|
|
|
|
'german',
|
|
|
|
|
|
encodeStudysetPayload(
|
|
|
|
|
|
StudysetContent(
|
|
|
|
|
|
title: 'German',
|
|
|
|
|
|
description: RichContent.empty(),
|
|
|
|
|
|
frontLanguage: 'de',
|
|
|
|
|
|
backLanguage: 'en',
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
feat(dictionary): characters of Chinese, Korean and Greek, written by hand; pinyin typed
Parity with Japanese's kanji and kana:
- The characters' tab is named by its language (Hanzi, Hangul & Hanja,
Alphabet), its groups too: HSK levels and the characters in common use
(Hong Kong's grades for traditional ones); Hangul and the Hanja taught
in schools; Greek's letters. Radicals for Chinese and Korean in their
own forms (讠 for 言 in simplified Chinese), none for Greek.
- A character's readings are named as its language names them: On and
Kun, Pinyin, Eum and Hun (음, 훈); a letter means its sound and name.
- How characters are written comes by language: Chinese from Make Me a
Hanzi (its medians in KanjiVG's square, Arphic Public License, a data
file beside the app, never in cards), the Hangul letters drawn by the
curator; a Korean word is written letter by letter (닭: ㄷ ㅏ ㄺ). Words
of alphabets (German, Greek) aren't written by hand.
- A Chinese word's pinyin is typed in a session, tone numbers turning
into marks (ni3 → nǐ); KRDict's levels (초급, 중급, 고급) tag Korean
words; a Korean word's pronunciation shows in brackets ([가치]).
- Smaller: Look up in the composer keeps a noun's article; the Watch
filter names its level list; hints and placeholders by language.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 12:35:17 +02:00
|
|
|
|
)
|
|
|
|
|
|
..receiveRemoteChange(
|
|
|
|
|
|
'greek',
|
|
|
|
|
|
encodeStudysetPayload(
|
|
|
|
|
|
StudysetContent(
|
|
|
|
|
|
title: 'Greek',
|
|
|
|
|
|
description: RichContent.empty(),
|
|
|
|
|
|
frontLanguage: 'el',
|
|
|
|
|
|
backLanguage: 'en',
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
..receiveRemoteChange(
|
|
|
|
|
|
'chinese',
|
|
|
|
|
|
encodeStudysetPayload(
|
|
|
|
|
|
StudysetContent(
|
|
|
|
|
|
title: 'Chinese',
|
|
|
|
|
|
description: RichContent.empty(),
|
|
|
|
|
|
frontLanguage: 'zh-Hans',
|
|
|
|
|
|
backLanguage: 'en',
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
feat(app): the dictionary in the app: look words up, add them as cards
A studyset's dictionary follows from its language, as decided: the
instance's dictionary of its front's language, with meanings in its
back's (else English). Nothing to attach or detach; nothing of it shows
where there is none.
- Add material › From the dictionary: search a word (kanji, kana,
romaji, meaning) and add it as a flashcard, or add a whole level
(JLPT N5…N1, common words) at once, asked first above 500 words, its
progress shown. Words already in the studyset show as such and are
left out. Each card is a vocabulary card (headword, reading, senses
with their codes), tagged `jlpt-n5` and with its word class, and
marked as imported from the dictionary.
- Look up beside a vocabulary card's front fills the card with the word
chosen, keeping its hints, explanation and tags.
- A card keeps the dictionary entry it was made from (card schema 6:
`dictionary` {source, id}), to be found again and not added twice.
- The dictionary's credits show at its foot and in About, as its licence
asks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 01:33:12 +02:00
|
|
|
|
);
|
|
|
|
|
|
await pumpApp(
|
|
|
|
|
|
tester,
|
|
|
|
|
|
testDependencies(
|
|
|
|
|
|
studysetGateway: studysets,
|
|
|
|
|
|
cardGateway: cardGateway,
|
fix(app): the dictionary adds each word once, says when it can't, and Look up tags hold
- A word tapped twice, or added while a level is being added, makes one
card: adds run one at a time, and the word counts as in the studyset
at once. The adder reads the studyset's cards once rather than for
every word.
- Adding that fails says so, and a level shows "Making the cards…" once
its words have come.
- Finding a studyset's dictionary tells finding, none and unreachable
apart: the dictionary screen says there is none or offers Retry, and
an instance unreachable at start is asked again once a sync goes
through (Look up and From the dictionary appear then).
- Look up's tags wait on the card by name and are made when it is saved:
leaving the card at once no longer saves it untagged, a word replaced
leaves no tag behind, and a new word replaces any level or word class
on the card, pinned ones too. Look up marks words already made into
cards of the studyset.
- The tag picker's Create keeps the case typed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 12:45:18 +02:00
|
|
|
|
dictionaryGateway:
|
|
|
|
|
|
dictionaries ??
|
|
|
|
|
|
FakeDictionaryGateway(words: [taberu, terebi, toukyou]),
|
feat(app): stroke order for kanji and kana, from KanjiVG
KanjiVG's strokes come with the app (assets/kanjivg/, CC BY-SA 3.0, its
notice in the licence list): 94 gzipped files, one per 256 code points,
2.7 MB in all, a file read the first time a character of it is asked for
(StrokeRepository, app-wide beside the maps). The strokes are SVG path
data, read with path_parsing (the Flutter team's, pure Dart, already in
the build through flutter_svg), so the model can flatten them into points
without dart:ui, as checking handwriting will.
A character's sheet traces its strokes in order over those to come, with
its stroke count, meanings, readings, level and the components KanjiVG
splits it into, each a tap away. It opens from a studied character's
answer (Stroke order) and from a character found in the dictionary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 15:35:00 +02:00
|
|
|
|
strokes: strokes,
|
feat(app): the dictionary in the app: look words up, add them as cards
A studyset's dictionary follows from its language, as decided: the
instance's dictionary of its front's language, with meanings in its
back's (else English). Nothing to attach or detach; nothing of it shows
where there is none.
- Add material › From the dictionary: search a word (kanji, kana,
romaji, meaning) and add it as a flashcard, or add a whole level
(JLPT N5…N1, common words) at once, asked first above 500 words, its
progress shown. Words already in the studyset show as such and are
left out. Each card is a vocabulary card (headword, reading, senses
with their codes), tagged `jlpt-n5` and with its word class, and
marked as imported from the dictionary.
- Look up beside a vocabulary card's front fills the card with the word
chosen, keeping its hints, explanation and tags.
- A card keeps the dictionary entry it was made from (card schema 6:
`dictionary` {source, id}), to be found again and not added twice.
- The dictionary's credits show at its foot and in About, as its licence
asks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 01:33:12 +02:00
|
|
|
|
),
|
fix(app): the dictionary adds each word once, says when it can't, and Look up tags hold
- A word tapped twice, or added while a level is being added, makes one
card: adds run one at a time, and the word counts as in the studyset
at once. The adder reads the studyset's cards once rather than for
every word.
- Adding that fails says so, and a level shows "Making the cards…" once
its words have come.
- Finding a studyset's dictionary tells finding, none and unreachable
apart: the dictionary screen says there is none or offers Retry, and
an instance unreachable at start is asked again once a sync goes
through (Look up and From the dictionary appear then).
- Look up's tags wait on the card by name and are made when it is saved:
leaving the card at once no longer saves it untagged, a word replaced
leaves no tag behind, and a new word replaces any level or word class
on the card, pinned ones too. Look up marks words already made into
cards of the studyset.
- The tag picker's Create keeps the case typed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 12:45:18 +02:00
|
|
|
|
location: location ?? RoutePaths.studyset(id),
|
feat(app): the dictionary in the app: look words up, add them as cards
A studyset's dictionary follows from its language, as decided: the
instance's dictionary of its front's language, with meanings in its
back's (else English). Nothing to attach or detach; nothing of it shows
where there is none.
- Add material › From the dictionary: search a word (kanji, kana,
romaji, meaning) and add it as a flashcard, or add a whole level
(JLPT N5…N1, common words) at once, asked first above 500 words, its
progress shown. Words already in the studyset show as such and are
left out. Each card is a vocabulary card (headword, reading, senses
with their codes), tagged `jlpt-n5` and with its word class, and
marked as imported from the dictionary.
- Look up beside a vocabulary card's front fills the card with the word
chosen, keeping its hints, explanation and tags.
- A card keeps the dictionary entry it was made from (card schema 6:
`dictionary` {source, id}), to be found again and not added twice.
- The dictionary's credits show at its foot and in About, as its licence
asks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 01:33:12 +02:00
|
|
|
|
);
|
|
|
|
|
|
await settle(tester);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Chooses [item] under Add material.
|
|
|
|
|
|
Future<void> addMaterial(WidgetTester tester, String item) async {
|
|
|
|
|
|
await tester.tap(find.text('Add material'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
await tester.tap(find.text(item));
|
|
|
|
|
|
await tester.pump();
|
|
|
|
|
|
await tester.pump();
|
|
|
|
|
|
await settle(tester);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
testWidgets('Add material offers the dictionary only where the '
|
|
|
|
|
|
"studyset's language has one", (tester) async {
|
|
|
|
|
|
await show(tester, id: 'german');
|
|
|
|
|
|
await tester.tap(find.text('Add material'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
|
|
|
|
|
|
expect(find.text('From the dictionary'), findsNothing);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
testWidgets('a word found is added as a flashcard, and then shows as in '
|
|
|
|
|
|
'the studyset; the screen credits its sources', (tester) async {
|
|
|
|
|
|
await show(tester);
|
|
|
|
|
|
await addMaterial(tester, 'From the dictionary');
|
|
|
|
|
|
|
2026-10-12 22:41:09 +02:00
|
|
|
|
// Its sources by name in a line; who made them, under which licence,
|
|
|
|
|
|
// a tap away.
|
feat(app): the dictionary in the app: look words up, add them as cards
A studyset's dictionary follows from its language, as decided: the
instance's dictionary of its front's language, with meanings in its
back's (else English). Nothing to attach or detach; nothing of it shows
where there is none.
- Add material › From the dictionary: search a word (kanji, kana,
romaji, meaning) and add it as a flashcard, or add a whole level
(JLPT N5…N1, common words) at once, asked first above 500 words, its
progress shown. Words already in the studyset show as such and are
left out. Each card is a vocabulary card (headword, reading, senses
with their codes), tagged `jlpt-n5` and with its word class, and
marked as imported from the dictionary.
- Look up beside a vocabulary card's front fills the card with the word
chosen, keeping its hints, explanation and tags.
- A card keeps the dictionary entry it was made from (card schema 6:
`dictionary` {source, id}), to be found again and not added twice.
- The dictionary's credits show at its foot and in About, as its licence
asks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 01:33:12 +02:00
|
|
|
|
expect(find.textContaining('Words from JMdict'), findsOneWidget);
|
2026-10-12 22:41:09 +02:00
|
|
|
|
expect(find.textContaining('EDRDG'), findsNothing);
|
|
|
|
|
|
await tester.tap(find.byTooltip('Sources'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
expect(find.textContaining('EDRDG'), findsOneWidget);
|
|
|
|
|
|
await tester.tap(find.byTooltip('Close'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
feat(app): the dictionary in the app: look words up, add them as cards
A studyset's dictionary follows from its language, as decided: the
instance's dictionary of its front's language, with meanings in its
back's (else English). Nothing to attach or detach; nothing of it shows
where there is none.
- Add material › From the dictionary: search a word (kanji, kana,
romaji, meaning) and add it as a flashcard, or add a whole level
(JLPT N5…N1, common words) at once, asked first above 500 words, its
progress shown. Words already in the studyset show as such and are
left out. Each card is a vocabulary card (headword, reading, senses
with their codes), tagged `jlpt-n5` and with its word class, and
marked as imported from the dictionary.
- Look up beside a vocabulary card's front fills the card with the word
chosen, keeping its hints, explanation and tags.
- A card keeps the dictionary entry it was made from (card schema 6:
`dictionary` {source, id}), to be found again and not added twice.
- The dictionary's credits show at its foot and in About, as its licence
asks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 01:33:12 +02:00
|
|
|
|
await tester.enterText(find.byType(TextField), 'たべ');
|
|
|
|
|
|
await tester.pump(const Duration(milliseconds: 300));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
expect(find.textContaining('食べる'), findsOneWidget);
|
|
|
|
|
|
await tester.tap(find.text('Add'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
|
|
|
|
|
|
expect(find.text('Added “食べる”.'), findsOneWidget);
|
|
|
|
|
|
expect(find.byTooltip('In this studyset'), findsOneWidget);
|
|
|
|
|
|
final card = (await cards.cardsOf('japanese')).single;
|
|
|
|
|
|
expect(card.content.entry, (source: 'jmdict', id: '1358280'));
|
|
|
|
|
|
expect(card.content.reading, 'たべる');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
fix(app): the dictionary adds each word once, says when it can't, and Look up tags hold
- A word tapped twice, or added while a level is being added, makes one
card: adds run one at a time, and the word counts as in the studyset
at once. The adder reads the studyset's cards once rather than for
every word.
- Adding that fails says so, and a level shows "Making the cards…" once
its words have come.
- Finding a studyset's dictionary tells finding, none and unreachable
apart: the dictionary screen says there is none or offers Retry, and
an instance unreachable at start is asked again once a sync goes
through (Look up and From the dictionary appear then).
- Look up's tags wait on the card by name and are made when it is saved:
leaving the card at once no longer saves it untagged, a word replaced
leaves no tag behind, and a new word replaces any level or word class
on the card, pinned ones too. Look up marks words already made into
cards of the studyset.
- The tag picker's Create keeps the case typed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 12:45:18 +02:00
|
|
|
|
testWidgets('tapping Add twice makes one card', (tester) async {
|
|
|
|
|
|
await show(tester);
|
|
|
|
|
|
await addMaterial(tester, 'From the dictionary');
|
|
|
|
|
|
await tester.enterText(find.byType(TextField), 'たべ');
|
|
|
|
|
|
await tester.pump(const Duration(milliseconds: 300));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
|
|
|
|
|
|
await tester.tap(find.text('Add'));
|
|
|
|
|
|
await tester.tap(find.text('Add'), warnIfMissed: false);
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
|
|
|
|
|
|
expect(await cards.cardsOf('japanese'), hasLength(1));
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
testWidgets('without the instance, the dictionary says so and offers to '
|
|
|
|
|
|
'try again, which finds it once the instance answers', (tester) async {
|
|
|
|
|
|
final dictionaries = FakeDictionaryGateway(words: [taberu])
|
|
|
|
|
|
..fail = Exception('offline');
|
|
|
|
|
|
await show(
|
|
|
|
|
|
tester,
|
|
|
|
|
|
dictionaries: dictionaries,
|
|
|
|
|
|
location: RoutePaths.dictionary('japanese'),
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
expect(find.text("The dictionary can't be reached right now."), findsOne);
|
|
|
|
|
|
dictionaries.fail = null;
|
|
|
|
|
|
await tester.tap(find.text('Retry'));
|
|
|
|
|
|
await settle(tester);
|
|
|
|
|
|
|
|
|
|
|
|
expect(find.text('N5'), findsOneWidget);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
testWidgets('a studyset whose language has no dictionary says so', (
|
|
|
|
|
|
tester,
|
|
|
|
|
|
) async {
|
|
|
|
|
|
await show(tester, id: 'german', location: RoutePaths.dictionary('german'));
|
|
|
|
|
|
|
|
|
|
|
|
expect(
|
|
|
|
|
|
find.text("This studyset's language has no dictionary on this instance."),
|
|
|
|
|
|
findsOneWidget,
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
feat(app): the dictionary in the app: look words up, add them as cards
A studyset's dictionary follows from its language, as decided: the
instance's dictionary of its front's language, with meanings in its
back's (else English). Nothing to attach or detach; nothing of it shows
where there is none.
- Add material › From the dictionary: search a word (kanji, kana,
romaji, meaning) and add it as a flashcard, or add a whole level
(JLPT N5…N1, common words) at once, asked first above 500 words, its
progress shown. Words already in the studyset show as such and are
left out. Each card is a vocabulary card (headword, reading, senses
with their codes), tagged `jlpt-n5` and with its word class, and
marked as imported from the dictionary.
- Look up beside a vocabulary card's front fills the card with the word
chosen, keeping its hints, explanation and tags.
- A card keeps the dictionary entry it was made from (card schema 6:
`dictionary` {source, id}), to be found again and not added twice.
- The dictionary's credits show at its foot and in About, as its licence
asks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 01:33:12 +02:00
|
|
|
|
testWidgets('a whole level becomes flashcards, its words in its order', (
|
|
|
|
|
|
tester,
|
|
|
|
|
|
) async {
|
|
|
|
|
|
await show(tester);
|
|
|
|
|
|
await addMaterial(tester, 'From the dictionary');
|
|
|
|
|
|
|
|
|
|
|
|
expect(find.text('N5'), findsOneWidget);
|
|
|
|
|
|
await tester.tap(find.text('Add 2 words'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
|
|
|
|
|
|
expect(find.text('Added 2 words.'), findsOneWidget);
|
|
|
|
|
|
expect(
|
|
|
|
|
|
[
|
|
|
|
|
|
for (final card in await cards.cardsOf('japanese'))
|
|
|
|
|
|
card.content.front.plainText.trim(),
|
|
|
|
|
|
],
|
|
|
|
|
|
['食べる', 'テレビ'],
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
feat(app): kanji and kana in the dictionary, beside the words
Where the instance holds the characters of a studyset's language, the
dictionary screen has a tab for them, Kanji & kana: search by character,
reading (kana or romaji) or meaning, and whole levels to add, under their
names: the kana (hiragana, katakana), the JLPT levels and the school
grades. A character becomes a character card, its meanings (a kana's
romaji) and readings, tagged with its level (JLPT-N5), school grade
(Grade 1 … Grade 6, Secondary school) and script (Hiragana, Katakana). A
character card's Look up searches the characters, and an entry looked up
again on a card swaps the tags the one before brought, as words do.
A dictionary's entries are now words or characters (DictionaryItem), as
its kind says.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 15:19:22 +02:00
|
|
|
|
testWidgets('beside the words, kanji and kana: their levels in groups, '
|
|
|
|
|
|
'one added as character cards tagged with level and grade', (
|
|
|
|
|
|
tester,
|
|
|
|
|
|
) async {
|
|
|
|
|
|
await show(tester, dictionaries: withKanji());
|
|
|
|
|
|
await addMaterial(tester, 'From the dictionary');
|
|
|
|
|
|
|
|
|
|
|
|
expect(find.text('Words'), findsOneWidget);
|
|
|
|
|
|
await tester.tap(find.text('Kanji & kana'));
|
|
|
|
|
|
await settle(tester);
|
|
|
|
|
|
expect(find.text('Kana'), findsOneWidget);
|
|
|
|
|
|
expect(find.text('JLPT levels'), findsOneWidget);
|
|
|
|
|
|
expect(find.text('School grades'), findsOneWidget);
|
|
|
|
|
|
expect(find.text('Grade 2'), findsOneWidget);
|
|
|
|
|
|
await tester.tap(find.text('N5').hitTestable());
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
await tester.tap(find.text('Add 1 character'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
|
|
|
|
|
|
expect(find.text('Added 1 character.'), findsOneWidget);
|
|
|
|
|
|
final made = (await cards.cardsOf('japanese')).single;
|
|
|
|
|
|
expect(made.content.type, CardType.character);
|
|
|
|
|
|
expect(made.content.onReadings, ['ショク']);
|
|
|
|
|
|
expect(made.content.entry, (source: 'kanjidic', id: '食'));
|
|
|
|
|
|
});
|
|
|
|
|
|
|
feat(dictionary): characters of Chinese, Korean and Greek, written by hand; pinyin typed
Parity with Japanese's kanji and kana:
- The characters' tab is named by its language (Hanzi, Hangul & Hanja,
Alphabet), its groups too: HSK levels and the characters in common use
(Hong Kong's grades for traditional ones); Hangul and the Hanja taught
in schools; Greek's letters. Radicals for Chinese and Korean in their
own forms (讠 for 言 in simplified Chinese), none for Greek.
- A character's readings are named as its language names them: On and
Kun, Pinyin, Eum and Hun (음, 훈); a letter means its sound and name.
- How characters are written comes by language: Chinese from Make Me a
Hanzi (its medians in KanjiVG's square, Arphic Public License, a data
file beside the app, never in cards), the Hangul letters drawn by the
curator; a Korean word is written letter by letter (닭: ㄷ ㅏ ㄺ). Words
of alphabets (German, Greek) aren't written by hand.
- A Chinese word's pinyin is typed in a session, tone numbers turning
into marks (ni3 → nǐ); KRDict's levels (초급, 중급, 고급) tag Korean
words; a Korean word's pronunciation shows in brackets ([가치]).
- Smaller: Look up in the composer keeps a noun's article; the Watch
filter names its level list; hints and placeholders by language.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 12:35:17 +02:00
|
|
|
|
testWidgets("Greek's letters in a tab of their own, without radicals, "
|
|
|
|
|
|
'added whole as character cards', (tester) async {
|
|
|
|
|
|
await show(
|
|
|
|
|
|
tester,
|
|
|
|
|
|
id: 'greek',
|
|
|
|
|
|
dictionaries: FakeDictionaryGateway(
|
|
|
|
|
|
available: [wiktionaryEl, greekAlphabet],
|
|
|
|
|
|
words: [
|
|
|
|
|
|
character(
|
|
|
|
|
|
'Β β',
|
|
|
|
|
|
labels: ['greek'],
|
|
|
|
|
|
romaji: 'v',
|
|
|
|
|
|
name: 'βήτα (beta)',
|
|
|
|
|
|
of: greekAlphabet,
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
await addMaterial(tester, 'From the dictionary');
|
|
|
|
|
|
|
|
|
|
|
|
expect(find.text('Radicals'), findsNothing);
|
|
|
|
|
|
await tester.tap(find.text('Alphabet'));
|
|
|
|
|
|
await settle(tester);
|
|
|
|
|
|
expect(find.text('Letters'), findsOneWidget);
|
|
|
|
|
|
await tester.tap(find.text('Greek alphabet').hitTestable());
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
await tester.tap(find.text('Add 1 character'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
|
|
|
|
|
|
final made = (await cards.cardsOf('greek')).single;
|
|
|
|
|
|
expect(made.content.front.plainText.trim(), 'Β β');
|
|
|
|
|
|
expect(made.content.meanings, ['v', 'βήτα (beta)']);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
testWidgets('Chinese characters by HSK level and in common use, beside '
|
|
|
|
|
|
'the radicals in their simplified forms', (tester) async {
|
|
|
|
|
|
await show(
|
|
|
|
|
|
tester,
|
|
|
|
|
|
id: 'chinese',
|
|
|
|
|
|
dictionaries: FakeDictionaryGateway(
|
|
|
|
|
|
available: [ccCedictHans, unihanHans],
|
|
|
|
|
|
words: [
|
|
|
|
|
|
character(
|
|
|
|
|
|
'说',
|
|
|
|
|
|
meanings: ['speak'],
|
|
|
|
|
|
on: ['shuō'],
|
|
|
|
|
|
labels: ['hsk:1', 'common', 'radical:149'],
|
|
|
|
|
|
of: unihanHans,
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
await addMaterial(tester, 'From the dictionary');
|
|
|
|
|
|
|
|
|
|
|
|
await tester.tap(find.text('Hanzi'));
|
|
|
|
|
|
await settle(tester);
|
|
|
|
|
|
expect(find.text('HSK levels'), findsOneWidget);
|
|
|
|
|
|
expect(find.text('In common use'), findsOneWidget);
|
|
|
|
|
|
expect(find.text('HSK 1'), findsOneWidget);
|
|
|
|
|
|
await tester.tap(find.text('Radicals'));
|
|
|
|
|
|
await settle(tester);
|
|
|
|
|
|
// 言 as simplified Chinese writes it at a character's side.
|
|
|
|
|
|
await tester.scrollUntilVisible(
|
|
|
|
|
|
find.text('讠'),
|
|
|
|
|
|
400,
|
|
|
|
|
|
scrollable: find.byType(Scrollable).last,
|
|
|
|
|
|
);
|
|
|
|
|
|
expect(find.text('讠'), findsOneWidget);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
feat(app): kanji and kana in the dictionary, beside the words
Where the instance holds the characters of a studyset's language, the
dictionary screen has a tab for them, Kanji & kana: search by character,
reading (kana or romaji) or meaning, and whole levels to add, under their
names: the kana (hiragana, katakana), the JLPT levels and the school
grades. A character becomes a character card, its meanings (a kana's
romaji) and readings, tagged with its level (JLPT-N5), school grade
(Grade 1 … Grade 6, Secondary school) and script (Hiragana, Katakana). A
character card's Look up searches the characters, and an entry looked up
again on a card swaps the tags the one before brought, as words do.
A dictionary's entries are now words or characters (DictionaryItem), as
its kind says.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 15:19:22 +02:00
|
|
|
|
testWidgets('a character found shows its readings and strokes, and is '
|
|
|
|
|
|
'added as a card', (tester) async {
|
|
|
|
|
|
await show(tester, dictionaries: withKanji());
|
|
|
|
|
|
await addMaterial(tester, 'From the dictionary');
|
|
|
|
|
|
await tester.tap(find.text('Kanji & kana'));
|
|
|
|
|
|
await settle(tester);
|
|
|
|
|
|
|
|
|
|
|
|
await tester.enterText(find.byType(TextField).hitTestable(), 'ショク');
|
|
|
|
|
|
await tester.pump(const Duration(milliseconds: 300));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
expect(find.text('ショク · た.べる'), findsOneWidget);
|
|
|
|
|
|
expect(find.text('9 strokes'), findsOneWidget);
|
|
|
|
|
|
await tester.tap(find.text('Add').hitTestable());
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
|
|
|
|
|
|
expect(find.text('Added “食”.'), findsOneWidget);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
feat(app): stroke order for kanji and kana, from KanjiVG
KanjiVG's strokes come with the app (assets/kanjivg/, CC BY-SA 3.0, its
notice in the licence list): 94 gzipped files, one per 256 code points,
2.7 MB in all, a file read the first time a character of it is asked for
(StrokeRepository, app-wide beside the maps). The strokes are SVG path
data, read with path_parsing (the Flutter team's, pure Dart, already in
the build through flutter_svg), so the model can flatten them into points
without dart:ui, as checking handwriting will.
A character's sheet traces its strokes in order over those to come, with
its stroke count, meanings, readings, level and the components KanjiVG
splits it into, each a tap away. It opens from a studied character's
answer (Stroke order) and from a character found in the dictionary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 15:35:00 +02:00
|
|
|
|
testWidgets('a character found shows how it is written when tapped', (
|
|
|
|
|
|
tester,
|
|
|
|
|
|
) async {
|
|
|
|
|
|
await show(
|
|
|
|
|
|
tester,
|
|
|
|
|
|
dictionaries: withKanji(),
|
|
|
|
|
|
strokes: await loadedStrokes(tester, ['食']),
|
|
|
|
|
|
);
|
|
|
|
|
|
await addMaterial(tester, 'From the dictionary');
|
|
|
|
|
|
await tester.tap(find.text('Kanji & kana'));
|
|
|
|
|
|
await settle(tester);
|
|
|
|
|
|
await tester.enterText(find.byType(TextField).hitTestable(), 'eat');
|
|
|
|
|
|
await tester.pump(const Duration(milliseconds: 300));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
|
|
|
|
|
|
await tester.tap(find.text('eat; food'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
|
|
|
|
|
|
expect(find.text('Stroke order'), findsNothing);
|
|
|
|
|
|
expect(find.text('Play again'), findsOneWidget);
|
|
|
|
|
|
expect(find.text('eat, food'), findsOneWidget);
|
|
|
|
|
|
expect(find.text('N5 · Grade 2'), findsOneWidget);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-10-14 17:50:15 +02:00
|
|
|
|
testWidgets('a Greek letter found opens no sheet of its strokes: none are '
|
|
|
|
|
|
'drawn', (tester) async {
|
|
|
|
|
|
await show(
|
|
|
|
|
|
tester,
|
|
|
|
|
|
id: 'greek',
|
|
|
|
|
|
dictionaries: FakeDictionaryGateway(
|
|
|
|
|
|
available: [wiktionaryEl, greekAlphabet],
|
|
|
|
|
|
words: [
|
|
|
|
|
|
character(
|
|
|
|
|
|
'Α α',
|
|
|
|
|
|
labels: ['greek'],
|
|
|
|
|
|
romaji: 'a',
|
|
|
|
|
|
name: 'άλφα (alpha)',
|
|
|
|
|
|
of: greekAlphabet,
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
await addMaterial(tester, 'From the dictionary');
|
|
|
|
|
|
await tester.tap(find.text('Alphabet'));
|
|
|
|
|
|
await settle(tester);
|
|
|
|
|
|
await tester.enterText(find.byType(TextField).hitTestable(), 'α');
|
|
|
|
|
|
await tester.pump(const Duration(milliseconds: 300));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
|
|
|
|
|
|
await tester.tap(find.text('Α α').hitTestable());
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
|
|
|
|
|
|
expect(
|
|
|
|
|
|
find.text("There's no stroke order for this character."),
|
|
|
|
|
|
findsNothing,
|
|
|
|
|
|
);
|
|
|
|
|
|
expect(find.text('Play again'), findsNothing);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
feat(app): kanji and kana in the dictionary, beside the words
Where the instance holds the characters of a studyset's language, the
dictionary screen has a tab for them, Kanji & kana: search by character,
reading (kana or romaji) or meaning, and whole levels to add, under their
names: the kana (hiragana, katakana), the JLPT levels and the school
grades. A character becomes a character card, its meanings (a kana's
romaji) and readings, tagged with its level (JLPT-N5), school grade
(Grade 1 … Grade 6, Secondary school) and script (Hiragana, Katakana). A
character card's Look up searches the characters, and an entry looked up
again on a card swaps the tags the one before brought, as words do.
A dictionary's entries are now words or characters (DictionaryItem), as
its kind says.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 15:19:22 +02:00
|
|
|
|
testWidgets('Look up fills a character card with its meanings, readings '
|
|
|
|
|
|
'and tags', (tester) async {
|
|
|
|
|
|
await show(tester, dictionaries: withKanji());
|
|
|
|
|
|
await addMaterial(tester, 'Create flashcards');
|
|
|
|
|
|
await tester.tap(find.text('Character'));
|
|
|
|
|
|
await settle(tester);
|
|
|
|
|
|
|
|
|
|
|
|
await tester.tap(find.byTooltip('Look up in the dictionary'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
await tester.enterText(find.byType(TextField).last, 'eat');
|
|
|
|
|
|
await tester.pump(const Duration(milliseconds: 300));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
await tester.tap(find.text('eat; food'));
|
|
|
|
|
|
await settle(tester);
|
|
|
|
|
|
|
|
|
|
|
|
expect(find.widgetWithText(TextField, 'eat'), findsOneWidget);
|
|
|
|
|
|
expect(find.widgetWithText(TextField, 'ショク'), findsOneWidget);
|
|
|
|
|
|
expect(find.widgetWithText(TextField, 'た.べる'), findsOneWidget);
|
|
|
|
|
|
expect(find.text('JLPT-N5'), findsOneWidget);
|
|
|
|
|
|
expect(find.text('Grade 2'), findsOneWidget);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
feat(app): the dictionary in the app: look words up, add them as cards
A studyset's dictionary follows from its language, as decided: the
instance's dictionary of its front's language, with meanings in its
back's (else English). Nothing to attach or detach; nothing of it shows
where there is none.
- Add material › From the dictionary: search a word (kanji, kana,
romaji, meaning) and add it as a flashcard, or add a whole level
(JLPT N5…N1, common words) at once, asked first above 500 words, its
progress shown. Words already in the studyset show as such and are
left out. Each card is a vocabulary card (headword, reading, senses
with their codes), tagged `jlpt-n5` and with its word class, and
marked as imported from the dictionary.
- Look up beside a vocabulary card's front fills the card with the word
chosen, keeping its hints, explanation and tags.
- A card keeps the dictionary entry it was made from (card schema 6:
`dictionary` {source, id}), to be found again and not added twice.
- The dictionary's credits show at its foot and in About, as its licence
asks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 01:33:12 +02:00
|
|
|
|
testWidgets('Look up fills a vocabulary card with a word of the '
|
|
|
|
|
|
'dictionary', (tester) async {
|
|
|
|
|
|
await show(tester);
|
|
|
|
|
|
await addMaterial(tester, 'Create flashcards');
|
|
|
|
|
|
await tester.tap(find.text('Vocabulary'));
|
|
|
|
|
|
await settle(tester);
|
|
|
|
|
|
|
|
|
|
|
|
await tester.tap(find.byTooltip('Look up in the dictionary'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
await tester.enterText(find.byType(TextField).last, 'たべる');
|
|
|
|
|
|
await tester.pump(const Duration(milliseconds: 300));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
await tester.tap(find.textContaining('食べる').last);
|
|
|
|
|
|
await settle(tester);
|
|
|
|
|
|
|
|
|
|
|
|
expect(find.text('食べる', findRichText: true), findsWidgets);
|
|
|
|
|
|
expect(find.widgetWithText(TextField, 'たべる'), findsOneWidget);
|
|
|
|
|
|
expect(find.text('to eat'), findsWidgets);
|
2026-10-09 11:40:03 +02:00
|
|
|
|
// Tagged as words from the dictionary screen are.
|
|
|
|
|
|
expect(find.text('JLPT-N5'), findsOneWidget);
|
|
|
|
|
|
expect(find.text('Verb'), findsOneWidget);
|
feat(app): the dictionary in the app: look words up, add them as cards
A studyset's dictionary follows from its language, as decided: the
instance's dictionary of its front's language, with meanings in its
back's (else English). Nothing to attach or detach; nothing of it shows
where there is none.
- Add material › From the dictionary: search a word (kanji, kana,
romaji, meaning) and add it as a flashcard, or add a whole level
(JLPT N5…N1, common words) at once, asked first above 500 words, its
progress shown. Words already in the studyset show as such and are
left out. Each card is a vocabulary card (headword, reading, senses
with their codes), tagged `jlpt-n5` and with its word class, and
marked as imported from the dictionary.
- Look up beside a vocabulary card's front fills the card with the word
chosen, keeping its hints, explanation and tags.
- A card keeps the dictionary entry it was made from (card schema 6:
`dictionary` {source, id}), to be found again and not added twice.
- The dictionary's credits show at its foot and in About, as its licence
asks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 01:33:12 +02:00
|
|
|
|
});
|
|
|
|
|
|
|
feat(dictionary): the radicals, each listing the kanji written with it
Beside Words and Kanji & kana, a Radicals tab lists the 214 Kangxi
radicals by their strokes, with the forms Japanese writes them as inside
kanji (亻, 氵, 艹, …). One chosen lists the kanji KANJIDIC2 files under
it, in the dictionary's order, to add or to see how one is written; the
tab shares the characters' bloc, so a kanji added there shows as held in
a search too.
The curator labels each kanji with its classical radical (`radical:N`),
so the server's existing label queries serve the tab unchanged. Its
files now say which conversion made them (`.conversion`): an instance
running a curator that converts differently converts again at once,
rather than at its weekly update, so the radicals appear right after a
deploy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-10 02:13:06 +02:00
|
|
|
|
group('Radicals', () {
|
|
|
|
|
|
final hito = character(
|
|
|
|
|
|
'人',
|
|
|
|
|
|
meanings: ['person'],
|
|
|
|
|
|
labels: ['jlpt:n5', 'grade:1', 'radical:9'],
|
|
|
|
|
|
);
|
|
|
|
|
|
final yasumu = character(
|
|
|
|
|
|
'休',
|
|
|
|
|
|
meanings: ['rest'],
|
|
|
|
|
|
labels: ['jlpt:n5', 'grade:1', 'radical:9'],
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
/// An instance whose KANJIDIC2 files 人 and 休 under the radical 人.
|
|
|
|
|
|
FakeDictionaryGateway withRadicals() => FakeDictionaryGateway(
|
|
|
|
|
|
available: [jmdict, kanjidic],
|
|
|
|
|
|
words: [taberu, hito, yasumu, shoku],
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
Future<void> openRadicals(
|
|
|
|
|
|
WidgetTester tester,
|
|
|
|
|
|
FakeDictionaryGateway dictionaries,
|
|
|
|
|
|
) async {
|
|
|
|
|
|
await show(
|
|
|
|
|
|
tester,
|
|
|
|
|
|
dictionaries: dictionaries,
|
|
|
|
|
|
location: RoutePaths.dictionary('japanese'),
|
|
|
|
|
|
);
|
|
|
|
|
|
await tester.tap(find.text('Radicals'));
|
|
|
|
|
|
await settle(tester);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
testWidgets('the radicals by their strokes; one lists the kanji written '
|
|
|
|
|
|
'with it, to add, and leads back to them all', (tester) async {
|
|
|
|
|
|
await openRadicals(tester, withRadicals());
|
|
|
|
|
|
|
|
|
|
|
|
expect(find.text('1 stroke'), findsOneWidget);
|
|
|
|
|
|
expect(find.text('2 strokes'), findsOneWidget);
|
|
|
|
|
|
await tester.tap(find.text('9'));
|
|
|
|
|
|
await settle(tester);
|
|
|
|
|
|
|
|
|
|
|
|
expect(find.text('man'), findsOneWidget);
|
|
|
|
|
|
expect(find.text('Radical 9 · 2 strokes · 2 kanji'), findsOneWidget);
|
|
|
|
|
|
expect(find.text('食'), findsNothing);
|
|
|
|
|
|
await tester.tap(find.text('Add').first);
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
expect(find.text('Added “人”.'), findsOneWidget);
|
|
|
|
|
|
expect(
|
|
|
|
|
|
(await cards.cardsOf('japanese')).single.content.entry,
|
|
|
|
|
|
(source: 'kanjidic', id: '人'),
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
await tester.tap(find.byTooltip('All radicals'));
|
|
|
|
|
|
await settle(tester);
|
|
|
|
|
|
expect(find.text('2 strokes'), findsOneWidget);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
testWidgets("a radical's kanji the instance can't give now are asked "
|
|
|
|
|
|
'for again', (tester) async {
|
|
|
|
|
|
final dictionaries = withRadicals();
|
|
|
|
|
|
await openRadicals(tester, dictionaries);
|
|
|
|
|
|
dictionaries.fail = Exception('offline');
|
|
|
|
|
|
await tester.tap(find.text('9'));
|
|
|
|
|
|
await settle(tester);
|
|
|
|
|
|
|
|
|
|
|
|
expect(
|
|
|
|
|
|
find.text("The dictionary can't be reached right now."),
|
|
|
|
|
|
findsOneWidget,
|
|
|
|
|
|
);
|
|
|
|
|
|
dictionaries.fail = null;
|
|
|
|
|
|
await tester.tap(find.text('Retry'));
|
|
|
|
|
|
await settle(tester);
|
|
|
|
|
|
expect(find.text('Radical 9 · 2 strokes · 2 kanji'), findsOneWidget);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
testWidgets('a radical KANJIDIC2 files nothing under on this instance '
|
|
|
|
|
|
'says so', (tester) async {
|
|
|
|
|
|
await openRadicals(tester, withKanji());
|
|
|
|
|
|
await tester.tap(find.text('9'));
|
|
|
|
|
|
await settle(tester);
|
|
|
|
|
|
|
|
|
|
|
|
expect(
|
|
|
|
|
|
find.text(
|
|
|
|
|
|
"The instance's dictionary has no kanji under this radical "
|
|
|
|
|
|
'yet.',
|
|
|
|
|
|
),
|
|
|
|
|
|
findsOneWidget,
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-10-09 16:05:05 +02:00
|
|
|
|
testWidgets("About names whose work the instance's dictionaries and the "
|
|
|
|
|
|
"app's own data are", (
|
feat(app): the dictionary in the app: look words up, add them as cards
A studyset's dictionary follows from its language, as decided: the
instance's dictionary of its front's language, with meanings in its
back's (else English). Nothing to attach or detach; nothing of it shows
where there is none.
- Add material › From the dictionary: search a word (kanji, kana,
romaji, meaning) and add it as a flashcard, or add a whole level
(JLPT N5…N1, common words) at once, asked first above 500 words, its
progress shown. Words already in the studyset show as such and are
left out. Each card is a vocabulary card (headword, reading, senses
with their codes), tagged `jlpt-n5` and with its word class, and
marked as imported from the dictionary.
- Look up beside a vocabulary card's front fills the card with the word
chosen, keeping its hints, explanation and tags.
- A card keeps the dictionary entry it was made from (card schema 6:
`dictionary` {source, id}), to be found again and not added twice.
- The dictionary's credits show at its foot and in About, as its licence
asks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 01:33:12 +02:00
|
|
|
|
tester,
|
|
|
|
|
|
) async {
|
|
|
|
|
|
await pumpApp(
|
|
|
|
|
|
tester,
|
|
|
|
|
|
testDependencies(dictionaryGateway: FakeDictionaryGateway()),
|
|
|
|
|
|
location: RoutePaths.settings,
|
|
|
|
|
|
);
|
|
|
|
|
|
await tester.scrollUntilVisible(
|
|
|
|
|
|
find.text('About Halkyon Learn'),
|
|
|
|
|
|
200,
|
|
|
|
|
|
scrollable: find.byType(Scrollable).first,
|
|
|
|
|
|
);
|
feat(explore): how many learn it here, opt-in; copies published within their licence
Explore says how many people learn a published studyset on this instance
(ADR 0005, rule 8): shown, never ranked, counted daily by the server from
the people with an identity who chose to be counted (Settings, Privacy;
settings schema 8) and studied a copy on 3 or more days of the last 30,
rounded down from 5 on, so that nobody can be told apart. Only a number
the instance computes itself counts; votes count for nothing. The author
sees the same rounded number in the Publish sheet.
A copy of a published studyset is published within the original's
licence: never under "no derivatives", under a licence that keeps
adaptations under it only under that one, in every studyset of the copy.
Its releases say what they are based on, and Explore shows it.
ADR 0005 gets an addendum with stage 3's decisions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-10 00:18:29 +02:00
|
|
|
|
// Wholly in view: scrolling stops once any of it shows.
|
|
|
|
|
|
await tester.ensureVisible(find.text('About Halkyon Learn'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
feat(app): the dictionary in the app: look words up, add them as cards
A studyset's dictionary follows from its language, as decided: the
instance's dictionary of its front's language, with meanings in its
back's (else English). Nothing to attach or detach; nothing of it shows
where there is none.
- Add material › From the dictionary: search a word (kanji, kana,
romaji, meaning) and add it as a flashcard, or add a whole level
(JLPT N5…N1, common words) at once, asked first above 500 words, its
progress shown. Words already in the studyset show as such and are
left out. Each card is a vocabulary card (headword, reading, senses
with their codes), tagged `jlpt-n5` and with its word class, and
marked as imported from the dictionary.
- Look up beside a vocabulary card's front fills the card with the word
chosen, keeping its hints, explanation and tags.
- A card keeps the dictionary entry it was made from (card schema 6:
`dictionary` {source, id}), to be found again and not added twice.
- The dictionary's credits show at its foot and in About, as its licence
asks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 01:33:12 +02:00
|
|
|
|
await tester.tap(find.text('About Halkyon Learn'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
|
|
|
|
|
|
expect(find.text('Dictionaries on this instance'), findsOneWidget);
|
|
|
|
|
|
expect(
|
|
|
|
|
|
find.text(
|
|
|
|
|
|
'JMdict: Electronic Dictionary Research and Development Group '
|
|
|
|
|
|
'(EDRDG), CC-BY-SA-4.0',
|
|
|
|
|
|
),
|
|
|
|
|
|
findsOneWidget,
|
|
|
|
|
|
);
|
2026-10-09 16:05:05 +02:00
|
|
|
|
// The app's own data, KanjiVG's strokes, as its licence asks.
|
|
|
|
|
|
expect(find.text('Data in the app'), findsOneWidget);
|
|
|
|
|
|
expect(find.text('KanjiVG: Ulrich Apel, CC-BY-SA-3.0'), findsOneWidget);
|
|
|
|
|
|
expect(find.text('https://kanjivg.tagaini.net'), findsOneWidget);
|
feat(app): the dictionary in the app: look words up, add them as cards
A studyset's dictionary follows from its language, as decided: the
instance's dictionary of its front's language, with meanings in its
back's (else English). Nothing to attach or detach; nothing of it shows
where there is none.
- Add material › From the dictionary: search a word (kanji, kana,
romaji, meaning) and add it as a flashcard, or add a whole level
(JLPT N5…N1, common words) at once, asked first above 500 words, its
progress shown. Words already in the studyset show as such and are
left out. Each card is a vocabulary card (headword, reading, senses
with their codes), tagged `jlpt-n5` and with its word class, and
marked as imported from the dictionary.
- Look up beside a vocabulary card's front fills the card with the word
chosen, keeping its hints, explanation and tags.
- A card keeps the dictionary entry it was made from (card schema 6:
`dictionary` {source, id}), to be found again and not added twice.
- The dictionary's credits show at its foot and in About, as its licence
asks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 01:33:12 +02:00
|
|
|
|
});
|
|
|
|
|
|
}
|