feat(app): the UI foundation: theme, tabs and routes, strings
The app gets its look and navigation before more features land on them.
- Theme (core/theme/): hand-written light and dark Material 3 colour schemes around
cobalt and copper; ThemeExtras for the colours Material has no role for (states, the
four FSRS ratings); the 20 colours a person can give content. Tests hold text to WCAG
AA contrast on each palette colour and on the dark theme's primary colour and surface.
- Layout and shared widgets: Material 3's window-size classes (core/layout/);
SectionCard, AppLoadingIndicator, confirmDestructiveAction and FoundById
(core/widget/). Lucide icons.
- Navigation with go_router: four tabs (Home, Library, Explore, Profile), each keeping
its own stack of screens, as a navigation rail with the app's icon from 600 dp and as
a navigation bar below; choosing the open tab again returns to its first screen. A
studyset's screen sits in the library; studying, cards and settings cover the tabs.
- Routes carry ids: FoundById finds the item in the repository's live list, so a
studyset deleted on another device, or an old link, says it no longer exists. A
screen that deletes its own item keeps showing it while it closes, rather than
flashing that message.
- Every person gets an app of their own (PersonApp, lib/src/app/): a
MaterialApp.router with its own GoRouter and PersonScope around the navigator, built
by SessionGate inside the person's keyed subtree. Screens read the person's
repositories wherever they are built, so the static route() helpers and the listener
that closed every pushed screen on a change of person are gone: a change of person
now disposes the whole router and starts again at home.
- The account dialog becomes the Profile tab, with Settings (sign out, delete the
account, About) behind a gear. A waiting GitHub sign-in now keeps waiting while
another tab shows; a change of person still cancels it.
- The new screens' strings come from Flutter's gen-l10n (English for now). The
generated files are committed, left out of analysis and formatting, and written
anew by `flutter pub get`, so CI's check of generated code also catches stale ones.
Widget tests now start the whole app (test/support/pump_app.dart) at the screen's path
and move through it as a person would, by tabs and taps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:18:31 +02:00
// ignore: unused_import
import ' package:intl/intl.dart ' as intl ;
import ' app_localizations.dart ' ;
// ignore_for_file: type=lint
/// The translations for English (`en`).
class AppLocalizationsEn extends AppLocalizations {
AppLocalizationsEn ( [ String locale = ' en ' ] ) : super ( locale ) ;
@ override
String get appTitle = > ' Halkyon Learn ' ;
@ override
String get shellTabHome = > ' Home ' ;
@ override
String get shellTabLibrary = > ' Library ' ;
@ override
String get shellTabExplore = > ' Explore ' ;
@ override
String get shellTabProfile = > ' Profile ' ;
@ override
String get homeTitle = > ' Home ' ;
@ override
String get libraryTitle = > ' Library ' ;
feat(app): the library: a grid of studyset cards, search and archive
The library tab becomes a grid of studyset cards (up to 400 dp wide, all
equally tall), starred ones first, and the archive as a section of its own
below, closed until opened and counting what rests there. It lists the top
level: studysets without a parent, those whose parent is gone, and those in
a cycle (ADR 0004). Each card's progress follows the studysets and every
change of a card, here or synced, and is worked out again after a study
session; a slow, older working out never overwrites a newer one.
- Search: a field below the app bar filters by title and secondary title,
opens the archive while it finds something there, and says when nothing
matches; closing it shows everything again.
- Creating and editing: a sheet with the title, the secondary title (typed
in capitals, as every surface shows it) and the colour, picked from the
palette's twenty, whose names are localised; a new studyset is cobalt,
and an existing one keeps its colour, even one only a newer app version
knows, until another is picked. Create waits for a title. Closing the
sheet with changes asks first. It can't be swiped away: a bottom sheet
swiped away closes without asking its PopScope (Flutter issue 152120,
still open), so such sheets have no drag handle and close by their close
button, the back gesture or a tap beside them, which all ask.
- Archiving doesn't ask, since nothing is lost; it offers to undo, as does
unarchiving. Deleting asks, then deletes the studyset with its cards, each
blanked first.
- The create button floats in compact windows and slides away while
reading onwards; from medium windows on it sits in the app bar.
The old list and its title-only dialog are gone; the studyset screen's
tests create studysets through the sheet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:53:51 +02:00
@ override
feat(app): appearance, language and version in Settings, and Settings from Home
Settings gets a card for how this device shows the app: its appearance
(as the device, light or dark) and its language (the device's, or one
the app speaks, each named in its own language), side by side from
medium windows on, as two outlined dropdowns. Both stay on the device
rather than following the person: they are kept in
<app support>/device_settings.json (written to a temporary file and
renamed into place), read before the app shows, so its first frame
already looks as chosen, and a file that can't be read falls back to
what the device does. DeviceSettingsBloc sits above SessionGate's keyed
subtree, since the settings outlive every person, and both the plain
app and the person's app take their theme mode and locale from it.
At the bottom of Settings, the release and its build, read once at
start together with the build the server is asked about.
Home has a Settings cog beside the streak, with the sync trouble dot as
on Profile. The library's create button reads "Create studyset", and
Delete is red in every menu that offers it: the studyset's card, screen
and rail, and the composer's draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-07 01:43:28 +02:00
String get libraryCreateStudyset = > ' Create studyset ' ;
feat(app): the library: a grid of studyset cards, search and archive
The library tab becomes a grid of studyset cards (up to 400 dp wide, all
equally tall), starred ones first, and the archive as a section of its own
below, closed until opened and counting what rests there. It lists the top
level: studysets without a parent, those whose parent is gone, and those in
a cycle (ADR 0004). Each card's progress follows the studysets and every
change of a card, here or synced, and is worked out again after a study
session; a slow, older working out never overwrites a newer one.
- Search: a field below the app bar filters by title and secondary title,
opens the archive while it finds something there, and says when nothing
matches; closing it shows everything again.
- Creating and editing: a sheet with the title, the secondary title (typed
in capitals, as every surface shows it) and the colour, picked from the
palette's twenty, whose names are localised; a new studyset is cobalt,
and an existing one keeps its colour, even one only a newer app version
knows, until another is picked. Create waits for a title. Closing the
sheet with changes asks first. It can't be swiped away: a bottom sheet
swiped away closes without asking its PopScope (Flutter issue 152120,
still open), so such sheets have no drag handle and close by their close
button, the back gesture or a tap beside them, which all ask.
- Archiving doesn't ask, since nothing is lost; it offers to undo, as does
unarchiving. Deleting asks, then deletes the studyset with its cards, each
blanked first.
- The create button floats in compact windows and slides away while
reading onwards; from medium windows on it sits in the app bar.
The old list and its title-only dialog are gone; the studyset screen's
tests create studysets through the sheet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:53:51 +02:00
@ override
String get libraryEmpty = > ' No studysets yet ' ;
@ override
String get libraryUnreadable = > ' Couldn \' t read your studysets. ' ;
@ override
String get libraryArchivedSection = > ' Archived ' ;
@ override
String get librarySearchTooltip = > ' Search ' ;
@ override
String get librarySearchHint = > ' Search studysets ' ;
@ override
String get librarySearchClearTooltip = > ' Clear query ' ;
@ override
String get librarySearchCloseTooltip = > ' Close search ' ;
@ override
String get librarySearchNoMatches = > ' No studysets match your search ' ;
@ override
String get libraryStudysetArchived = > ' Studyset archived ' ;
@ override
String get libraryStudysetUnarchived = > ' Studyset unarchived ' ;
2026-10-01 08:55:39 +02:00
@ override
String get libraryStudysetDeleted = > ' Studyset deleted ' ;
feat(app): the library: a grid of studyset cards, search and archive
The library tab becomes a grid of studyset cards (up to 400 dp wide, all
equally tall), starred ones first, and the archive as a section of its own
below, closed until opened and counting what rests there. It lists the top
level: studysets without a parent, those whose parent is gone, and those in
a cycle (ADR 0004). Each card's progress follows the studysets and every
change of a card, here or synced, and is worked out again after a study
session; a slow, older working out never overwrites a newer one.
- Search: a field below the app bar filters by title and secondary title,
opens the archive while it finds something there, and says when nothing
matches; closing it shows everything again.
- Creating and editing: a sheet with the title, the secondary title (typed
in capitals, as every surface shows it) and the colour, picked from the
palette's twenty, whose names are localised; a new studyset is cobalt,
and an existing one keeps its colour, even one only a newer app version
knows, until another is picked. Create waits for a title. Closing the
sheet with changes asks first. It can't be swiped away: a bottom sheet
swiped away closes without asking its PopScope (Flutter issue 152120,
still open), so such sheets have no drag handle and close by their close
button, the back gesture or a tap beside them, which all ask.
- Archiving doesn't ask, since nothing is lost; it offers to undo, as does
unarchiving. Deleting asks, then deletes the studyset with its cards, each
blanked first.
- The create button floats in compact windows and slides away while
reading onwards; from medium windows on it sits in the app bar.
The old list and its title-only dialog are gone; the studyset screen's
tests create studysets through the sheet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:53:51 +02:00
@ override
String get libraryDeleteTitle = > ' Delete studyset? ' ;
@ override
String get libraryDeleteMessage = >
feat(app): the studyset screen with its topics, the tree's own screen
A studyset's screen, as decided with the tree (2026-09-30/10-01):
- A breadcrumb from the library down to the studyset; its steps go up
(replacing the location, so back leads further up), and steps that
don't fit fold into a "…" listing them, nearest first.
- From expanded windows on, two columns: the description over the topics,
the flashcards beside them, Study and Add material in the app bar. Below
that, the topics are in a drawer, opened from a bar at the bottom that
also holds Study and Add material and slides away while reading onwards;
the back button stays where a drawer would have taken its place.
- The topics: "All topics" for the whole studyset, then the studysets
inside it in their order, each with the study it invites, what it holds
and its options (edit, delete), opening on top of this screen; a switch
rearranges them by dragging; "New sub-studyset" creates one inside.
- Study covers the studyset with everything inside it ("Learn 12" while
something is due). Add material offers flashcards for now.
- Options: Edit (the sheet, now with the description, edited in the whole
window, and for a studyset inside another the colour of the one it is
in, which a new one takes at first), Move to… (the tree and the top
level, without the studyset itself and what is inside it), Archive or
Unarchive with Undo, Delete with Undo, which closes the screen.
- The description cut short when long, the whole a tap away; the licence
as the last line, linking to its text where that is known.
The list's bloc, scoped to a parent, lists its children, works out the
studyset's own progress, creates inside it, reorders and moves. The card
list leaves adding and studying to the screen. The inline editing of the
title and description, and its bloc, are gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 12:59:20 +02:00
' It is deleted with everything inside it and all their cards, on all your devices. You can undo this for a few seconds. ' ;
feat(app): the library: a grid of studyset cards, search and archive
The library tab becomes a grid of studyset cards (up to 400 dp wide, all
equally tall), starred ones first, and the archive as a section of its own
below, closed until opened and counting what rests there. It lists the top
level: studysets without a parent, those whose parent is gone, and those in
a cycle (ADR 0004). Each card's progress follows the studysets and every
change of a card, here or synced, and is worked out again after a study
session; a slow, older working out never overwrites a newer one.
- Search: a field below the app bar filters by title and secondary title,
opens the archive while it finds something there, and says when nothing
matches; closing it shows everything again.
- Creating and editing: a sheet with the title, the secondary title (typed
in capitals, as every surface shows it) and the colour, picked from the
palette's twenty, whose names are localised; a new studyset is cobalt,
and an existing one keeps its colour, even one only a newer app version
knows, until another is picked. Create waits for a title. Closing the
sheet with changes asks first. It can't be swiped away: a bottom sheet
swiped away closes without asking its PopScope (Flutter issue 152120,
still open), so such sheets have no drag handle and close by their close
button, the back gesture or a tap beside them, which all ask.
- Archiving doesn't ask, since nothing is lost; it offers to undo, as does
unarchiving. Deleting asks, then deletes the studyset with its cards, each
blanked first.
- The create button floats in compact windows and slides away while
reading onwards; from medium windows on it sits in the app bar.
The old list and its title-only dialog are gone; the studyset screen's
tests create studysets through the sheet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:53:51 +02:00
@ override
String get libraryDeleteConfirm = > ' Delete ' ;
@ override
String get studysetSheetCreateTitle = > ' Create studyset ' ;
@ override
String get studysetSheetEditTitle = > ' Edit studyset ' ;
@ override
String get studysetSheetCreate = > ' Create ' ;
@ override
String get studysetSheetSave = > ' Save ' ;
@ override
String get studysetSheetTitleLabel = > ' Title ' ;
@ override
String get studysetSheetSecondaryLabel = > ' Secondary title ' ;
@ override
String get studysetSheetSecondaryHelper = >
' Shown as a small label above the title. ' ;
@ override
feat(app): the app speaks British and American English and German
Settings offers English (UK), English (US) and Deutsch after the
device's language, each named in its own language. app_en.arb, the
template, is now British English (colour, centre) and the fallback for
any language the app doesn't speak; app_en_US.arb overrides the five
messages American English spells differently, and app_en_GB.arb is
empty, there so that Material's texts and dates are British too. The
German translation says "du" and keeps one set of terms throughout
(Lernset, Karteikarte, Lernpfad; Anki's own Stapel and Unterstapel in
the Anki import). arb_files_test checks that German has every message
of the template and that every translation keeps its placeholders.
resolveAppLocale picks the first language the device prefers that the
app speaks, in its country's variant where there is one; other English
is British, and a country alone never picks a language (Spanish in the
United States isn't American English), which Flutter's own resolution
would do.
Both MaterialApps take material_ui's GlobalMaterialLocalizations
delegates: gen-l10n's list brings flutter_localizations', which serve
only package:flutter/material.dart, so material_ui's widgets found no
Material texts in German and threw.
The editor's own text blocks show our placeholders while empty (list
item, to-do, quote, heading, and the desktop's / hint): the editor's
own follow a locale of their own, process-wide, in few languages. The
last texts written into widgets (the person's data failing to open,
the cards failing to read, the About dialog) moved into the arb files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-07 02:01:01 +02:00
String get studysetSheetColor = > ' Colour ' ;
feat(app): the library: a grid of studyset cards, search and archive
The library tab becomes a grid of studyset cards (up to 400 dp wide, all
equally tall), starred ones first, and the archive as a section of its own
below, closed until opened and counting what rests there. It lists the top
level: studysets without a parent, those whose parent is gone, and those in
a cycle (ADR 0004). Each card's progress follows the studysets and every
change of a card, here or synced, and is worked out again after a study
session; a slow, older working out never overwrites a newer one.
- Search: a field below the app bar filters by title and secondary title,
opens the archive while it finds something there, and says when nothing
matches; closing it shows everything again.
- Creating and editing: a sheet with the title, the secondary title (typed
in capitals, as every surface shows it) and the colour, picked from the
palette's twenty, whose names are localised; a new studyset is cobalt,
and an existing one keeps its colour, even one only a newer app version
knows, until another is picked. Create waits for a title. Closing the
sheet with changes asks first. It can't be swiped away: a bottom sheet
swiped away closes without asking its PopScope (Flutter issue 152120,
still open), so such sheets have no drag handle and close by their close
button, the back gesture or a tap beside them, which all ask.
- Archiving doesn't ask, since nothing is lost; it offers to undo, as does
unarchiving. Deleting asks, then deletes the studyset with its cards, each
blanked first.
- The create button floats in compact windows and slides away while
reading onwards; from medium windows on it sits in the app bar.
The old list and its title-only dialog are gone; the studyset screen's
tests create studysets through the sheet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:53:51 +02:00
@ override
String get studysetSheetDiscardTitle = > ' Discard changes? ' ;
@ override
String get studysetSheetDiscardMessage = >
' You have unsaved changes to this studyset. Discard them and close the form? ' ;
@ override
String get studysetSheetDiscardConfirm = > ' Discard ' ;
@ override
String paletteColorName ( String name ) {
String _temp0 = intl . Intl . selectLogic (
name ,
{
' carrot ' : ' Carrot ' ,
' cerise ' : ' Cerise ' ,
' cobalt ' : ' Cobalt ' ,
' copper ' : ' Copper ' ,
' enzian ' : ' Enzian ' ,
' fern ' : ' Fern ' ,
' forest ' : ' Forest ' ,
' ocean ' : ' Ocean ' ,
' olive ' : ' Olive ' ,
' pacific ' : ' Pacific ' ,
' pickle ' : ' Pickle ' ,
' pine ' : ' Pine ' ,
' pink ' : ' Pink ' ,
' plum ' : ' Plum ' ,
' purple ' : ' Purple ' ,
' reef ' : ' Reef ' ,
' sahara ' : ' Sahara ' ,
' slateblue ' : ' Slate blue ' ,
' soil ' : ' Soil ' ,
' strawberry ' : ' Strawberry ' ,
' other ' : ' $ name ' ,
} ,
) ;
return ' $ _temp0 ' ;
}
@ override
String colorPickerLabel ( String color ) {
feat(app): the app speaks British and American English and German
Settings offers English (UK), English (US) and Deutsch after the
device's language, each named in its own language. app_en.arb, the
template, is now British English (colour, centre) and the fallback for
any language the app doesn't speak; app_en_US.arb overrides the five
messages American English spells differently, and app_en_GB.arb is
empty, there so that Material's texts and dates are British too. The
German translation says "du" and keeps one set of terms throughout
(Lernset, Karteikarte, Lernpfad; Anki's own Stapel and Unterstapel in
the Anki import). arb_files_test checks that German has every message
of the template and that every translation keeps its placeholders.
resolveAppLocale picks the first language the device prefers that the
app speaks, in its country's variant where there is one; other English
is British, and a country alone never picks a language (Spanish in the
United States isn't American English), which Flutter's own resolution
would do.
Both MaterialApps take material_ui's GlobalMaterialLocalizations
delegates: gen-l10n's list brings flutter_localizations', which serve
only package:flutter/material.dart, so material_ui's widgets found no
Material texts in German and threw.
The editor's own text blocks show our placeholders while empty (list
item, to-do, quote, heading, and the desktop's / hint): the editor's
own follow a locale of their own, process-wide, in few languages. The
last texts written into widgets (the person's data failing to open,
the cards failing to read, the About dialog) moved into the arb files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-07 02:01:01 +02:00
return ' Colour: $ color ' ;
feat(app): the library: a grid of studyset cards, search and archive
The library tab becomes a grid of studyset cards (up to 400 dp wide, all
equally tall), starred ones first, and the archive as a section of its own
below, closed until opened and counting what rests there. It lists the top
level: studysets without a parent, those whose parent is gone, and those in
a cycle (ADR 0004). Each card's progress follows the studysets and every
change of a card, here or synced, and is worked out again after a study
session; a slow, older working out never overwrites a newer one.
- Search: a field below the app bar filters by title and secondary title,
opens the archive while it finds something there, and says when nothing
matches; closing it shows everything again.
- Creating and editing: a sheet with the title, the secondary title (typed
in capitals, as every surface shows it) and the colour, picked from the
palette's twenty, whose names are localised; a new studyset is cobalt,
and an existing one keeps its colour, even one only a newer app version
knows, until another is picked. Create waits for a title. Closing the
sheet with changes asks first. It can't be swiped away: a bottom sheet
swiped away closes without asking its PopScope (Flutter issue 152120,
still open), so such sheets have no drag handle and close by their close
button, the back gesture or a tap beside them, which all ask.
- Archiving doesn't ask, since nothing is lost; it offers to undo, as does
unarchiving. Deleting asks, then deletes the studyset with its cards, each
blanked first.
- The create button floats in compact windows and slides away while
reading onwards; from medium windows on it sits in the app bar.
The old list and its title-only dialog are gone; the studyset screen's
tests create studysets through the sheet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:53:51 +02:00
}
feat(app): the library's studyset card
A studyset in the library: a bar in its colour, its title with the secondary
title as a kicker, what it holds, and beside the title the action it
invites inside a ring its mature cards fill, with a badge counting the due
cards (at most "99+", growing inwards so it never passes the card's edge).
The action is a review when reviews are due, studying when only new cards
are, and adding content otherwise, which opens the studyset: the card only
invites a session that has something to study. A long press stars it, as
does the star, which a hovering pointer reveals and which stays while
starred; the options edit, archive or unarchive, and delete it. An archived
studyset rests: dimmed, neutral, paused. Until its progress is known, a
muted bar stands in for "No content yet", which it might not be.
A starred star is amber, dark enough in the light theme to stand out from
the cards as a graphic must (3:1), a colour the theme extras now hold. It is
Material's filled star: Lucide has no filled one, and the fill is what says
the star is on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:40:52 +02:00
@ override
String studysetCountFlashcards ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' $ count flashcards ' ,
one: ' 1 flashcard ' ,
) ;
return ' $ _temp0 ' ;
}
feat(app): notes in the studyset screen, written in the whole window
- A Notes tab beside Flashcards lists a studyset's notes in order: the
title ("Untitled note" without one) and when each was last edited
("Edited 5 minutes ago", by the calendar from yesterday on, the date
after a week); Edit and Delete behind its options. Deleting asks,
then offers Undo for a moment (`NoteDeletions`). With none, the tab
says so.
- "Create a note" in Add material opens a new note over the tabs; so
does a note of the list. The note: a title, its tags once it exists,
and its text at reading width; the app bar says how saving stands.
- `NoteEditorBloc` saves after 5 seconds without typing, when the app
goes to the background, and when the note is closed (the bloc saves
as it closes, so leaving never waits, and the write outlives the
screen). Each save builds on the revisions the editor started from;
another device's change takes over a note with nothing unsaved, and
merges with one that has. A note changed differently on two devices
is read-only until the person keeps a version of each field in
conflict. A new note is created by its first save, never when left
blank; a note deleted elsewhere says it no longer exists.
- The editor gains a format bar, docked below it while it has the
focus (`RichContentEditor(formatBar: true)`): alignment, bold,
italic, underline, superscript and subscript. It takes no focus, so
the selection stays. Superscript and subscript are inline marks of
their own, drawn by the font's raised and lowered glyphs in editors
and read-only text alike.
- The library's and the topics' counts include notes (a studyset with
what is inside it, as for cards).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 22:30:53 +02:00
@ override
String studysetCountNotes ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' $ count notes ' ,
one: ' 1 note ' ,
) ;
return ' $ _temp0 ' ;
}
feat(app): the library's studyset card
A studyset in the library: a bar in its colour, its title with the secondary
title as a kicker, what it holds, and beside the title the action it
invites inside a ring its mature cards fill, with a badge counting the due
cards (at most "99+", growing inwards so it never passes the card's edge).
The action is a review when reviews are due, studying when only new cards
are, and adding content otherwise, which opens the studyset: the card only
invites a session that has something to study. A long press stars it, as
does the star, which a hovering pointer reveals and which stays while
starred; the options edit, archive or unarchive, and delete it. An archived
studyset rests: dimmed, neutral, paused. Until its progress is known, a
muted bar stands in for "No content yet", which it might not be.
A starred star is amber, dark enough in the light theme to stand out from
the cards as a graphic must (3:1), a colour the theme extras now hold. It is
Material's filled star: Lucide has no filled one, and the fill is what says
the star is on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:40:52 +02:00
@ override
String get studysetNoContent = > ' No content yet ' ;
@ override
String get studysetActionReview = > ' Review ' ;
@ override
String get studysetActionStudy = > ' Study ' ;
@ override
String get studysetActionAdd = > ' Add content ' ;
@ override
String get studysetArchived = > ' Archived ' ;
@ override
String get studysetStar = > ' Star ' ;
@ override
String get studysetUnstar = > ' Unstar ' ;
@ override
String get studysetOptions = > ' Options ' ;
@ override
String get studysetMenuEdit = > ' Edit ' ;
@ override
String get studysetMenuArchive = > ' Archive ' ;
@ override
String get studysetMenuUnarchive = > ' Unarchive ' ;
@ override
String get studysetMenuDelete = > ' Delete ' ;
feat(app): the studyset screen with its topics, the tree's own screen
A studyset's screen, as decided with the tree (2026-09-30/10-01):
- A breadcrumb from the library down to the studyset; its steps go up
(replacing the location, so back leads further up), and steps that
don't fit fold into a "…" listing them, nearest first.
- From expanded windows on, two columns: the description over the topics,
the flashcards beside them, Study and Add material in the app bar. Below
that, the topics are in a drawer, opened from a bar at the bottom that
also holds Study and Add material and slides away while reading onwards;
the back button stays where a drawer would have taken its place.
- The topics: "All topics" for the whole studyset, then the studysets
inside it in their order, each with the study it invites, what it holds
and its options (edit, delete), opening on top of this screen; a switch
rearranges them by dragging; "New sub-studyset" creates one inside.
- Study covers the studyset with everything inside it ("Learn 12" while
something is due). Add material offers flashcards for now.
- Options: Edit (the sheet, now with the description, edited in the whole
window, and for a studyset inside another the colour of the one it is
in, which a new one takes at first), Move to… (the tree and the top
level, without the studyset itself and what is inside it), Archive or
Unarchive with Undo, Delete with Undo, which closes the screen.
- The description cut short when long, the whole a tap away; the licence
as the last line, linking to its text where that is known.
The list's bloc, scoped to a parent, lists its children, works out the
studyset's own progress, creates inside it, reorders and moves. The card
list leaves adding and studying to the screen. The inline editing of the
title and description, and its bloc, are gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 12:59:20 +02:00
@ override
String get studysetMenuMove = > ' Move to… ' ;
@ override
String get studysetTabFlashcards = > ' Flashcards ' ;
feat(app): notes in the studyset screen, written in the whole window
- A Notes tab beside Flashcards lists a studyset's notes in order: the
title ("Untitled note" without one) and when each was last edited
("Edited 5 minutes ago", by the calendar from yesterday on, the date
after a week); Edit and Delete behind its options. Deleting asks,
then offers Undo for a moment (`NoteDeletions`). With none, the tab
says so.
- "Create a note" in Add material opens a new note over the tabs; so
does a note of the list. The note: a title, its tags once it exists,
and its text at reading width; the app bar says how saving stands.
- `NoteEditorBloc` saves after 5 seconds without typing, when the app
goes to the background, and when the note is closed (the bloc saves
as it closes, so leaving never waits, and the write outlives the
screen). Each save builds on the revisions the editor started from;
another device's change takes over a note with nothing unsaved, and
merges with one that has. A note changed differently on two devices
is read-only until the person keeps a version of each field in
conflict. A new note is created by its first save, never when left
blank; a note deleted elsewhere says it no longer exists.
- The editor gains a format bar, docked below it while it has the
focus (`RichContentEditor(formatBar: true)`): alignment, bold,
italic, underline, superscript and subscript. It takes no focus, so
the selection stays. Superscript and subscript are inline marks of
their own, drawn by the font's raised and lowered glyphs in editors
and read-only text alike.
- The library's and the topics' counts include notes (a studyset with
what is inside it, as for cards).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 22:30:53 +02:00
@ override
String get studysetTabNotes = > ' Notes ' ;
feat(app): learning paths in the studyset screen: worked through and written
- A Paths tab before Flashcards and Notes: each path with its title and
secondary title, dimmed once completed; Edit, Reset progress (with an
undo) and Delete (with an undo) behind its options. The studyset opens
on its paths while one is unfinished, on its flashcards otherwise.
- "Create a path" in Add material asks for the titles in a sheet and
opens the new path to write; "Import a path (JSON)" reads a file in
the import format, says what came in and how many blocks were left out
(images and animations: the app never fetches from the web).
- The player: one step at a time at reading width, a bar of the steps
above, Back and "Check answers" / "Continue" / "Finish" below. It
opens at the first unfinished step; an exercise passes only once
answered right, as often as it takes; after a check, only the
learner's own picks are marked, with the author's feedback; sources
behind a chip; a finish screen leading to the next path. Words and
items are dragged into place, or tapped into place for phones.
- Writing: the path's screen (titles, description, tags, steps added by
kind, reordered, deleted with an undo) and the step's (title, text
with the format bar, its kind's exercise, feedback for right and
wrong in the whole window, sources). The step saves on what it
started from, as notes do, read-only while devices disagree until a
version is kept.
- The library and the topics count paths; steps done count on the
dashboard, for the streak and the activity.
- The rich-text screen for a studyset's description moves to core, for
the steps' feedback too, and gains the format bar.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-03 09:25:08 +02:00
@ override
String get studysetTabPaths = > ' Paths ' ;
@ override
String get studysetCreatePath = > ' Create a path ' ;
@ override
String get pathImportMenu = > ' Import a path (JSON) ' ;
@ override
String get pathImportFileType = > ' Learning path (JSON) ' ;
@ override
String pathImported ( String title , int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' Imported \" $ title \" with $ count steps. ' ,
one: ' Imported \" $ title \" with 1 step. ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String pathImportSkipped ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other:
' $ count blocks were left out: images and animations aren \' t imported. ' ,
one: ' 1 block was left out: images and animations aren \' t imported. ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String pathImportFailed ( String reason ) {
return ' Couldn \' t import this file. $ reason ' ;
}
@ override
String get pathImportUnreadable = > ' It isn \' t text in UTF-8. ' ;
@ override
String get pathsEmpty = > ' No paths yet ' ;
@ override
String get pathsEmptyHint = > ' A path leads through a lesson, step by step. ' ;
@ override
String get pathOptions = > ' Path options ' ;
@ override
String get pathEdit = > ' Edit ' ;
@ override
String get pathResetProgress = > ' Reset progress ' ;
@ override
String get pathProgressReset = > ' Progress reset ' ;
@ override
String get pathDelete = > ' Delete ' ;
@ override
String get pathDeleteTitle = > ' Delete path? ' ;
@ override
String get pathDeleteMessage = >
' This path and its steps will be removed from the studyset. You can undo this for a few seconds. ' ;
@ override
String get pathDeleteConfirm = > ' Delete ' ;
@ override
String get pathDeleted = > ' Path deleted ' ;
@ override
String get pathCreateTitle = > ' New path ' ;
@ override
String get pathCreate = > ' Create ' ;
@ override
String get pathTitleLabel = > ' Title ' ;
@ override
String get pathTitleRequired = > ' A path needs a title. ' ;
@ override
String get pathSecondaryTitleLabel = > ' Secondary title ' ;
@ override
String get pathSecondaryTitleHint = >
' E.g. the title in the language you learn ' ;
@ override
String get pathEditTitle = > ' Edit path ' ;
@ override
String get pathDescription = > ' Description ' ;
@ override
String get pathAddDescription = > ' Add a description ' ;
@ override
String get pathDescriptionEditTitle = > ' Path description ' ;
@ override
String get pathAddTags = > ' Add tags ' ;
@ override
String get pathTags = > ' Tags ' ;
@ override
String get pathSteps = > ' Steps ' ;
@ override
String get pathStepsEmpty = > ' No steps yet ' ;
@ override
String get pathStepsEmptyHint = >
' Pick a step type to start writing the lesson. ' ;
@ override
String get pathAddStep = > ' Add step ' ;
@ override
String get pathReorder = > ' Reorder ' ;
@ override
String get pathReorderDone = > ' Done ' ;
@ override
String get pathStepDelete = > ' Delete step ' ;
@ override
String get pathStepDeleted = > ' Step deleted ' ;
@ override
String get stepUntitled = > ' Untitled step ' ;
@ override
String get stepKindInfo = > ' Info ' ;
@ override
String get stepKindSingleChoice = > ' Single choice ' ;
@ override
String get stepKindMultipleChoice = > ' Multiple choice ' ;
@ override
String get stepKindDragAndDrop = > ' Drag and drop ' ;
@ override
String get stepKindFillInTheBlank = > ' Fill in the blank ' ;
@ override
String get stepEditTitle = > ' Edit step ' ;
@ override
String get stepTitleHint = > ' Title ' ;
@ override
String get stepOptions = > ' Options ' ;
@ override
String stepOptionHint ( int number ) {
return ' Option $ number ' ;
}
@ override
String get stepAddOption = > ' Add option ' ;
@ override
String get stepRemoveOption = > ' Remove option ' ;
@ override
String get stepCorrectOption = > ' The right one ' ;
@ override
String get stepCorrectOptions = > ' Right ones ' ;
@ override
String get stepCategories = > ' Categories ' ;
@ override
String stepCategoryHint ( int number ) {
return ' Category $ number ' ;
}
@ override
String get stepAddCategory = > ' Add category ' ;
@ override
String get stepRemoveCategory = > ' Remove category ' ;
@ override
String get stepItems = > ' Items ' ;
@ override
String stepItemHint ( int number ) {
return ' Item $ number ' ;
}
@ override
String get stepAddItem = > ' Add item ' ;
@ override
String get stepRemoveItem = > ' Remove item ' ;
@ override
String get stepItemCategory = > ' Belongs to ' ;
@ override
String get stepPassage = > ' Passage ' ;
@ override
String get stepPassageHint = >
' Type ___ (three underscores) wherever a word should be dropped in. ' ;
@ override
String stepBlankHint ( int number ) {
return ' Word for gap $ number ' ;
}
@ override
String get stepIncomplete = >
' Not ready to answer yet: fill in every field and choose what is right. ' ;
@ override
String get stepFeedbackRight = > ' Feedback after a right answer ' ;
@ override
String get stepFeedbackWrong = > ' Feedback after a wrong answer ' ;
@ override
String get stepFeedbackNone = > ' None ' ;
@ override
String get stepSources = > ' Sources ' ;
@ override
String get stepAddSource = > ' Add source ' ;
@ override
String get stepRemoveSource = > ' Remove source ' ;
@ override
String get stepSourceAuthors = > ' Authors ' ;
@ override
String get stepSourceYear = > ' Year ' ;
@ override
String get stepSourceTitle = > ' Title ' ;
@ override
String get stepSourceUrl = > ' Link ' ;
@ override
String stepSourcesCount ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' $ count sources ' ,
one: ' 1 source ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String get stepConflict = >
' This step was changed differently on another device. Keep one version to go on writing. ' ;
@ override
String get stepFieldTitle = > ' Title ' ;
@ override
String get stepFieldBody = > ' Text ' ;
@ override
String get stepFieldExercise = > ' Exercise ' ;
@ override
String get stepFieldRightFeedback = > ' Feedback after a right answer ' ;
@ override
String get stepFieldWrongFeedback = > ' Feedback after a wrong answer ' ;
@ override
String get stepFieldSources = > ' Sources ' ;
@ override
String get runnerClose = > ' Close ' ;
@ override
String get runnerEditStep = > ' Edit step ' ;
@ override
String get runnerBack = > ' Back ' ;
@ override
String get runnerCheck = > ' Check answers ' ;
@ override
String get runnerContinue = > ' Continue ' ;
@ override
String get runnerFinish = > ' Finish ' ;
@ override
String get runnerRight = > ' Correct! ' ;
@ override
String get runnerWrong = > ' Not quite ' ;
@ override
String get runnerAlreadyRight = > ' You already answered this correctly. ' ;
@ override
String get runnerSelectAll = > ' Select all that apply. ' ;
@ override
String get runnerDragHint = >
' Drag each item into the category it belongs to, or tap it and then the category. Tap a placed item to take it back. ' ;
@ override
String get runnerBlankHint = >
' Drag a word into each gap, or tap it and then the gap. Tap a placed word to take it back. ' ;
@ override
String get runnerEmpty = > ' This path has no steps yet. ' ;
@ override
String get runnerFinished = > ' You finished this path ' ;
@ override
String runnerFinishedSteps ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' $ count steps complete. ' ,
one: ' 1 step complete. ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String get runnerNextPath = > ' Continue to next path ' ;
@ override
String get runnerBackToStudyset = > ' Back to studyset ' ;
@ override
String studysetCountPaths ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' $ count paths ' ,
one: ' 1 path ' ,
) ;
return ' $ _temp0 ' ;
}
feat(app): notes in the studyset screen, written in the whole window
- A Notes tab beside Flashcards lists a studyset's notes in order: the
title ("Untitled note" without one) and when each was last edited
("Edited 5 minutes ago", by the calendar from yesterday on, the date
after a week); Edit and Delete behind its options. Deleting asks,
then offers Undo for a moment (`NoteDeletions`). With none, the tab
says so.
- "Create a note" in Add material opens a new note over the tabs; so
does a note of the list. The note: a title, its tags once it exists,
and its text at reading width; the app bar says how saving stands.
- `NoteEditorBloc` saves after 5 seconds without typing, when the app
goes to the background, and when the note is closed (the bloc saves
as it closes, so leaving never waits, and the write outlives the
screen). Each save builds on the revisions the editor started from;
another device's change takes over a note with nothing unsaved, and
merges with one that has. A note changed differently on two devices
is read-only until the person keeps a version of each field in
conflict. A new note is created by its first save, never when left
blank; a note deleted elsewhere says it no longer exists.
- The editor gains a format bar, docked below it while it has the
focus (`RichContentEditor(formatBar: true)`): alignment, bold,
italic, underline, superscript and subscript. It takes no focus, so
the selection stays. Superscript and subscript are inline marks of
their own, drawn by the font's raised and lowered glyphs in editors
and read-only text alike.
- The library's and the topics' counts include notes (a studyset with
what is inside it, as for cards).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 22:30:53 +02:00
@ override
String get studysetCreateNote = > ' Create a note ' ;
@ override
String get notesEmpty = > ' No notes yet ' ;
@ override
String get notesEmptyHint = > ' Notes will appear here when you add them. ' ;
@ override
String get noteUntitled = > ' Untitled note ' ;
@ override
String noteEdited ( String when ) {
return ' Edited $ when ' ;
}
@ override
String get timeJustNow = > ' just now ' ;
@ override
String timeMinutesAgo ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' $ count minutes ago ' ,
one: ' 1 minute ago ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String timeHoursAgo ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' $ count hours ago ' ,
one: ' 1 hour ago ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String timeDaysAgo ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' $ count days ago ' ,
one: ' yesterday ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String get noteOptions = > ' Note options ' ;
@ override
String get noteEdit = > ' Edit ' ;
@ override
String get noteDelete = > ' Delete ' ;
@ override
String get noteDeleteTitle = > ' Delete note? ' ;
@ override
String get noteDeleteMessage = >
' This note will be removed from the studyset. You can undo this for a few seconds. ' ;
@ override
String get noteDeleteConfirm = > ' Delete ' ;
@ override
String get noteDeleted = > ' Note deleted ' ;
@ override
String noteNewTitleFor ( String title ) {
return ' New note in \" $ title \" ' ;
}
@ override
String noteTitleFor ( String title ) {
return ' Note in \" $ title \" ' ;
}
@ override
String get noteTitleHint = > ' Title ' ;
@ override
String get noteEditing = > ' Editing… ' ;
@ override
String get noteSaving = > ' Saving… ' ;
@ override
String get noteSaved = > ' Saved ' ;
@ override
String get noteSaveFailed = > ' Save failed ' ;
@ override
String get noteConflict = >
' This note was changed differently on another device. Keep one version to go on writing. ' ;
@ override
String get noteFieldTitle = > ' Title ' ;
@ override
String get noteFieldBody = > ' Text ' ;
@ override
String get noteKeepThis = > ' Keep this ' ;
@ override
String get noteAddTags = > ' Add tags ' ;
@ override
String get noteTags = > ' Tags ' ;
feat(app): the studyset screen with its topics, the tree's own screen
A studyset's screen, as decided with the tree (2026-09-30/10-01):
- A breadcrumb from the library down to the studyset; its steps go up
(replacing the location, so back leads further up), and steps that
don't fit fold into a "…" listing them, nearest first.
- From expanded windows on, two columns: the description over the topics,
the flashcards beside them, Study and Add material in the app bar. Below
that, the topics are in a drawer, opened from a bar at the bottom that
also holds Study and Add material and slides away while reading onwards;
the back button stays where a drawer would have taken its place.
- The topics: "All topics" for the whole studyset, then the studysets
inside it in their order, each with the study it invites, what it holds
and its options (edit, delete), opening on top of this screen; a switch
rearranges them by dragging; "New sub-studyset" creates one inside.
- Study covers the studyset with everything inside it ("Learn 12" while
something is due). Add material offers flashcards for now.
- Options: Edit (the sheet, now with the description, edited in the whole
window, and for a studyset inside another the colour of the one it is
in, which a new one takes at first), Move to… (the tree and the top
level, without the studyset itself and what is inside it), Archive or
Unarchive with Undo, Delete with Undo, which closes the screen.
- The description cut short when long, the whole a tap away; the licence
as the last line, linking to its text where that is known.
The list's bloc, scoped to a parent, lists its children, works out the
studyset's own progress, creates inside it, reorders and moves. The card
list leaves adding and studying to the screen. The inline editing of the
title and description, and its bloc, are gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 12:59:20 +02:00
@ override
String get studysetFlashcardsEmpty = > ' No flashcards yet ' ;
@ override
String get studysetFlashcardsEmptyHint = > ' Add some to start studying. ' ;
@ override
String get studysetAddMaterial = > ' Add material ' ;
@ override
String get studysetCreateFlashcards = > ' Create flashcards ' ;
@ override
String get studysetStudy = > ' Study ' ;
@ override
String studysetLearnCount ( int count ) {
return ' Learn $ count ' ;
}
@ override
String get studysetOpenTopics = > ' Show topics ' ;
@ override
String get studysetTopics = > ' Topics ' ;
@ override
String get studysetAllTopics = > ' All topics ' ;
@ override
String get studysetNewSubStudyset = > ' New sub-studyset ' ;
@ override
String get studysetSubStudysetsEmpty = > ' No sub-studysets yet. ' ;
@ override
String get studysetSubStudysetsEmptyHint = >
' Break this studyset into milestones or lessons. ' ;
@ override
String get studysetReorder = > ' Reorder ' ;
@ override
String get studysetReorderDone = > ' Done ' ;
@ override
String get studysetDragToReorder = > ' Drag to reorder ' ;
@ override
String get studysetMoveTitle = > ' Move studyset to ' ;
@ override
String get studysetMoveTopLevel = > ' Top level ' ;
@ override
String get studysetMoveCurrent = > ' Where it is now ' ;
@ override
String get studysetMoveConfirm = > ' Move ' ;
@ override
String get studysetMoveShowInside = > ' Show what is inside ' ;
@ override
String get studysetMoveHideInside = > ' Hide what is inside ' ;
@ override
String studysetMoved ( String destination ) {
return ' Moved to $ destination . ' ;
}
@ override
String get studysetReadMore = > ' Read more ' ;
@ override
String get studysetDescriptionTitle = > ' Description ' ;
@ override
String get studysetDescriptionEditTitle = > ' Edit description ' ;
@ override
feat(app): learning paths in the studyset screen: worked through and written
- A Paths tab before Flashcards and Notes: each path with its title and
secondary title, dimmed once completed; Edit, Reset progress (with an
undo) and Delete (with an undo) behind its options. The studyset opens
on its paths while one is unfinished, on its flashcards otherwise.
- "Create a path" in Add material asks for the titles in a sheet and
opens the new path to write; "Import a path (JSON)" reads a file in
the import format, says what came in and how many blocks were left out
(images and animations: the app never fetches from the web).
- The player: one step at a time at reading width, a bar of the steps
above, Back and "Check answers" / "Continue" / "Finish" below. It
opens at the first unfinished step; an exercise passes only once
answered right, as often as it takes; after a check, only the
learner's own picks are marked, with the author's feedback; sources
behind a chip; a finish screen leading to the next path. Words and
items are dragged into place, or tapped into place for phones.
- Writing: the path's screen (titles, description, tags, steps added by
kind, reordered, deleted with an undo) and the step's (title, text
with the format bar, its kind's exercise, feedback for right and
wrong in the whole window, sources). The step saves on what it
started from, as notes do, read-only while devices disagree until a
version is kept.
- The library and the topics count paths; steps done count on the
dashboard, for the streak and the activity.
- The rich-text screen for a studyset's description moves to core, for
the steps' feedback too, and gains the format bar.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-03 09:25:08 +02:00
String get richTextSave = > ' Save ' ;
feat(app): the studyset screen with its topics, the tree's own screen
A studyset's screen, as decided with the tree (2026-09-30/10-01):
- A breadcrumb from the library down to the studyset; its steps go up
(replacing the location, so back leads further up), and steps that
don't fit fold into a "…" listing them, nearest first.
- From expanded windows on, two columns: the description over the topics,
the flashcards beside them, Study and Add material in the app bar. Below
that, the topics are in a drawer, opened from a bar at the bottom that
also holds Study and Add material and slides away while reading onwards;
the back button stays where a drawer would have taken its place.
- The topics: "All topics" for the whole studyset, then the studysets
inside it in their order, each with the study it invites, what it holds
and its options (edit, delete), opening on top of this screen; a switch
rearranges them by dragging; "New sub-studyset" creates one inside.
- Study covers the studyset with everything inside it ("Learn 12" while
something is due). Add material offers flashcards for now.
- Options: Edit (the sheet, now with the description, edited in the whole
window, and for a studyset inside another the colour of the one it is
in, which a new one takes at first), Move to… (the tree and the top
level, without the studyset itself and what is inside it), Archive or
Unarchive with Undo, Delete with Undo, which closes the screen.
- The description cut short when long, the whole a tap away; the licence
as the last line, linking to its text where that is known.
The list's bloc, scoped to a parent, lists its children, works out the
studyset's own progress, creates inside it, reorders and moves. The card
list leaves adding and studying to the screen. The inline editing of the
title and description, and its bloc, are gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 12:59:20 +02:00
@ override
String get studysetSheetDescription = > ' Description ' ;
@ override
String get studysetSheetDescriptionAdd = > ' Add a description ' ;
@ override
feat(app): the app speaks British and American English and German
Settings offers English (UK), English (US) and Deutsch after the
device's language, each named in its own language. app_en.arb, the
template, is now British English (colour, centre) and the fallback for
any language the app doesn't speak; app_en_US.arb overrides the five
messages American English spells differently, and app_en_GB.arb is
empty, there so that Material's texts and dates are British too. The
German translation says "du" and keeps one set of terms throughout
(Lernset, Karteikarte, Lernpfad; Anki's own Stapel and Unterstapel in
the Anki import). arb_files_test checks that German has every message
of the template and that every translation keeps its placeholders.
resolveAppLocale picks the first language the device prefers that the
app speaks, in its country's variant where there is one; other English
is British, and a country alone never picks a language (Spanish in the
United States isn't American English), which Flutter's own resolution
would do.
Both MaterialApps take material_ui's GlobalMaterialLocalizations
delegates: gen-l10n's list brings flutter_localizations', which serve
only package:flutter/material.dart, so material_ui's widgets found no
Material texts in German and threw.
The editor's own text blocks show our placeholders while empty (list
item, to-do, quote, heading, and the desktop's / hint): the editor's
own follow a locale of their own, process-wide, in few languages. The
last texts written into widgets (the person's data failing to open,
the cards failing to read, the About dialog) moved into the arb files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-07 02:01:01 +02:00
String get studysetSheetInheritColor = >
' Same colour as the studyset it is in ' ;
feat(app): the studyset screen with its topics, the tree's own screen
A studyset's screen, as decided with the tree (2026-09-30/10-01):
- A breadcrumb from the library down to the studyset; its steps go up
(replacing the location, so back leads further up), and steps that
don't fit fold into a "…" listing them, nearest first.
- From expanded windows on, two columns: the description over the topics,
the flashcards beside them, Study and Add material in the app bar. Below
that, the topics are in a drawer, opened from a bar at the bottom that
also holds Study and Add material and slides away while reading onwards;
the back button stays where a drawer would have taken its place.
- The topics: "All topics" for the whole studyset, then the studysets
inside it in their order, each with the study it invites, what it holds
and its options (edit, delete), opening on top of this screen; a switch
rearranges them by dragging; "New sub-studyset" creates one inside.
- Study covers the studyset with everything inside it ("Learn 12" while
something is due). Add material offers flashcards for now.
- Options: Edit (the sheet, now with the description, edited in the whole
window, and for a studyset inside another the colour of the one it is
in, which a new one takes at first), Move to… (the tree and the top
level, without the studyset itself and what is inside it), Archive or
Unarchive with Undo, Delete with Undo, which closes the screen.
- The description cut short when long, the whole a tap away; the licence
as the last line, linking to its text where that is known.
The list's bloc, scoped to a parent, lists its children, works out the
studyset's own progress, creates inside it, reorders and moves. The card
list leaves adding and studying to the screen. The inline editing of the
title and description, and its bloc, are gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 12:59:20 +02:00
feat(app): the UI foundation: theme, tabs and routes, strings
The app gets its look and navigation before more features land on them.
- Theme (core/theme/): hand-written light and dark Material 3 colour schemes around
cobalt and copper; ThemeExtras for the colours Material has no role for (states, the
four FSRS ratings); the 20 colours a person can give content. Tests hold text to WCAG
AA contrast on each palette colour and on the dark theme's primary colour and surface.
- Layout and shared widgets: Material 3's window-size classes (core/layout/);
SectionCard, AppLoadingIndicator, confirmDestructiveAction and FoundById
(core/widget/). Lucide icons.
- Navigation with go_router: four tabs (Home, Library, Explore, Profile), each keeping
its own stack of screens, as a navigation rail with the app's icon from 600 dp and as
a navigation bar below; choosing the open tab again returns to its first screen. A
studyset's screen sits in the library; studying, cards and settings cover the tabs.
- Routes carry ids: FoundById finds the item in the repository's live list, so a
studyset deleted on another device, or an old link, says it no longer exists. A
screen that deletes its own item keeps showing it while it closes, rather than
flashing that message.
- Every person gets an app of their own (PersonApp, lib/src/app/): a
MaterialApp.router with its own GoRouter and PersonScope around the navigator, built
by SessionGate inside the person's keyed subtree. Screens read the person's
repositories wherever they are built, so the static route() helpers and the listener
that closed every pushed screen on a change of person are gone: a change of person
now disposes the whole router and starts again at home.
- The account dialog becomes the Profile tab, with Settings (sign out, delete the
account, About) behind a gear. A waiting GitHub sign-in now keeps waiting while
another tab shows; a change of person still cancels it.
- The new screens' strings come from Flutter's gen-l10n (English for now). The
generated files are committed, left out of analysis and formatting, and written
anew by `flutter pub get`, so CI's check of generated code also catches stale ones.
Widget tests now start the whole app (test/support/pump_app.dart) at the screen's path
and move through it as a person would, by tabs and taps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:18:31 +02:00
@ override
String get exploreTitle = > ' Explore ' ;
@ override
String get exploreEmpty = > ' Studysets that others share will appear here. ' ;
@ override
String get profileTitle = > ' Profile ' ;
@ override
String get profileSettingsTooltip = > ' Settings ' ;
@ override
String get profileIdentified = > ' Your progress is kept with your account. ' ;
@ override
String get profileAnonymous = >
' Progress is saved on this device. Sign in to keep it. ' ;
@ override
String get profileSignInWithGitHub = > ' Sign in with GitHub ' ;
@ override
String get profileSignInFailed = > ' Couldn \' t sign in with GitHub. ' ;
@ override
String get profileContinueInBrowser = > ' Continue in your browser. ' ;
@ override
String get profileSwitchTitle = > ' Switch to your GitHub account? ' ;
@ override
String get profileSwitchMessage = >
' This GitHub account already has progress from another device. The studysets on this device can \' t be combined with it yet: if you switch, they are deleted. ' ;
@ override
String get profileSwitchStay = > ' Stay anonymous ' ;
@ override
String get profileSwitchConfirm = > ' Switch ' ;
@ override
String get settingsTitle = > ' Settings ' ;
@ override
String get settingsAccount = > ' Account ' ;
@ override
String get settingsSignOut = > ' Sign out ' ;
@ override
String get settingsSignOutTitle = > ' Sign out? ' ;
@ override
String get settingsSignOutAnonymousMessage = >
' This account can \' t be signed into again, so signing out deletes it with its progress. ' ;
@ override
String get settingsDeleteAccount = > ' Delete account ' ;
@ override
String get settingsDeleteTitle = > ' Delete your account? ' ;
@ override
String get settingsDeleteMessage = >
' Everything you have, your studysets, cards and reviews, is deleted on the server and on all your devices. This can \' t be undone. ' ;
@ override
String get settingsAbout = > ' About Halkyon Learn ' ;
feat(app): the app speaks British and American English and German
Settings offers English (UK), English (US) and Deutsch after the
device's language, each named in its own language. app_en.arb, the
template, is now British English (colour, centre) and the fallback for
any language the app doesn't speak; app_en_US.arb overrides the five
messages American English spells differently, and app_en_GB.arb is
empty, there so that Material's texts and dates are British too. The
German translation says "du" and keeps one set of terms throughout
(Lernset, Karteikarte, Lernpfad; Anki's own Stapel and Unterstapel in
the Anki import). arb_files_test checks that German has every message
of the template and that every translation keeps its placeholders.
resolveAppLocale picks the first language the device prefers that the
app speaks, in its country's variant where there is one; other English
is British, and a country alone never picks a language (Spanish in the
United States isn't American English), which Flutter's own resolution
would do.
Both MaterialApps take material_ui's GlobalMaterialLocalizations
delegates: gen-l10n's list brings flutter_localizations', which serve
only package:flutter/material.dart, so material_ui's widgets found no
Material texts in German and threw.
The editor's own text blocks show our placeholders while empty (list
item, to-do, quote, heading, and the desktop's / hint): the editor's
own follow a locale of their own, process-wide, in few languages. The
last texts written into widgets (the person's data failing to open,
the cards failing to read, the About dialog) moved into the arb files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-07 02:01:01 +02:00
@ override
String get aboutLegalese = >
' Free software: the app is under the GNU Affero General Public License, version 3 or any later version, the server under the Server Side Public License, version 1. ' ;
@ override
String get aboutSourceCode = > ' The source of what this instance runs: ' ;
@ override
String get aboutSourceUnreachable = >
' Couldn \' t reach this instance to ask where its source is. ' ;
feat(app): appearance, language and version in Settings, and Settings from Home
Settings gets a card for how this device shows the app: its appearance
(as the device, light or dark) and its language (the device's, or one
the app speaks, each named in its own language), side by side from
medium windows on, as two outlined dropdowns. Both stay on the device
rather than following the person: they are kept in
<app support>/device_settings.json (written to a temporary file and
renamed into place), read before the app shows, so its first frame
already looks as chosen, and a file that can't be read falls back to
what the device does. DeviceSettingsBloc sits above SessionGate's keyed
subtree, since the settings outlive every person, and both the plain
app and the person's app take their theme mode and locale from it.
At the bottom of Settings, the release and its build, read once at
start together with the build the server is asked about.
Home has a Settings cog beside the streak, with the sync trouble dot as
on Profile. The library's create button reads "Create studyset", and
Delete is red in every menu that offers it: the studyset's card, screen
and rail, and the composer's draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-07 01:43:28 +02:00
@ override
String get settingsAppearance = > ' Appearance ' ;
@ override
String get settingsAppearanceDevice = > ' Device theme ' ;
@ override
String get settingsAppearanceLight = > ' Light ' ;
@ override
String get settingsAppearanceDark = > ' Dark ' ;
@ override
String get settingsLanguage = > ' Language ' ;
@ override
String get settingsLanguageDevice = > ' Device language ' ;
@ override
String settingsVersion ( String version , String build ) {
return ' Version $ version (build $ build ) ' ;
}
feat: the app tells when sync fails, and when it is too old
Published apps can't be made to update, so the app now notices when it
no longer matches the server: the server names the oldest app build it
works with (instance.info's minimumAppBuild), asked before the first
sync of each launch, and the sync engine's refusal of differing synced
tables counts the same. Either way the app says "update the app; if it
is up to date, the server needs an update" in a banner on Home and in
the new Sync card in Settings, and what the person does meanwhile stays
on the device.
The Sync card shows where sync stands since the app started (syncing,
everything synced, sync failing, with the last completion and failure)
and offers Sync now. While the last sync failed, a dot sits on the
Profile tab and the Settings cog. One sync that goes through clears it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-03 23:26:47 +02:00
@ override
String get syncTitle = > ' Sync ' ;
@ override
String get syncStateSyncing = > ' Syncing… ' ;
@ override
String get syncStateSynced = > ' Everything synced ' ;
@ override
String get syncStateNotYet = > ' Not synced yet ' ;
@ override
String get syncStateFailing = > ' Sync is failing ' ;
@ override
String syncStateMismatch ( String server ) {
return ' This app can \' t sync with $ server ' ;
}
@ override
String get syncMismatchDetail = >
' Update the app. If it is up to date, the server needs an update. What you do meanwhile stays on this device and syncs afterwards. ' ;
@ override
String syncLastCompleted ( String time ) {
return ' Last completed at $ time ' ;
}
@ override
String get syncNoRunYet = > ' No sync has completed since the app started ' ;
@ override
String syncLastFailed ( String time ) {
return ' Last attempt failed at $ time ' ;
}
@ override
String get syncNow = > ' Sync now ' ;
@ override
String get syncTroubleLabel = > ' Sync needs attention ' ;
@ override
String syncMismatchBanner ( String server ) {
return ' This app can \' t sync with $ server any more. Update it to keep syncing. ' ;
}
@ override
String get syncMismatchMore = > ' More ' ;
2026-10-01 16:57:57 +02:00
@ override
String get tagsReadFailed = > ' Couldn \' t read the tags. ' ;
@ override
String get tagPickerTitle = > ' Tags ' ;
@ override
String get tagPickerManage = > ' Manage tags ' ;
@ override
String get tagPickerSearchLabel = > ' Search or create a tag ' ;
@ override
String tagPickerCreate ( String name ) {
return ' Create \" $ name \" ' ;
}
@ override
String get tagSheetCreateTitle = > ' Create tag ' ;
@ override
String get tagSheetEditTitle = > ' Edit tag ' ;
@ override
String get tagSheetNameLabel = > ' Name ' ;
@ override
String get tagSheetNameHelper = > ' Stored and displayed in lowercase. ' ;
@ override
String tagSheetNameExists ( String name ) {
return ' The tag \' $ name \' exists already. ' ;
}
@ override
String tagSheetNameMerges ( String name ) {
return ' There is a tag \' $ name \' already: saving merges this one into it. ' ;
}
@ override
feat(app): the app speaks British and American English and German
Settings offers English (UK), English (US) and Deutsch after the
device's language, each named in its own language. app_en.arb, the
template, is now British English (colour, centre) and the fallback for
any language the app doesn't speak; app_en_US.arb overrides the five
messages American English spells differently, and app_en_GB.arb is
empty, there so that Material's texts and dates are British too. The
German translation says "du" and keeps one set of terms throughout
(Lernset, Karteikarte, Lernpfad; Anki's own Stapel and Unterstapel in
the Anki import). arb_files_test checks that German has every message
of the template and that every translation keeps its placeholders.
resolveAppLocale picks the first language the device prefers that the
app speaks, in its country's variant where there is one; other English
is British, and a country alone never picks a language (Spanish in the
United States isn't American English), which Flutter's own resolution
would do.
Both MaterialApps take material_ui's GlobalMaterialLocalizations
delegates: gen-l10n's list brings flutter_localizations', which serve
only package:flutter/material.dart, so material_ui's widgets found no
Material texts in German and threw.
The editor's own text blocks show our placeholders while empty (list
item, to-do, quote, heading, and the desktop's / hint): the editor's
own follow a locale of their own, process-wide, in few languages. The
last texts written into widgets (the person's data failing to open,
the cards failing to read, the About dialog) moved into the arb files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-07 02:01:01 +02:00
String get tagSheetColor = > ' Colour ' ;
2026-10-01 16:57:57 +02:00
@ override
String get tagSheetCreate = > ' Create ' ;
@ override
String get tagSheetSave = > ' Save ' ;
@ override
String get tagManageTitle = > ' Manage tags ' ;
@ override
String get tagManageEmpty = > ' No tags yet. Add one below. ' ;
@ override
String get tagManageAdd = > ' Add tag ' ;
@ override
String tagManageRow ( String name , int count ) {
return ' $ name ( $ count ) ' ;
}
@ override
String get tagManageEdit = > ' Edit tag ' ;
@ override
String get tagManageDelete = > ' Delete tag ' ;
@ override
String tagDeleteTitle ( String name ) {
return ' Delete the tag \' $ name \' ? ' ;
}
@ override
String tagDeleteMessage ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' It is removed from $ count flashcards, on all your devices. ' ,
one: ' It is removed from 1 flashcard, on all your devices. ' ,
zero: ' It isn \' t on any flashcards. ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String get tagDeleteConfirm = > ' Delete ' ;
@ override
String tagDeleted ( String name ) {
return ' Deleted the tag \' $ name \' ' ;
}
2026-10-01 17:30:24 +02:00
@ override
String get studySettingsTitle = > ' Session settings ' ;
@ override
String get studySettingsFilterByTags = > ' Filter by tags ' ;
@ override
String get studySettingsFilterHint = >
' Study only the flashcards with any of the tags chosen. ' ;
@ override
String get studySettingsNoTags = >
' No tags yet. Add tags to your flashcards to filter them here. ' ;
@ override
String get studySettingsReset = > ' Reset ' ;
@ override
String get studySettingsSave = > ' Save ' ;
2026-10-01 16:57:57 +02:00
@ override
String get cardTagsLabel = > ' Tags ' ;
@ override
String get cardTagsAdd = > ' Add tags ' ;
@ override
String get cardTagsEdit = > ' Edit tags ' ;
@ override
String get cardFilterTooltip = > ' Filter ' ;
@ override
String get cardFilterTitle = > ' Filter ' ;
@ override
String get cardFilterTag = > ' Tag ' ;
@ override
String get cardFilterAllTags = > ' All tags ' ;
@ override
String get cardFilterNoTags = > ' None of these flashcards has a tag yet. ' ;
@ override
String get cardFilterClear = > ' Clear all ' ;
@ override
String get cardFilterApply = > ' Apply ' ;
@ override
String get cardFilterNothing = > ' No flashcards have this tag. ' ;
feat(app): the app speaks British and American English and German
Settings offers English (UK), English (US) and Deutsch after the
device's language, each named in its own language. app_en.arb, the
template, is now British English (colour, centre) and the fallback for
any language the app doesn't speak; app_en_US.arb overrides the five
messages American English spells differently, and app_en_GB.arb is
empty, there so that Material's texts and dates are British too. The
German translation says "du" and keeps one set of terms throughout
(Lernset, Karteikarte, Lernpfad; Anki's own Stapel and Unterstapel in
the Anki import). arb_files_test checks that German has every message
of the template and that every translation keeps its placeholders.
resolveAppLocale picks the first language the device prefers that the
app speaks, in its country's variant where there is one; other English
is British, and a country alone never picks a language (Spanish in the
United States isn't American English), which Flutter's own resolution
would do.
Both MaterialApps take material_ui's GlobalMaterialLocalizations
delegates: gen-l10n's list brings flutter_localizations', which serve
only package:flutter/material.dart, so material_ui's widgets found no
Material texts in German and threw.
The editor's own text blocks show our placeholders while empty (list
item, to-do, quote, heading, and the desktop's / hint): the editor's
own follow a locale of their own, process-wide, in few languages. The
last texts written into widgets (the person's data failing to open,
the cards failing to read, the About dialog) moved into the arb files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-07 02:01:01 +02:00
@ override
String get cardListFailed = > ' Couldn \' t read the cards. ' ;
2026-10-01 16:57:57 +02:00
@ override
String get cardListMore = > ' More ' ;
@ override
String get cardListManageTags = > ' Manage tags ' ;
feat(app): the card composer, for many cards in one sitting
Create flashcards, and tapping a card, open the composer: the stack of
cards on the left (New flashcard on top, the new ones, then the stored
ones; collapsible), the card being written on the right, saved as the
person leaves it (another card, the pane, the screen, the app). The
title says how saving stands; leaving with cards that couldn't be saved
asks first. A card has its tags and their pin, an action menu
(duplicate, clear, delete), the type switch (flashcard, vocabulary,
matching), the front, and below a line its answer side: one answer or
several to mark right or wrong, the reading and meanings, or categories
and items. Fields changed differently on two devices show every version
to keep one. The single-card editor goes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 18:42:25 +02:00
@ override
String get composerTitle = > ' Create flashcards ' ;
@ override
String composerTitleFor ( String title ) {
return ' Create flashcards for \" $ title \" ' ;
}
Fix what the first Android run found
The flow ran on a Pixel 9a and its emulator (2026-10-02); what it found:
- The composer stayed on the card just written after "New flashcard", and
on the original after "Duplicate": leaving a card changed the state
before the new one was in it, so the screen took the new card for gone.
It now waits for the card it asked for.
- Bottom sheets reached under the status bar with the keyboard open
(useSafeArea).
- The field being written stayed behind the keyboard: the editor scrolls
only within itself, so the card face now keeps it in view when it opens
and when the keyboard rises, as a text field does.
- A tap on read-only rich text put a caret in it and opened the keyboard on
phones, and in the composer never opened the field. RichContentView is
display only now: no selection, taps go to what surrounds it, a sound's
button still plays (decided 2026-10-02).
- The card types broke their names on a phone; they are choice chips that
wrap (decided 2026-10-02).
- The week overview marked days before the person's first activity as
missed; they show nothing now. An import starts the activity too, without
counting as studying (decided 2026-10-02).
- A composer preview cut short gave no sign of it; it fades out over its
last 32 dp, only when cut short (decided 2026-10-02).
- "Isn't on this device yet" showed for any image or sound that failed;
a file that is here but can't be decoded now says so, and offers no retry.
- The signed-in account card was narrower than the anonymous one.
- The composer's title said "Create flashcards" also when opened on a
stored card; it says "Edit flashcards in …" then (decided 2026-10-02).
- The Anki fixtures carry a valid PNG and a silent MP3, so they also serve
to try the import by hand (decided 2026-10-02); regenerated with Anki
26.9.3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 19:52:39 +02:00
@ override
String get composerEditTitle = > ' Edit flashcards ' ;
@ override
String composerEditTitleFor ( String title ) {
return ' Edit flashcards in \" $ title \" ' ;
}
feat(app): the card composer, for many cards in one sitting
Create flashcards, and tapping a card, open the composer: the stack of
cards on the left (New flashcard on top, the new ones, then the stored
ones; collapsible), the card being written on the right, saved as the
person leaves it (another card, the pane, the screen, the app). The
title says how saving stands; leaving with cards that couldn't be saved
asks first. A card has its tags and their pin, an action menu
(duplicate, clear, delete), the type switch (flashcard, vocabulary,
matching), the front, and below a line its answer side: one answer or
several to mark right or wrong, the reading and meanings, or categories
and items. Fields changed differently on two devices show every version
to keep one. The single-card editor goes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 18:42:25 +02:00
@ override
String get composerSaving = > ' Saving… ' ;
@ override
String get composerSaveFailed = > ' Save failed — retry ' ;
@ override
String get composerAllSaved = > ' All changes saved ' ;
@ override
String get composerNewFlashcard = > ' New flashcard ' ;
@ override
String get composerCollapseStack = > ' Collapse stack ' ;
@ override
String get composerExpandStack = > ' Expand stack ' ;
@ override
String get composerStatusSaving = > ' Saving ' ;
@ override
String get composerStatusSaved = > ' Saved ' ;
@ override
String get composerStatusFailed = > ' Save failed ' ;
@ override
String get composerSaveFailedInline = > ' Save failed. ' ;
@ override
String get composerRetry = > ' Retry ' ;
@ override
String get composerEmptyPane = > ' Add a flashcard to get started. ' ;
@ override
String get composerKindFlashcard = > ' Flashcard ' ;
@ override
String get composerKindVocabulary = > ' Vocabulary ' ;
@ override
String get composerKindMatching = > ' Matching ' ;
@ override
String get composerKindMap = > ' Map ' ;
@ override
String get composerLabelFront = > ' Front ' ;
@ override
String get composerLabelQuestion = > ' Question ' ;
@ override
String get composerLabelExpression = > ' Expression ' ;
@ override
String get composerLabelPrompt = > ' Prompt ' ;
@ override
String get composerLabelBack = > ' Back ' ;
@ override
String get composerLabelAnswers = > ' Answers ' ;
@ override
String get composerLabelAnswer = > ' Answer ' ;
@ override
String get composerPlaceholderFront = > ' Add question ' ;
@ override
String get composerPlaceholderExpression = > ' Add expression ' ;
@ override
String get composerPlaceholderPrompt = > ' Add prompt ' ;
@ override
String get composerAddAnswer = > ' Add answer ' ;
@ override
String get composerCorrectAnswer = > ' Correct answer ' ;
@ override
String get composerWrongAnswer = > ' Wrong answer ' ;
@ override
String get composerRemoveAnswer = > ' Remove answer ' ;
@ override
String composerPin ( String field ) {
return ' Pin $ field : keep it for the next card ' ;
}
@ override
String composerUnpin ( String field ) {
return ' Unpin $ field : clear it for the next card ' ;
}
@ override
String get composerTags = > ' Tags ' ;
@ override
String get composerAddTags = > ' Add tags ' ;
@ override
String get composerMoreActions = > ' More actions ' ;
@ override
String get composerDuplicate = > ' Duplicate ' ;
@ override
String get composerClearFields = > ' Clear fields ' ;
@ override
String get composerDelete = > ' Delete ' ;
@ override
String get composerDeleteTitle = > ' Delete this flashcard? ' ;
@ override
String get composerDeleteDraftMessage = >
' The draft will be removed from this session. It has not been saved. ' ;
@ override
String get composerDeleteSavedMessage = >
' The flashcard will be removed from the studyset, on all your devices. ' ;
@ override
String composerDiscardTitle ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' Discard unsaved flashcards? ' ,
one: ' Discard unsaved flashcard? ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String composerDiscardMessage ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other:
' $ count flashcards haven \' t been saved yet. Leaving now will discard them. ' ,
one: ' 1 flashcard hasn \' t been saved yet. Leaving now will discard it. ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String get composerDiscardConfirm = > ' Discard ' ;
@ override
String get composerConflict = >
' This flashcard was changed differently on two devices. Choose the version to keep of each part below. ' ;
@ override
String get composerKeepThis = > ' Keep this ' ;
feat(app): notes in the studyset screen, written in the whole window
- A Notes tab beside Flashcards lists a studyset's notes in order: the
title ("Untitled note" without one) and when each was last edited
("Edited 5 minutes ago", by the calendar from yesterday on, the date
after a week); Edit and Delete behind its options. Deleting asks,
then offers Undo for a moment (`NoteDeletions`). With none, the tab
says so.
- "Create a note" in Add material opens a new note over the tabs; so
does a note of the list. The note: a title, its tags once it exists,
and its text at reading width; the app bar says how saving stands.
- `NoteEditorBloc` saves after 5 seconds without typing, when the app
goes to the background, and when the note is closed (the bloc saves
as it closes, so leaving never waits, and the write outlives the
screen). Each save builds on the revisions the editor started from;
another device's change takes over a note with nothing unsaved, and
merges with one that has. A note changed differently on two devices
is read-only until the person keeps a version of each field in
conflict. A new note is created by its first save, never when left
blank; a note deleted elsewhere says it no longer exists.
- The editor gains a format bar, docked below it while it has the
focus (`RichContentEditor(formatBar: true)`): alignment, bold,
italic, underline, superscript and subscript. It takes no focus, so
the selection stays. Superscript and subscript are inline marks of
their own, drawn by the font's raised and lowered glyphs in editors
and read-only text alike.
- The library's and the topics' counts include notes (a studyset with
what is inside it, as for cards).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 22:30:53 +02:00
@ override
String get formatAlignLeft = > ' Align left ' ;
@ override
feat(app): the app speaks British and American English and German
Settings offers English (UK), English (US) and Deutsch after the
device's language, each named in its own language. app_en.arb, the
template, is now British English (colour, centre) and the fallback for
any language the app doesn't speak; app_en_US.arb overrides the five
messages American English spells differently, and app_en_GB.arb is
empty, there so that Material's texts and dates are British too. The
German translation says "du" and keeps one set of terms throughout
(Lernset, Karteikarte, Lernpfad; Anki's own Stapel and Unterstapel in
the Anki import). arb_files_test checks that German has every message
of the template and that every translation keeps its placeholders.
resolveAppLocale picks the first language the device prefers that the
app speaks, in its country's variant where there is one; other English
is British, and a country alone never picks a language (Spanish in the
United States isn't American English), which Flutter's own resolution
would do.
Both MaterialApps take material_ui's GlobalMaterialLocalizations
delegates: gen-l10n's list brings flutter_localizations', which serve
only package:flutter/material.dart, so material_ui's widgets found no
Material texts in German and threw.
The editor's own text blocks show our placeholders while empty (list
item, to-do, quote, heading, and the desktop's / hint): the editor's
own follow a locale of their own, process-wide, in few languages. The
last texts written into widgets (the person's data failing to open,
the cards failing to read, the About dialog) moved into the arb files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-07 02:01:01 +02:00
String get formatAlignCenter = > ' Centre ' ;
feat(app): notes in the studyset screen, written in the whole window
- A Notes tab beside Flashcards lists a studyset's notes in order: the
title ("Untitled note" without one) and when each was last edited
("Edited 5 minutes ago", by the calendar from yesterday on, the date
after a week); Edit and Delete behind its options. Deleting asks,
then offers Undo for a moment (`NoteDeletions`). With none, the tab
says so.
- "Create a note" in Add material opens a new note over the tabs; so
does a note of the list. The note: a title, its tags once it exists,
and its text at reading width; the app bar says how saving stands.
- `NoteEditorBloc` saves after 5 seconds without typing, when the app
goes to the background, and when the note is closed (the bloc saves
as it closes, so leaving never waits, and the write outlives the
screen). Each save builds on the revisions the editor started from;
another device's change takes over a note with nothing unsaved, and
merges with one that has. A note changed differently on two devices
is read-only until the person keeps a version of each field in
conflict. A new note is created by its first save, never when left
blank; a note deleted elsewhere says it no longer exists.
- The editor gains a format bar, docked below it while it has the
focus (`RichContentEditor(formatBar: true)`): alignment, bold,
italic, underline, superscript and subscript. It takes no focus, so
the selection stays. Superscript and subscript are inline marks of
their own, drawn by the font's raised and lowered glyphs in editors
and read-only text alike.
- The library's and the topics' counts include notes (a studyset with
what is inside it, as for cards).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 22:30:53 +02:00
@ override
String get formatAlignRight = > ' Align right ' ;
@ override
String get formatBold = > ' Bold ' ;
@ override
String get formatItalic = > ' Italic ' ;
@ override
String get formatUnderline = > ' Underline ' ;
@ override
String get formatSuperscript = > ' Superscript ' ;
@ override
String get formatSubscript = > ' Subscript ' ;
feat(app): the card composer, for many cards in one sitting
Create flashcards, and tapping a card, open the composer: the stack of
cards on the left (New flashcard on top, the new ones, then the stored
ones; collapsible), the card being written on the right, saved as the
person leaves it (another card, the pane, the screen, the app). The
title says how saving stands; leaving with cards that couldn't be saved
asks first. A card has its tags and their pin, an action menu
(duplicate, clear, delete), the type switch (flashcard, vocabulary,
matching), the front, and below a line its answer side: one answer or
several to mark right or wrong, the reading and meanings, or categories
and items. Fields changed differently on two devices show every version
to keep one. The single-card editor goes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 18:42:25 +02:00
@ override
String get vocabularyReading = > ' Reading ' ;
@ override
String get vocabularyReadingHint = > ' Kana reading ' ;
@ override
String get vocabularyMeanings = > ' Meanings ' ;
@ override
String get vocabularyPrimaryMeaningHint = > ' Primary meaning ' ;
@ override
String get vocabularyMeaningHint = > ' Meaning ' ;
@ override
String get vocabularyAddMeaning = > ' Add meaning ' ;
@ override
String get vocabularyRemoveMeaning = > ' Remove meaning ' ;
@ override
String get matchingCategories = > ' Categories ' ;
@ override
String get matchingCategoriesHint = >
' The drop-zone cards learners drag items onto. Each category needs at least one item, so a card can \' t have more categories than items. ' ;
@ override
String matchingCategory ( int number ) {
return ' Category $ number ' ;
}
@ override
String get matchingAddCategory = > ' Add category ' ;
@ override
String get matchingAddCategoryBlocked = >
' Add an item first: a card can \' t have more categories than items ' ;
@ override
String get matchingRemoveCategory = > ' Remove category ' ;
@ override
String get matchingMinCategories = > ' At least two categories required ' ;
@ override
String get matchingItems = > ' Items ' ;
@ override
String get matchingItemsHint = >
' The draggable items. Assign every item to its correct category: an unassigned item can never be placed right. ' ;
@ override
String matchingItem ( int number ) {
return ' Item $ number ' ;
}
@ override
String get matchingAddItem = > ' Add item ' ;
@ override
String get matchingUnassigned = > ' Unassigned ' ;
@ override
String get matchingRemoveItem = > ' Remove item ' ;
@ override
String get matchingMinItems = > ' At least two items required ' ;
2026-10-01 18:58:54 +02:00
@ override
String get cardTileOptions = > ' Options ' ;
@ override
String get cardTileEdit = > ' Edit ' ;
@ override
String get cardTileDelete = > ' Delete ' ;
@ override
String get cardTileUntitled = > ' Untitled ' ;
@ override
String get cardTileConflict = > ' Changed on two devices ' ;
feat(app): study sessions in rounds, by card type, cramming and reversed
A session studies a round of up to the settings' cards per round. In
spaced repetition a card leaves the round once it is a day or more away,
one rated lower comes back when due; cramming walks every card once,
round after round, its reviews moving no schedule, with going back and
on. Asking the back first (a toggle in the header, saved in the
settings) asks front/back and vocabulary cards their back, on their own
schedule. Each type studies its way: a word shows its reading and
meanings, a multiple-choice card is picked and checked, a matching card
sorted by dragging or tapping and checked; then the person rates (Bad,
OK, Good, Perfect, each with when the card comes back). Progress shows
as pills, or a tally bar while cramming; a round ends with how it went.
The session settings gain the mode, cards per round, new cards per day
and the order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 19:29:21 +02:00
@ override
String get studyClose = > ' Close session ' ;
@ override
String get studyBackFirstOn = >
' Showing the back of cards first: tap to revert ' ;
@ override
String get studyBackFirstOff = > ' Show the back of cards first ' ;
@ override
String get studyShowAnswer = > ' Show answer ' ;
@ override
String get studyCheck = > ' Check ' ;
@ override
String get studyRatingBad = > ' Bad ' ;
@ override
String get studyRatingOk = > ' OK ' ;
@ override
String get studyRatingGood = > ' Good ' ;
@ override
String get studyRatingPerfect = > ' Perfect ' ;
@ override
String get studyNothingTitle = > ' Nothing to study right now ' ;
@ override
String get studyNothingBody = > ' Come back later when more cards are due. ' ;
@ override
String studyNextDue ( String interval ) {
return ' The next card is due in $ interval . ' ;
}
@ override
String get studyDone = > ' Done ' ;
@ override
String get studyRoundDone = > ' Round complete ' ;
@ override
String get studyContinue = > ' Continue learning ' ;
@ override
String get studyPrevious = > ' Previous card ' ;
@ override
String get studyNext = > ' Next card ' ;
@ override
String get studyReading = > ' Reading ' ;
@ override
String get studyMeanings = > ' Meanings ' ;
@ override
String get studyPlaceItems = >
' Drag each item onto its category, or tap an item and then a category. ' ;
@ override
String studyMinutes ( int count ) {
return ' $ count min ' ;
}
@ override
String studyHours ( int count ) {
return ' $ count h ' ;
}
@ override
String studyDays ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' $ count days ' ,
one: ' 1 day ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String studyMonths ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' $ count months ' ,
one: ' 1 month ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String studyYears ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' $ count years ' ,
one: ' 1 year ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String studyFilteredBy ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' Narrowed to $ count tags ' ,
one: ' Narrowed to 1 tag ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String get studySettingsCardsPerRound = > ' Cards per round ' ;
@ override
String get studySettingsNewPerDay = > ' New cards per day ' ;
@ override
String get studySettingsNewPerDayHint = >
feat(app): the library's options move, import into and set how a studyset is studied
A studyset's options in the library now offer, as on its own screen,
Edit, Move to…, Study options, Import from Anki, Archive and Delete.
Moving goes through the same sheet and says where the studyset went;
importing reads an Anki deck into it as Add material does.
Study options say how a studyset is studied, on every device: how many
new cards it starts a day (by default as the person's study settings
say), how many reviews it brings a day (no limit by default; cards in
their learning steps come anyway), and whether reviews or new cards
come first, or the new ones spread evenly among the reviews. They live
in the studyset's payload, schema 3; a studyset without them is still
written in schema 2, so that older apps, which skip a payload in a
newer schema whole, keep seeing every studyset nobody gave options.
Editing a studyset keeps its options, and Reset to defaults takes them
away.
The study queue takes each studyset's limits for its own cards and the
order of the studyset studied; a card's schedule now knows when it was
last reviewed, to count the reviews of the day. Sessions, the round's
end and the library's badges all count the same way, the badges now
with the person's own number of new cards a day too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-07 02:18:42 +02:00
' How many never-studied cards each studyset starts a day, unless its own study options say otherwise. ' ;
@ override
String get studyOptionsTitle = > ' Study options ' ;
@ override
String get studyOptionsDescription = >
' How many cards this studyset brings a day, and which come first, on every device. The studysets inside it keep their own. ' ;
@ override
String get studyOptionsReviewsPerDay = > ' Reviews per day ' ;
@ override
String get studyOptionsReviewsPerDayHint = >
' Cards in their learning steps come anyway. ' ;
@ override
String get studyOptionsOrder = > ' Order ' ;
@ override
String get studyOptionsReviewsFirst = > ' Reviews first ' ;
@ override
String get studyOptionsMixed = > ' Mixed ' ;
@ override
String get studyOptionsNewFirst = > ' New first ' ;
@ override
String get studyOptionsReset = > ' Reset to defaults ' ;
feat(app): study sessions in rounds, by card type, cramming and reversed
A session studies a round of up to the settings' cards per round. In
spaced repetition a card leaves the round once it is a day or more away,
one rated lower comes back when due; cramming walks every card once,
round after round, its reviews moving no schedule, with going back and
on. Asking the back first (a toggle in the header, saved in the
settings) asks front/back and vocabulary cards their back, on their own
schedule. Each type studies its way: a word shows its reading and
meanings, a multiple-choice card is picked and checked, a matching card
sorted by dragging or tapping and checked; then the person rates (Bad,
OK, Good, Perfect, each with when the card comes back). Progress shows
as pills, or a tally bar while cramming; a round ends with how it went.
The session settings gain the mode, cards per round, new cards per day
and the order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 19:29:21 +02:00
@ override
String get studySettingsUnlimited = > ' Unlimited ' ;
@ override
String get studySettingsSortBy = > ' Sort by ' ;
@ override
String get studySettingsLearnMode = > ' Learn mode ' ;
@ override
String get studyModeSpaced = > ' Spaced repetition ' ;
@ override
String get studyModeSpacedHint = >
' Due cards on their schedules; ratings move them. ' ;
@ override
String get studyModeCram = > ' Cram ' ;
@ override
String get studyModeCramHint = >
' Walk every card without touching the schedules. ' ;
@ override
String get studySortOldestFirst = > ' Oldest first ' ;
@ override
String get studySortRandom = > ' Random ' ;
@ override
String get studySortNewestFirst = > ' Newest first ' ;
@ override
String get studyFewer = > ' Fewer ' ;
@ override
String get studyMore = > ' More ' ;
2026-10-01 19:34:44 +02:00
@ override
String get studysetMenuReset = > ' Reset progress ' ;
@ override
String get studysetResetTitle = > ' Reset progress? ' ;
@ override
String get studysetResetMessage = >
' Every flashcard in it, and in the studysets inside it, starts afresh as new, on all your devices. Your study history stays. You can undo this for a few seconds. ' ;
@ override
String get studysetResetConfirm = > ' Reset ' ;
@ override
String get studysetResetDone = > ' Progress reset ' ;
2026-10-01 19:56:29 +02:00
@ override
String get mapUnknown = > ' This map needs a newer version of the app. ' ;
@ override
String get mapPickRegion = >
' Tap the region that answers the question; pinch or scroll to zoom. ' ;
@ override
String get mapNonePicked = > ' No region picked yet ' ;
@ override
String get composerPlaceholderQuestion = > ' Add question ' ;
@ override
String get composerLabelRegion = > ' Answer ' ;
feat(app): the home dashboard: streak, goal, week, activity
Home opens on a study dashboard. The streak's flame sits in the app bar
and opens a sheet of how it stands this week. Below: the study goal (a
ring of the day's or week's progress, set and removed in its sheet), the
week (each day studied, rested, missed or to come with the cards falling
due; days to go back and on; today or a day to come made a rest day),
and a year of activity as a heatmap, its weekdays staying in view while
the weeks scroll. A long press arranges the dashboard: drag a widget
elsewhere, hide or show it; the arrangement is synced.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 22:13:34 +02:00
@ override
String get homeStreakTooltip = > ' Study streak ' ;
@ override
String get homeArrange = > ' Arrange widgets ' ;
@ override
String get homeDone = > ' Done ' ;
@ override
String get homeDragToReorder = > ' Drag to reorder ' ;
@ override
String homeShowWidget ( String label ) {
return ' Show $ label ' ;
}
@ override
String homeHideWidget ( String label ) {
return ' Hide $ label ' ;
}
@ override
String get homeAllHidden = > ' All widgets are hidden ' ;
@ override
String get homeAllHiddenHint = > ' Long-press here to bring some back. ' ;
@ override
String get homeWidgetGoal = > ' Study goal ' ;
@ override
String get homeWidgetWeek = > ' Week overview ' ;
@ override
String get homeWidgetActivity = > ' Activity heatmap ' ;
@ override
String get homeActivityTitle = > ' Your activity ' ;
@ override
String streakDays ( num count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' $ count day streak ' ,
one: ' 1 day streak ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String get streakStart = > ' Study today to start your streak. ' ;
@ override
String get streakGrow = >
' Keep it going: study at least once a day to grow your streak. ' ;
@ override
String get streakFirstDay = >
' Review some cards today to light the first day. ' ;
@ override
String get streakComeBack = > ' Come back tomorrow to keep your streak. ' ;
@ override
String get streakKeepAlive = > ' Study today to keep your streak alive. ' ;
@ override
String get goalEditTitle = > ' Edit study goal ' ;
@ override
String get goalSetTitle = > ' Set a study goal ' ;
@ override
String get goalDescriptionDaily = >
' Review a number of flashcards each day. Every card you rate counts. ' ;
@ override
String get goalDescriptionWeekly = >
' Review a number of flashcards each week, from Monday. Every card you rate counts. ' ;
@ override
String get goalPeriod = > ' Period ' ;
@ override
String get goalPerDay = > ' Per day ' ;
@ override
String get goalPerWeek = > ' Per week ' ;
@ override
String get goalCardsPerDay = > ' Cards per day ' ;
@ override
String get goalCardsPerWeek = > ' Cards per week ' ;
@ override
String get goalSave = > ' Save goal ' ;
@ override
String get goalRemove = > ' Remove goal ' ;
@ override
String get goalEmptySubtitle = >
' Review a set number of cards each day or week. ' ;
@ override
String get goalDailyHeadline = > ' Daily goal ' ;
@ override
String get goalWeeklyHeadline = > ' Weekly goal ' ;
@ override
String goalProgressToday ( int reviewed , int target ) {
return ' $ reviewed of $ target cards today ' ;
}
@ override
String goalProgressThisWeek ( int reviewed , int target ) {
return ' $ reviewed of $ target cards this week ' ;
}
@ override
String get goalMet = > ' Goal met: nice work! ' ;
@ override
String goalRemaining ( int count ) {
return ' $ count to go ' ;
}
@ override
String get weekToday = > ' Today ' ;
@ override
String get weekPreviousDay = > ' Previous day ' ;
@ override
String get weekNextDay = > ' Next day ' ;
@ override
String get weekRestDay = > ' Rest day ' ;
@ override
String weekDueCount ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' $ count cards due ' ,
one: ' 1 card due ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String get heatmapLess = > ' Less ' ;
@ override
String get heatmapMore = > ' More ' ;
@ override
String heatmapTotal ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' $ count activities in the last year ' ,
one: ' 1 activity in the last year ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String heatmapDay ( int count , String date ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' $ count activities ' ,
one: ' 1 activity ' ,
zero: ' Nothing ' ,
) ;
return ' $ _temp0 · $ date ' ;
}
feat(app): the UI foundation: theme, tabs and routes, strings
The app gets its look and navigation before more features land on them.
- Theme (core/theme/): hand-written light and dark Material 3 colour schemes around
cobalt and copper; ThemeExtras for the colours Material has no role for (states, the
four FSRS ratings); the 20 colours a person can give content. Tests hold text to WCAG
AA contrast on each palette colour and on the dark theme's primary colour and surface.
- Layout and shared widgets: Material 3's window-size classes (core/layout/);
SectionCard, AppLoadingIndicator, confirmDestructiveAction and FoundById
(core/widget/). Lucide icons.
- Navigation with go_router: four tabs (Home, Library, Explore, Profile), each keeping
its own stack of screens, as a navigation rail with the app's icon from 600 dp and as
a navigation bar below; choosing the open tab again returns to its first screen. A
studyset's screen sits in the library; studying, cards and settings cover the tabs.
- Routes carry ids: FoundById finds the item in the repository's live list, so a
studyset deleted on another device, or an old link, says it no longer exists. A
screen that deletes its own item keeps showing it while it closes, rather than
flashing that message.
- Every person gets an app of their own (PersonApp, lib/src/app/): a
MaterialApp.router with its own GoRouter and PersonScope around the navigator, built
by SessionGate inside the person's keyed subtree. Screens read the person's
repositories wherever they are built, so the static route() helpers and the listener
that closed every pushed screen on a change of person are gone: a change of person
now disposes the whole router and starts again at home.
- The account dialog becomes the Profile tab, with Settings (sign out, delete the
account, About) behind a gear. A waiting GitHub sign-in now keeps waiting while
another tab shows; a change of person still cancels it.
- The new screens' strings come from Flutter's gen-l10n (English for now). The
generated files are committed, left out of analysis and formatting, and written
anew by `flutter pub get`, so CI's check of generated code also catches stale ones.
Widget tests now start the whole app (test/support/pump_app.dart) at the screen's path
and move through it as a person would, by tabs and taps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:18:31 +02:00
@ override
String get commonCancel = > ' Cancel ' ;
feat(app): editor blocks: callout, spoiler, formulas, files, video, study button
Blocks beyond the editor's own, each built once for editor and view:
- callout: eight kinds in theme colours, kind, title and "folded at
first" from its header; written in place, left with Enter on its
empty last line; never inside another
- spoiler: written in plain sight, hidden behind Reveal read-only
- formula on its own line, written on its own screen with a preview
and snippet buttons, and formulas within a line (Σ in the format
bar); set by flutter_math_fork behind MathView, LaTeX's numbered
environments read as their aligned forms
- image and sound picked from the device, kept as synced files
- YouTube video from its link, with its start and a caption: a card
until played, nothing fetched before; played in place where a web
view exists (youtube_player_iframe, privacy-enhanced), else opened
in the browser, behind YoutubeVideos in Dependencies
- study button: a studyset, a tag or all cards, with or without its
topics, back first; its StudyFocus rides on the study route and
narrows that session only
- tables, already in the editor, now insertable everywhere
An Insert button opens on the format bar, so blocks can be inserted on
phones too; the desktop's / menu lists the same blocks, translated.
Card faces now dock the format bar while written, so cards get blocks
and formatting on phones as well. The blocks are listed in
EditorBlocks: the core's, plus the features' through
EditorBlocksScope. Blocks of a type the app doesn't know say they need
a newer version and stay as they are, so formatVersion stays 1.
Their types and keys are in content_blocks.dart, which RichContent
reads: a block's text counts as text, and a video or study button
isn't blank. The path import now makes formulas real ones.
integration_test/youtube_player_test.dart plays a real video through
the app's player on a device, which no widget test can: YouTube
refuses embeds that don't say where they are embedded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-03 12:20:41 +02:00
@ override
String get commonSave = > ' Save ' ;
feat(app): the UI foundation: theme, tabs and routes, strings
The app gets its look and navigation before more features land on them.
- Theme (core/theme/): hand-written light and dark Material 3 colour schemes around
cobalt and copper; ThemeExtras for the colours Material has no role for (states, the
four FSRS ratings); the 20 colours a person can give content. Tests hold text to WCAG
AA contrast on each palette colour and on the dark theme's primary colour and surface.
- Layout and shared widgets: Material 3's window-size classes (core/layout/);
SectionCard, AppLoadingIndicator, confirmDestructiveAction and FoundById
(core/widget/). Lucide icons.
- Navigation with go_router: four tabs (Home, Library, Explore, Profile), each keeping
its own stack of screens, as a navigation rail with the app's icon from 600 dp and as
a navigation bar below; choosing the open tab again returns to its first screen. A
studyset's screen sits in the library; studying, cards and settings cover the tabs.
- Routes carry ids: FoundById finds the item in the repository's live list, so a
studyset deleted on another device, or an old link, says it no longer exists. A
screen that deletes its own item keeps showing it while it closes, rather than
flashing that message.
- Every person gets an app of their own (PersonApp, lib/src/app/): a
MaterialApp.router with its own GoRouter and PersonScope around the navigator, built
by SessionGate inside the person's keyed subtree. Screens read the person's
repositories wherever they are built, so the static route() helpers and the listener
that closed every pushed screen on a change of person are gone: a change of person
now disposes the whole router and starts again at home.
- The account dialog becomes the Profile tab, with Settings (sign out, delete the
account, About) behind a gear. A waiting GitHub sign-in now keeps waiting while
another tab shows; a change of person still cancels it.
- The new screens' strings come from Flutter's gen-l10n (English for now). The
generated files are committed, left out of analysis and formatting, and written
anew by `flutter pub get`, so CI's check of generated code also catches stale ones.
Widget tests now start the whole app (test/support/pump_app.dart) at the screen's path
and move through it as a person would, by tabs and taps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:18:31 +02:00
@ override
String get commonClose = > ' Close ' ;
@ override
String get commonUndo = > ' Undo ' ;
@ override
String get commonTryAgain = > ' Try again ' ;
feat(app): the app speaks British and American English and German
Settings offers English (UK), English (US) and Deutsch after the
device's language, each named in its own language. app_en.arb, the
template, is now British English (colour, centre) and the fallback for
any language the app doesn't speak; app_en_US.arb overrides the five
messages American English spells differently, and app_en_GB.arb is
empty, there so that Material's texts and dates are British too. The
German translation says "du" and keeps one set of terms throughout
(Lernset, Karteikarte, Lernpfad; Anki's own Stapel and Unterstapel in
the Anki import). arb_files_test checks that German has every message
of the template and that every translation keeps its placeholders.
resolveAppLocale picks the first language the device prefers that the
app speaks, in its country's variant where there is one; other English
is British, and a country alone never picks a language (Spanish in the
United States isn't American English), which Flutter's own resolution
would do.
Both MaterialApps take material_ui's GlobalMaterialLocalizations
delegates: gen-l10n's list brings flutter_localizations', which serve
only package:flutter/material.dart, so material_ui's widgets found no
Material texts in German and threw.
The editor's own text blocks show our placeholders while empty (list
item, to-do, quote, heading, and the desktop's / hint): the editor's
own follow a locale of their own, process-wide, in few languages. The
last texts written into widgets (the person's data failing to open,
the cards failing to read, the About dialog) moved into the arb files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-07 02:01:01 +02:00
@ override
String get personDataFailed = > ' Couldn \' t open this device \' s data. ' ;
feat(app): the UI foundation: theme, tabs and routes, strings
The app gets its look and navigation before more features land on them.
- Theme (core/theme/): hand-written light and dark Material 3 colour schemes around
cobalt and copper; ThemeExtras for the colours Material has no role for (states, the
four FSRS ratings); the 20 colours a person can give content. Tests hold text to WCAG
AA contrast on each palette colour and on the dark theme's primary colour and surface.
- Layout and shared widgets: Material 3's window-size classes (core/layout/);
SectionCard, AppLoadingIndicator, confirmDestructiveAction and FoundById
(core/widget/). Lucide icons.
- Navigation with go_router: four tabs (Home, Library, Explore, Profile), each keeping
its own stack of screens, as a navigation rail with the app's icon from 600 dp and as
a navigation bar below; choosing the open tab again returns to its first screen. A
studyset's screen sits in the library; studying, cards and settings cover the tabs.
- Routes carry ids: FoundById finds the item in the repository's live list, so a
studyset deleted on another device, or an old link, says it no longer exists. A
screen that deletes its own item keeps showing it while it closes, rather than
flashing that message.
- Every person gets an app of their own (PersonApp, lib/src/app/): a
MaterialApp.router with its own GoRouter and PersonScope around the navigator, built
by SessionGate inside the person's keyed subtree. Screens read the person's
repositories wherever they are built, so the static route() helpers and the listener
that closed every pushed screen on a change of person are gone: a change of person
now disposes the whole router and starts again at home.
- The account dialog becomes the Profile tab, with Settings (sign out, delete the
account, About) behind a gear. A waiting GitHub sign-in now keeps waiting while
another tab shows; a change of person still cancels it.
- The new screens' strings come from Flutter's gen-l10n (English for now). The
generated files are committed, left out of analysis and formatting, and written
anew by `flutter pub get`, so CI's check of generated code also catches stale ones.
Widget tests now start the whole app (test/support/pump_app.dart) at the screen's path
and move through it as a person would, by tabs and taps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:18:31 +02:00
@ override
String get sessionUnreachable = > ' Couldn \' t reach the server. ' ;
@ override
String get notFoundGone = >
' This no longer exists. It may have been deleted on another device. ' ;
Import Anki decks with their files and history
Files in content: the person's images and sounds as asset rows synced like
all content, their files kept by hash on each device, uploaded after each
sync, downloaded on demand and checked against their hash
(core/asset/AssetRepository). Rich text names them as asset:<id> URLs, in
the editor's image block and an audio block of ours; any other URL is never
fetched. Sounds play through audioplayers, one at a time. Content holding
only an image or a sound isn't blank, and lists show it as such.
The Anki import ("Import from Anki" under Add material): .apkg and .colpkg
packages in all three formats Anki has written, read on an isolate of their
own. Modern packages compress everything with zstd in frames that don't
record their size, which pure_zstd v1.0.0 refuses; our fork decodes them.
Fields become rich text by a converter of our own (lines, lists, formatting,
images, sounds, cloze deletions hidden on the front). One card per note,
the files first, studysets as chosen (flatten, sub-studysets, or tags by
subdeck), the notes' tags, and, if wanted, the review history, which FSRS
replays: the first template's reviews to the card's schedule, a reversed
note type's second to the reverse one.
Imported cards say where they came from in their first revision (card
schema 3); the dashboard doesn't count them as written. The test fixtures
are exported by Anki itself (tool/anki/make_fixtures.py).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 04:52:02 +02:00
@ override
String get fileImage = > ' Image ' ;
@ override
String get fileSound = > ' Sound ' ;
@ override
String get fileImageUnavailable = >
' This image isn \' t on this device yet. It shows once it \' s uploaded and you \' re online. ' ;
Fix what the first Android run found
The flow ran on a Pixel 9a and its emulator (2026-10-02); what it found:
- The composer stayed on the card just written after "New flashcard", and
on the original after "Duplicate": leaving a card changed the state
before the new one was in it, so the screen took the new card for gone.
It now waits for the card it asked for.
- Bottom sheets reached under the status bar with the keyboard open
(useSafeArea).
- The field being written stayed behind the keyboard: the editor scrolls
only within itself, so the card face now keeps it in view when it opens
and when the keyboard rises, as a text field does.
- A tap on read-only rich text put a caret in it and opened the keyboard on
phones, and in the composer never opened the field. RichContentView is
display only now: no selection, taps go to what surrounds it, a sound's
button still plays (decided 2026-10-02).
- The card types broke their names on a phone; they are choice chips that
wrap (decided 2026-10-02).
- The week overview marked days before the person's first activity as
missed; they show nothing now. An import starts the activity too, without
counting as studying (decided 2026-10-02).
- A composer preview cut short gave no sign of it; it fades out over its
last 32 dp, only when cut short (decided 2026-10-02).
- "Isn't on this device yet" showed for any image or sound that failed;
a file that is here but can't be decoded now says so, and offers no retry.
- The signed-in account card was narrower than the anonymous one.
- The composer's title said "Create flashcards" also when opened on a
stored card; it says "Edit flashcards in …" then (decided 2026-10-02).
- The Anki fixtures carry a valid PNG and a silent MP3, so they also serve
to try the import by hand (decided 2026-10-02); regenerated with Anki
26.9.3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 19:52:39 +02:00
@ override
String get fileImageUnreadable = >
' This image can \' t be shown. Its file may be damaged, or in a format this device can \' t read. ' ;
Import Anki decks with their files and history
Files in content: the person's images and sounds as asset rows synced like
all content, their files kept by hash on each device, uploaded after each
sync, downloaded on demand and checked against their hash
(core/asset/AssetRepository). Rich text names them as asset:<id> URLs, in
the editor's image block and an audio block of ours; any other URL is never
fetched. Sounds play through audioplayers, one at a time. Content holding
only an image or a sound isn't blank, and lists show it as such.
The Anki import ("Import from Anki" under Add material): .apkg and .colpkg
packages in all three formats Anki has written, read on an isolate of their
own. Modern packages compress everything with zstd in frames that don't
record their size, which pure_zstd v1.0.0 refuses; our fork decodes them.
Fields become rich text by a converter of our own (lines, lists, formatting,
images, sounds, cloze deletions hidden on the front). One card per note,
the files first, studysets as chosen (flatten, sub-studysets, or tags by
subdeck), the notes' tags, and, if wanted, the review history, which FSRS
replays: the first template's reviews to the card's schedule, a reversed
note type's second to the reverse one.
Imported cards say where they came from in their first revision (card
schema 3); the dashboard doesn't count them as written. The test fixtures
are exported by Anki itself (tool/anki/make_fixtures.py).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 04:52:02 +02:00
@ override
String get fileFromElsewhere = > ' Files from the web aren \' t shown. ' ;
@ override
String get filePlaySound = > ' Play sound ' ;
@ override
String get fileStopSound = > ' Stop sound ' ;
@ override
String get fileSoundUnavailable = >
' This sound isn \' t on this device yet. It plays once it \' s uploaded and you \' re online. ' ;
Fix what the first Android run found
The flow ran on a Pixel 9a and its emulator (2026-10-02); what it found:
- The composer stayed on the card just written after "New flashcard", and
on the original after "Duplicate": leaving a card changed the state
before the new one was in it, so the screen took the new card for gone.
It now waits for the card it asked for.
- Bottom sheets reached under the status bar with the keyboard open
(useSafeArea).
- The field being written stayed behind the keyboard: the editor scrolls
only within itself, so the card face now keeps it in view when it opens
and when the keyboard rises, as a text field does.
- A tap on read-only rich text put a caret in it and opened the keyboard on
phones, and in the composer never opened the field. RichContentView is
display only now: no selection, taps go to what surrounds it, a sound's
button still plays (decided 2026-10-02).
- The card types broke their names on a phone; they are choice chips that
wrap (decided 2026-10-02).
- The week overview marked days before the person's first activity as
missed; they show nothing now. An import starts the activity too, without
counting as studying (decided 2026-10-02).
- A composer preview cut short gave no sign of it; it fades out over its
last 32 dp, only when cut short (decided 2026-10-02).
- "Isn't on this device yet" showed for any image or sound that failed;
a file that is here but can't be decoded now says so, and offers no retry.
- The signed-in account card was narrower than the anonymous one.
- The composer's title said "Create flashcards" also when opened on a
stored card; it says "Edit flashcards in …" then (decided 2026-10-02).
- The Anki fixtures carry a valid PNG and a silent MP3, so they also serve
to try the import by hand (decided 2026-10-02); regenerated with Anki
26.9.3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 19:52:39 +02:00
@ override
String get fileSoundUnplayable = >
' This sound can \' t be played. Its file may be damaged, or in a format this device can \' t play. ' ;
feat(app): editor blocks: callout, spoiler, formulas, files, video, study button
Blocks beyond the editor's own, each built once for editor and view:
- callout: eight kinds in theme colours, kind, title and "folded at
first" from its header; written in place, left with Enter on its
empty last line; never inside another
- spoiler: written in plain sight, hidden behind Reveal read-only
- formula on its own line, written on its own screen with a preview
and snippet buttons, and formulas within a line (Σ in the format
bar); set by flutter_math_fork behind MathView, LaTeX's numbered
environments read as their aligned forms
- image and sound picked from the device, kept as synced files
- YouTube video from its link, with its start and a caption: a card
until played, nothing fetched before; played in place where a web
view exists (youtube_player_iframe, privacy-enhanced), else opened
in the browser, behind YoutubeVideos in Dependencies
- study button: a studyset, a tag or all cards, with or without its
topics, back first; its StudyFocus rides on the study route and
narrows that session only
- tables, already in the editor, now insertable everywhere
An Insert button opens on the format bar, so blocks can be inserted on
phones too; the desktop's / menu lists the same blocks, translated.
Card faces now dock the format bar while written, so cards get blocks
and formatting on phones as well. The blocks are listed in
EditorBlocks: the core's, plus the features' through
EditorBlocksScope. Blocks of a type the app doesn't know say they need
a newer version and stay as they are, so formatVersion stays 1.
Their types and keys are in content_blocks.dart, which RichContent
reads: a block's text counts as text, and a video or study button
isn't blank. The path import now makes formulas real ones.
integration_test/youtube_player_test.dart plays a real video through
the app's player on a device, which no widget test can: YouTube
refuses embeds that don't say where they are embedded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-03 12:20:41 +02:00
@ override
String get fileImages = > ' Images ' ;
@ override
String get fileSounds = > ' Sounds ' ;
@ override
String get fileNotAnImage = > ' This file isn \' t an image the app can show. ' ;
@ override
String get fileNotASound = > ' This file isn \' t a sound the app can play. ' ;
@ override
String fileTooLarge ( int megabytes ) {
return ' This file is larger than $ megabytes MB. ' ;
}
@ override
String get fileNotKept = > ' The file couldn \' t be kept on this device. ' ;
@ override
String get fileRemoveImage = > ' Remove image ' ;
@ override
String get fileRemoveSound = > ' Remove sound ' ;
@ override
String get editorInsert = > ' Insert ' ;
@ override
String get blockGroupText = > ' Text ' ;
@ override
String get blockGroupBlocks = > ' Blocks ' ;
@ override
String get blockGroupMedia = > ' Media ' ;
@ override
String blockHeading ( int level ) {
return ' Heading $ level ' ;
}
@ override
String get blockBulletedList = > ' Bulleted list ' ;
@ override
String get blockNumberedList = > ' Numbered list ' ;
@ override
String get blockChecklist = > ' Checklist ' ;
@ override
String get blockQuote = > ' Quote ' ;
feat(app): the app speaks British and American English and German
Settings offers English (UK), English (US) and Deutsch after the
device's language, each named in its own language. app_en.arb, the
template, is now British English (colour, centre) and the fallback for
any language the app doesn't speak; app_en_US.arb overrides the five
messages American English spells differently, and app_en_GB.arb is
empty, there so that Material's texts and dates are British too. The
German translation says "du" and keeps one set of terms throughout
(Lernset, Karteikarte, Lernpfad; Anki's own Stapel and Unterstapel in
the Anki import). arb_files_test checks that German has every message
of the template and that every translation keeps its placeholders.
resolveAppLocale picks the first language the device prefers that the
app speaks, in its country's variant where there is one; other English
is British, and a country alone never picks a language (Spanish in the
United States isn't American English), which Flutter's own resolution
would do.
Both MaterialApps take material_ui's GlobalMaterialLocalizations
delegates: gen-l10n's list brings flutter_localizations', which serve
only package:flutter/material.dart, so material_ui's widgets found no
Material texts in German and threw.
The editor's own text blocks show our placeholders while empty (list
item, to-do, quote, heading, and the desktop's / hint): the editor's
own follow a locale of their own, process-wide, in few languages. The
last texts written into widgets (the person's data failing to open,
the cards failing to read, the About dialog) moved into the arb files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-07 02:01:01 +02:00
@ override
String get blockListItem = > ' List item ' ;
@ override
String get blockChecklistItem = > ' To-do ' ;
@ override
String get editorSlashHint = >
' Type / to insert a block, or just start typing ' ;
feat(app): editor blocks: callout, spoiler, formulas, files, video, study button
Blocks beyond the editor's own, each built once for editor and view:
- callout: eight kinds in theme colours, kind, title and "folded at
first" from its header; written in place, left with Enter on its
empty last line; never inside another
- spoiler: written in plain sight, hidden behind Reveal read-only
- formula on its own line, written on its own screen with a preview
and snippet buttons, and formulas within a line (Σ in the format
bar); set by flutter_math_fork behind MathView, LaTeX's numbered
environments read as their aligned forms
- image and sound picked from the device, kept as synced files
- YouTube video from its link, with its start and a caption: a card
until played, nothing fetched before; played in place where a web
view exists (youtube_player_iframe, privacy-enhanced), else opened
in the browser, behind YoutubeVideos in Dependencies
- study button: a studyset, a tag or all cards, with or without its
topics, back first; its StudyFocus rides on the study route and
narrows that session only
- tables, already in the editor, now insertable everywhere
An Insert button opens on the format bar, so blocks can be inserted on
phones too; the desktop's / menu lists the same blocks, translated.
Card faces now dock the format bar while written, so cards get blocks
and formatting on phones as well. The blocks are listed in
EditorBlocks: the core's, plus the features' through
EditorBlocksScope. Blocks of a type the app doesn't know say they need
a newer version and stay as they are, so formatVersion stays 1.
Their types and keys are in content_blocks.dart, which RichContent
reads: a block's text counts as text, and a video or study button
isn't blank. The path import now makes formulas real ones.
integration_test/youtube_player_test.dart plays a real video through
the app's player on a device, which no widget test can: YouTube
refuses embeds that don't say where they are embedded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-03 12:20:41 +02:00
@ override
String get blockDivider = > ' Divider ' ;
@ override
String get blockTable = > ' Table ' ;
@ override
String get blockRemove = > ' Remove ' ;
@ override
String get blockUnknown = > ' This part needs a newer version of the app. ' ;
@ override
String get blockCallout = > ' Callout ' ;
@ override
String get blockSpoiler = > ' Spoiler ' ;
@ override
String get calloutNote = > ' Note ' ;
@ override
String get calloutInfo = > ' Info ' ;
@ override
String get calloutTip = > ' Tip ' ;
@ override
String get calloutSuccess = > ' Success ' ;
@ override
String get calloutQuestion = > ' Question ' ;
@ override
String get calloutWarning = > ' Warning ' ;
@ override
String get calloutDanger = > ' Danger ' ;
@ override
String get calloutQuote = > ' Quote ' ;
@ override
String get calloutOptions = > ' Callout options ' ;
@ override
String get calloutEditTitle = > ' Edit title… ' ;
@ override
String get calloutFoldedAtFirst = > ' Folded at first ' ;
@ override
String get calloutRemove = > ' Remove callout ' ;
@ override
String get calloutShow = > ' Show ' ;
@ override
String get calloutHide = > ' Hide ' ;
@ override
String get calloutWriteInside = > ' Write inside ' ;
@ override
String get calloutTitleDialog = > ' Callout title ' ;
@ override
String get calloutTitleLabel = > ' Title ' ;
@ override
String get calloutTitleHelper = > ' Leave it empty to show the kind \' s name. ' ;
@ override
String get spoilerReveal = > ' Reveal ' ;
@ override
String get spoilerPlaceholder = > ' Hidden text, revealed on a tap ' ;
@ override
String get blockFormula = > ' Formula ' ;
@ override
String get formatInlineMath = > ' Formula in the line ' ;
@ override
String get formulaTitle = > ' Formula ' ;
@ override
String get formulaInLineTitle = > ' Formula in the line ' ;
@ override
String get formulaPreview = > ' Preview ' ;
@ override
String get formulaPreviewEmpty = > ' The formula shows here ' ;
@ override
String get formulaLatex = > ' LaTeX ' ;
@ override
String get formulaLatexHelper = >
' \\ \\ starts a new line in aligned, cases or a matrix. ' ;
@ override
String get formulaEmpty = > ' An empty formula. Tap to write it. ' ;
@ override
String get formulaEdit = > ' Edit formula ' ;
@ override
String get formulaRemove = > ' Remove formula ' ;
@ override
String get blockYoutube = > ' YouTube video ' ;
@ override
String get youtubeLink = > ' YouTube link ' ;
@ override
String get youtubeLinkHelper = > ' Paste a link from YouTube. ' ;
@ override
String get youtubeNotFound = > ' No YouTube video in this link. ' ;
@ override
String get youtubeFound = > ' Found the video. ' ;
@ override
String youtubeFoundFrom ( String time ) {
return ' Found the video, starting at $ time . ' ;
}
@ override
String get youtubeCaption = > ' Caption ' ;
@ override
String get youtubeCaptionHelper = >
' Shown in place of the video until it plays. ' ;
@ override
String get youtubeVideo = > ' YouTube video ' ;
@ override
String get youtubeSource = > ' YouTube ' ;
@ override
String youtubeFrom ( String time ) {
return ' YouTube · from $ time ' ;
}
@ override
String get youtubeEdit = > ' Edit video ' ;
@ override
String get youtubePlay = > ' Play video ' ;
@ override
String get youtubeOpen = > ' Open on YouTube ' ;
@ override
String get youtubeRemove = > ' Remove video ' ;
@ override
String get youtubeBroken = > ' This video \' s link can \' t be read. ' ;
@ override
String get youtubeNotOpened = > ' Couldn \' t open YouTube. ' ;
@ override
String get blockStudyButton = > ' Study button ' ;
@ override
String get studyButtonDefaultLabel = > ' Study ' ;
@ override
String get studyButtonStudyset = > ' Studyset to study ' ;
@ override
String get studyButtonTag = > ' Cards ' ;
@ override
String get studyButtonAllCards = > ' All cards ' ;
@ override
String get studyButtonLabel = > ' Label ' ;
@ override
String get studyButtonTopics = > ' With its topics ' ;
@ override
String get studyButtonBackFirst = > ' Back first ' ;
@ override
String get studyButtonBackFirstHelper = >
' Front/back and vocabulary cards ask their back. ' ;
@ override
String studyButtonWithTag ( String tag ) {
return ' Tagged $ tag ' ;
}
@ override
String get studyButtonAlone = > ' Without its topics ' ;
@ override
String get studyButtonEdit = > ' Edit study button ' ;
@ override
String get studyButtonRemove = > ' Remove study button ' ;
@ override
String get studyButtonBroken = >
' This study button doesn \' t say what to study. ' ;
@ override
String get studyButtonGone = > ' That studyset no longer exists. ' ;
feat: PDF sources: read, take notes and cards from pages, deadlines
PDFs live in a studyset's new Files tab: uploaded from Add material
(a file the studyset holds already asks first), each row with its
pages, cards, notes and size, how far it is read and its reading
deadline; renamed, given a deadline (a page or a chapter by a day),
marked as read, started afresh, deleted with a moment to undo.
The reader (/pdfs/:id) opens where reading left off: on wide windows
a rail of tools (search, fit, zoom, fullscreen), a page counter with
the chapter, and a sidebar; on phones a top bar, a page scrubber and
a sheet. The sidebar holds the PDF's flashcards, by page or chapter,
made from selected text or written there, each tied to its page and
tagged with its chapter; and the page's note, an ordinary note tied
to the page. Screenshots of a region become images that know their
page. Cards and screenshots lead back to their page; reading on past
a chapter with cards suggests studying it. Home shows the deadlines
running and to come, and a timeline of them.
Server: pdf_source rows (content in the payload, last writer wins)
and an append-only pdf_reading log, the furthest page derived from
it so devices never conflict. Files up to 50 MB for PDFs (20 MB for
others; a larger file that isn't a PDF is deleted once stored). A
deleted PDF takes its file's asset row along unless another PDF has
the same file. Card payload 4 and note payload 2 keep the page.
The reader draws with pdfrx (PDFium) behind PdfEngine in
Dependencies; widget tests use a fake, and
integration_test/pdf_engine_test.dart checks the real engine on a
device or the desktop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-03 14:40:08 +02:00
@ override
String get pdfPageField = > ' PDF page ' ;
@ override
String get studysetTabFiles = > ' Files ' ;
@ override
String get pdfUploadMenu = > ' Upload a PDF ' ;
@ override
String get pdfFileType = > ' PDFs ' ;
@ override
String pdfAdded ( String title ) {
return ' Added \" $ title \" . ' ;
}
@ override
String get pdfNotPdf = > ' This file isn \' t a PDF. ' ;
@ override
String pdfTooLarge ( int megabytes ) {
return ' This PDF is larger than $ megabytes MB. ' ;
}
@ override
String get pdfDuplicateTitle = > ' PDF already here ' ;
@ override
String pdfDuplicateMessage ( String title ) {
return ' This file is \" $ title \" , which this studyset holds already. Add it as a copy, or keep the one there? ' ;
}
@ override
String get pdfDuplicateKeep = > ' Keep the one there ' ;
@ override
String get pdfDuplicateAdd = > ' Add a copy ' ;
@ override
String get filesEmpty = > ' No files yet ' ;
@ override
String get filesEmptyHint = >
' Use \" Add material → Upload a PDF \" to add one. ' ;
@ override
String get filesSort = > ' Sort ' ;
@ override
String get filesSortName = > ' Name (A– Z) ' ;
@ override
String get filesSortNewest = > ' Newest first ' ;
@ override
String get filesSortOldest = > ' Oldest first ' ;
@ override
String get filesSortLargest = > ' Largest first ' ;
@ override
String pdfPages ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' $ count pages ' ,
one: ' 1 page ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String pdfPercentRead ( int percent ) {
return ' $ percent % read ' ;
}
@ override
String get pdfDeadlineMet = > ' Reading deadline met ' ;
@ override
String pdfDeadlineUpcoming ( int days , int pages ) {
String _temp0 = intl . Intl . pluralLogic (
days ,
locale: localeName ,
other: ' $ days days ' ,
one: ' 1 day ' ,
) ;
return ' Starts in $ _temp0 — $ pages /day planned ' ;
}
@ override
String pdfDeadlinePassed ( int pages ) {
String _temp0 = intl . Intl . pluralLogic (
pages ,
locale: localeName ,
other: ' $ pages pages ' ,
one: ' 1 page ' ,
) ;
return ' Deadline passed — $ _temp0 left ' ;
}
@ override
String pdfDeadlineToday ( int pages ) {
String _temp0 = intl . Intl . pluralLogic (
pages ,
locale: localeName ,
other: ' $ pages pages ' ,
one: ' 1 page ' ,
) ;
return ' Due today — $ _temp0 left ' ;
}
@ override
String pdfDeadlinePace ( int pages , int target , int days ) {
String _temp0 = intl . Intl . pluralLogic (
days ,
locale: localeName ,
other: ' $ days days ' ,
one: ' 1 day ' ,
) ;
return ' $ pages /day to reach page $ target in $ _temp0 ' ;
}
@ override
String get pdfOptions = > ' Actions ' ;
@ override
String get pdfRename = > ' Rename ' ;
@ override
String get pdfRenameTitle = > ' Rename PDF ' ;
@ override
String get pdfRenameLabel = > ' Title ' ;
@ override
String get pdfSetDeadline = > ' Set reading deadline ' ;
@ override
String get pdfEditDeadline = > ' Edit reading deadline ' ;
@ override
String get pdfMarkFinished = > ' Mark as finished ' ;
@ override
String get pdfResetReading = > ' Reset reading progress ' ;
@ override
String get pdfResetTitle = > ' Reset reading progress? ' ;
@ override
String pdfResetMessage ( String title ) {
return ' How far you \' ve read \" $ title \" is forgotten. The PDF, its flashcards and notes stay. ' ;
}
@ override
String get pdfResetConfirm = > ' Reset ' ;
@ override
String get pdfMoreInfo = > ' More info ' ;
@ override
String get pdfInfoFileName = > ' File name ' ;
@ override
String get pdfInfoSize = > ' Size ' ;
@ override
String get pdfInfoPages = > ' Pages ' ;
@ override
String get pdfInfoUnknown = > ' Known once it is opened ' ;
@ override
String get pdfDelete = > ' Delete ' ;
@ override
String get pdfDeleteTitle = > ' Delete PDF? ' ;
@ override
String pdfDeleteMessage ( String title ) {
return ' \" $ title \" is removed from this studyset, with how far it was read. Flashcards and notes made from it stay. ' ;
}
@ override
String get pdfDeleteConfirm = > ' Delete ' ;
@ override
String get pdfDeleted = > ' PDF deleted ' ;
@ override
String get deadlineSheetSet = > ' Set a reading deadline ' ;
@ override
String get deadlineSheetEdit = > ' Edit reading deadline ' ;
@ override
String get deadlineSheetHelp = >
' Reach a page by a day. Progress follows the furthest page you reach as you read. ' ;
@ override
String deadlineReachPageOf ( int count ) {
return ' Reach page (of $ count ) ' ;
}
@ override
String get deadlineReachPage = > ' Reach page ' ;
@ override
String get deadlineDecrease = > ' Decrease ' ;
@ override
String get deadlineIncrease = > ' Increase ' ;
@ override
String get deadlineChapter = > ' Or finish a chapter ' ;
@ override
String deadlineChapterEntry ( String title , int page ) {
return ' $ title · to p. $ page ' ;
}
@ override
String get deadlineBy = > ' By ' ;
@ override
String get deadlineStart = > ' Start on ' ;
@ override
String get deadlineStartToday = > ' Today ' ;
@ override
String get deadlineStartReset = > ' Start today ' ;
@ override
String get deadlineStartHelp = >
' The daily pace counts from this day on. Until then the PDF waits under \" Upcoming \" . ' ;
@ override
String get deadlineSave = > ' Save deadline ' ;
@ override
String get deadlineRemove = > ' Remove deadline ' ;
@ override
String get readerMissing = >
' This PDF isn \' t on this device yet. It opens once the device that added it has uploaded it and you \' re online. ' ;
@ override
String get readerUnreadable = > ' This file can \' t be read as a PDF. ' ;
@ override
String get readerBack = > ' Back to studyset ' ;
@ override
String get readerSearch = > ' Search PDF ' ;
@ override
String get readerFitWidth = > ' Fit width ' ;
@ override
String get readerFitHeight = > ' Fit height ' ;
@ override
String get readerZoomIn = > ' Zoom in ' ;
@ override
String get readerZoomOut = > ' Zoom out ' ;
@ override
String get readerSetZoom = > ' Set zoom ' ;
@ override
String get readerZoomTitle = > ' Zoom ' ;
@ override
String get readerZoomApply = > ' Apply ' ;
@ override
String get readerFullscreen = > ' Fullscreen ' ;
@ override
String get readerExitFullscreen = > ' Exit fullscreen ' ;
@ override
String get readerPreviousPage = > ' Previous page ' ;
@ override
String get readerNextPage = > ' Next page ' ;
@ override
String readerPageOf ( int count ) {
return ' / $ count ' ;
}
@ override
String get readerFindHint = > ' Find in PDF ' ;
@ override
String readerMatchOf ( int current , int total ) {
return ' $ current of $ total ' ;
}
@ override
String get readerPreviousMatch = > ' Previous match ' ;
@ override
String get readerNextMatch = > ' Next match ' ;
@ override
String get readerCloseSearch = > ' Close search ' ;
@ override
String get readerExpand = > ' Expand ' ;
@ override
String get readerCollapse = > ' Collapse ' ;
@ override
String get readerTabFlashcards = > ' Flashcards ' ;
@ override
String get readerTabNotes = > ' Notes ' ;
@ override
String get readerCardCreated = > ' Flashcard created ' ;
@ override
String get readerCardDeleteTitle = > ' Delete flashcard? ' ;
@ override
String get readerCardDeleteMessage = >
' The flashcard is deleted, with how it was studied. ' ;
@ override
String get readerCardDeleteConfirm = > ' Delete ' ;
@ override
String get readerFrontMatter = > ' Front matter ' ;
@ override
String readerPage ( int page ) {
return ' Page $ page ' ;
}
@ override
String get readerByPage = > ' Page ' ;
@ override
String get readerByChapter = > ' Chapter ' ;
@ override
String get readerCardsEmpty = > ' Select text to make flashcards in no time ' ;
@ override
String get readerCardsEmptyHint = >
' Select the lines that matter and send them to a question or an answer, or press \" Create flashcard \" . ' ;
@ override
String get readerCreateCard = > ' Create flashcard ' ;
@ override
String get readerEditCard = > ' Edit flashcard ' ;
@ override
String get readerBackToCards = > ' Back to flashcards ' ;
@ override
String readerReferencedPage ( int page ) {
return ' Referenced page: $ page ' ;
}
@ override
String readerUseCurrentPage ( int page ) {
return ' Use current ( $ page ) ' ;
}
@ override
String get readerQuestion = > ' Question ' ;
@ override
String get readerAnswer = > ' Answer ' ;
@ override
String get readerKeepOpen = > ' Keep creation mode open ' ;
@ override
String get readerClear = > ' Clear ' ;
@ override
String get readerCreate = > ' Create ' ;
@ override
String get readerUntitledCard = > ' Untitled flashcard ' ;
@ override
String get readerCardOptions = > ' Card options ' ;
@ override
String get readerNoteSaved = > ' Saved ' ;
@ override
String get readerNoteEditing = > ' Editing… ' ;
@ override
String get readerNoteSaving = > ' Saving… ' ;
@ override
String get readerNoteFailed = > ' Save failed ' ;
@ override
String get readerToQuestion = > ' Question ' ;
@ override
String get readerToAnswer = > ' Answer ' ;
@ override
String get readerToNote = > ' Notes ' ;
@ override
String get readerLineBreaksJoined = > ' Line breaks joined (tap to keep) ' ;
@ override
String get readerLineBreaksKept = > ' Line breaks kept (tap to join) ' ;
@ override
String get blockScreenshot = > ' Screenshot of the page ' ;
@ override
String get readerCaptureCancel = > ' Cancel screenshot ' ;
@ override
String get readerCaptureHint = > ' Drag over the part of the page to keep ' ;
@ override
String get readerCaptureFailed = >
' The page couldn \' t be drawn. Try again once it has loaded. ' ;
@ override
String get readerScreenshotSaved = > ' Screenshot saved ' ;
@ override
String get imageOpenPdfPage = > ' Open the PDF at this page ' ;
@ override
String get homeWidgetDeadlines = > ' Reading deadlines ' ;
@ override
String get deadlinesTitle = > ' Reading deadlines ' ;
@ override
String get deadlinesCurrent = > ' Current ' ;
@ override
String get deadlinesUpcoming = > ' Upcoming ' ;
@ override
String deadlinesProgress ( int furthest , int target , String when ) {
return ' p. $ furthest / $ target · $ when ' ;
}
@ override
String get deadlinePassedShort = > ' deadline passed ' ;
@ override
String get deadlineDueTodayShort = > ' due today ' ;
@ override
String deadlineDaysLeft ( int days ) {
String _temp0 = intl . Intl . pluralLogic (
days ,
locale: localeName ,
other: ' $ days days left ' ,
one: ' 1 day left ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String deadlineToPageToday ( int page ) {
return ' to p. $ page today ' ;
}
@ override
String deadlineStartsIn ( int days ) {
String _temp0 = intl . Intl . pluralLogic (
days ,
locale: localeName ,
other: ' $ days days ' ,
one: ' 1 day ' ,
) ;
return ' Starts in $ _temp0 ' ;
}
@ override
String get deadlinesPlaceholder = > ' Appears when you have reading deadlines ' ;
@ override
String get deadlinesOpenTimeline = > ' Open the timeline ' ;
@ override
String get timelineTitle = > ' Reading timeline ' ;
@ override
String get timelineEmpty = > ' No reading deadlines yet ' ;
@ override
String get timelineEmptyHint = >
' Set a reading deadline on a PDF to see it here. ' ;
@ override
String get timelineToday = > ' Today ' ;
@ override
String timelinePages ( int furthest , int target ) {
return ' p. $ furthest / $ target ' ;
}
@ override
String chapterFinished ( String chapter , int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' $ count cards ' ,
one: ' card ' ,
) ;
return ' Finished “ $ chapter ” — study the $ _temp0 you made? ' ;
}
@ override
String get chapterStudyNow = > ' Study now ' ;
@ override
String get chapterDismiss = > ' Dismiss ' ;
@ override
String get deadlineChapterLoading = > ' Loading chapters… ' ;
@ override
String fileSizeKb ( int size ) {
return ' $ size KB ' ;
}
@ override
String fileSizeMb ( String size ) {
return ' $ size MB ' ;
}
@ override
String pdfPageShort ( int page ) {
return ' p. $ page ' ;
}
Import Anki decks with their files and history
Files in content: the person's images and sounds as asset rows synced like
all content, their files kept by hash on each device, uploaded after each
sync, downloaded on demand and checked against their hash
(core/asset/AssetRepository). Rich text names them as asset:<id> URLs, in
the editor's image block and an audio block of ours; any other URL is never
fetched. Sounds play through audioplayers, one at a time. Content holding
only an image or a sound isn't blank, and lists show it as such.
The Anki import ("Import from Anki" under Add material): .apkg and .colpkg
packages in all three formats Anki has written, read on an isolate of their
own. Modern packages compress everything with zstd in frames that don't
record their size, which pure_zstd v1.0.0 refuses; our fork decodes them.
Fields become rich text by a converter of our own (lines, lists, formatting,
images, sounds, cloze deletions hidden on the front). One card per note,
the files first, studysets as chosen (flatten, sub-studysets, or tags by
subdeck), the notes' tags, and, if wanted, the review history, which FSRS
replays: the first template's reviews to the card's schedule, a reversed
note type's second to the reverse one.
Imported cards say where they came from in their first revision (card
schema 3); the dashboard doesn't count them as written. The test fixtures
are exported by Anki itself (tool/anki/make_fixtures.py).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 04:52:02 +02:00
@ override
String get ankiImportMenu = > ' Import from Anki ' ;
@ override
String get ankiImportFileType = > ' Anki decks ' ;
@ override
String get ankiImportTitle = > ' Import Anki deck ' ;
@ override
String get ankiImportOptionsTitle = > ' How should subdecks be imported? ' ;
@ override
String get ankiImportReading = > ' Reading the deck… ' ;
@ override
String ankiImportSummary ( int cards ) {
String _temp0 = intl . Intl . pluralLogic (
cards ,
locale: localeName ,
other: ' $ cards cards. ' ,
one: ' 1 card. ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String ankiImportSummaryDecks ( int cards , int decks ) {
String _temp0 = intl . Intl . pluralLogic (
cards ,
locale: localeName ,
other: ' $ cards cards ' ,
one: ' 1 card ' ,
) ;
return ' $ _temp0 across $ decks subdecks. ' ;
}
@ override
String get ankiImportModeFlatten = > ' Flatten into this studyset ' ;
@ override
String get ankiImportModeFlattenSubtitle = >
' Import every card here and ignore the subdecks. ' ;
@ override
String get ankiImportModeSubStudysets = > ' Recreate as sub-studysets ' ;
@ override
String get ankiImportModeSubStudysetsSubtitle = >
' Mirror the subdeck tree: each subdeck becomes a nested studyset. ' ;
@ override
String get ankiImportModeTag = > ' Tag cards by subdeck ' ;
@ override
String get ankiImportModeTagSubtitle = >
' Keep every card here, tagged with each subdeck it came from. ' ;
@ override
String get ankiImportProgress = > ' Import study progress ' ;
@ override
String get ankiImportProgressSubtitle = >
' Bring Anki \' s review history over: cards continue where they left off. New cards stay new. ' ;
@ override
String ankiImportMedia ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' Include media ( $ count files) ' ,
one: ' Include media (1 file) ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String get ankiImportMediaSubtitle = >
' Keep the deck \' s images and sounds on the cards. ' ;
Limit how much room people's files take
An anonymous person may keep 100 MB of images and sounds, an identified one
500 MB, and one person more by a file_allowance row the operator adds; all
of them together 15 GB, since every file is in the database and in each dump
the server keeps (decided 2026-10-02). Each comes from the environment
(FILE_LIMIT_*_MB), with these defaults, documented in deploy/.env.example.
The server checks when an upload starts, counting unfinished uploads so they
can't fill the disk unnoticed, and again when it finishes, counting what is
kept, so uploads started side by side can't overshoot; a refusal is a
FileLimitExceededException, and an upload refused as it finishes is deleted.
deletePerson deletes the allowance with the person. Migration: the new
table file_allowance, only added.
The app keeps a refused file waiting on the device and stops that round of
uploads; the Anki import warns when a deck's files may not fit in the room
left (asset.usage()).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 05:57:57 +02:00
@ override
String ankiImportMediaMayNotFit ( String size , String left ) {
return ' Its files take $ size MB, more than the $ left MB you have left: those that don \' t fit stay on this device, and your other devices won \' t show them. ' ;
}
Import Anki decks with their files and history
Files in content: the person's images and sounds as asset rows synced like
all content, their files kept by hash on each device, uploaded after each
sync, downloaded on demand and checked against their hash
(core/asset/AssetRepository). Rich text names them as asset:<id> URLs, in
the editor's image block and an audio block of ours; any other URL is never
fetched. Sounds play through audioplayers, one at a time. Content holding
only an image or a sound isn't blank, and lists show it as such.
The Anki import ("Import from Anki" under Add material): .apkg and .colpkg
packages in all three formats Anki has written, read on an isolate of their
own. Modern packages compress everything with zstd in frames that don't
record their size, which pure_zstd v1.0.0 refuses; our fork decodes them.
Fields become rich text by a converter of our own (lines, lists, formatting,
images, sounds, cloze deletions hidden on the front). One card per note,
the files first, studysets as chosen (flatten, sub-studysets, or tags by
subdeck), the notes' tags, and, if wanted, the review history, which FSRS
replays: the first template's reviews to the card's schedule, a reversed
note type's second to the reverse one.
Imported cards say where they came from in their first revision (card
schema 3); the dashboard doesn't count them as written. The test fixtures
are exported by Anki itself (tool/anki/make_fixtures.py).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 04:52:02 +02:00
@ override
String get ankiImportRunning = > ' Importing… ' ;
@ override
String get ankiImportConfirm = > ' Import ' ;
@ override
String get ankiImportNotAPackage = >
' This isn \' t an Anki deck. In Anki, export a deck as an .apkg file and import that. ' ;
@ override
String get ankiImportNewerFormat = >
' This deck comes from a newer Anki than this app knows. Export it again with “Support older Anki versions”. ' ;
@ override
String get ankiImportUnreadable = > ' This deck is damaged and can \' t be read. ' ;
@ override
String get ankiImportBrokeOff = >
' The import broke off. The cards it brought in so far stay. ' ;
@ override
String ankiImported ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' Imported $ count flashcards from Anki. ' ,
one: ' Imported 1 flashcard from Anki. ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String ankiImportedSubStudysets ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' Imported $ count cards into sub-studysets. ' ,
one: ' Imported 1 card into sub-studysets. ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String ankiImportedTagged ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' Imported $ count cards, tagged by subdeck. ' ,
one: ' Imported 1 card, tagged by subdeck. ' ,
) ;
return ' $ _temp0 ' ;
}
@ override
String ankiImportFilesSkipped ( int count ) {
String _temp0 = intl . Intl . pluralLogic (
count ,
locale: localeName ,
other: ' $ count media files couldn \' t be imported. ' ,
one: ' 1 media file couldn \' t be imported. ' ,
) ;
return ' $ _temp0 ' ;
}
feat(app): the UI foundation: theme, tabs and routes, strings
The app gets its look and navigation before more features land on them.
- Theme (core/theme/): hand-written light and dark Material 3 colour schemes around
cobalt and copper; ThemeExtras for the colours Material has no role for (states, the
four FSRS ratings); the 20 colours a person can give content. Tests hold text to WCAG
AA contrast on each palette colour and on the dark theme's primary colour and surface.
- Layout and shared widgets: Material 3's window-size classes (core/layout/);
SectionCard, AppLoadingIndicator, confirmDestructiveAction and FoundById
(core/widget/). Lucide icons.
- Navigation with go_router: four tabs (Home, Library, Explore, Profile), each keeping
its own stack of screens, as a navigation rail with the app's icon from 600 dp and as
a navigation bar below; choosing the open tab again returns to its first screen. A
studyset's screen sits in the library; studying, cards and settings cover the tabs.
- Routes carry ids: FoundById finds the item in the repository's live list, so a
studyset deleted on another device, or an old link, says it no longer exists. A
screen that deletes its own item keeps showing it while it closes, rather than
flashing that message.
- Every person gets an app of their own (PersonApp, lib/src/app/): a
MaterialApp.router with its own GoRouter and PersonScope around the navigator, built
by SessionGate inside the person's keyed subtree. Screens read the person's
repositories wherever they are built, so the static route() helpers and the listener
that closed every pushed screen on a change of person are gone: a change of person
now disposes the whole router and starts again at home.
- The account dialog becomes the Profile tab, with Settings (sign out, delete the
account, About) behind a gear. A waiting GitHub sign-in now keeps waiting while
another tab shows; a change of person still cancels it.
- The new screens' strings come from Flutter's gen-l10n (English for now). The
generated files are committed, left out of analysis and formatting, and written
anew by `flutter pub get`, so CI's check of generated code also catches stale ones.
Widget tests now start the whole app (test/support/pump_app.dart) at the screen's path
and move through it as a person would, by tabs and taps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:18:31 +02:00
}
feat(app): the app speaks British and American English and German
Settings offers English (UK), English (US) and Deutsch after the
device's language, each named in its own language. app_en.arb, the
template, is now British English (colour, centre) and the fallback for
any language the app doesn't speak; app_en_US.arb overrides the five
messages American English spells differently, and app_en_GB.arb is
empty, there so that Material's texts and dates are British too. The
German translation says "du" and keeps one set of terms throughout
(Lernset, Karteikarte, Lernpfad; Anki's own Stapel and Unterstapel in
the Anki import). arb_files_test checks that German has every message
of the template and that every translation keeps its placeholders.
resolveAppLocale picks the first language the device prefers that the
app speaks, in its country's variant where there is one; other English
is British, and a country alone never picks a language (Spanish in the
United States isn't American English), which Flutter's own resolution
would do.
Both MaterialApps take material_ui's GlobalMaterialLocalizations
delegates: gen-l10n's list brings flutter_localizations', which serve
only package:flutter/material.dart, so material_ui's widgets found no
Material texts in German and threw.
The editor's own text blocks show our placeholders while empty (list
item, to-do, quote, heading, and the desktop's / hint): the editor's
own follow a locale of their own, process-wide, in few languages. The
last texts written into widgets (the person's data failing to open,
the cards failing to read, the About dialog) moved into the arb files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-07 02:01:01 +02:00
/// The translations for English, as used in the United Kingdom (`en_GB`).
class AppLocalizationsEnGb extends AppLocalizationsEn {
AppLocalizationsEnGb ( ) : super ( ' en_GB ' ) ;
}
/// The translations for English, as used in the United States (`en_US`).
class AppLocalizationsEnUs extends AppLocalizationsEn {
AppLocalizationsEnUs ( ) : super ( ' en_US ' ) ;
@ override
String get studysetSheetColor = > ' Color ' ;
@ override
String colorPickerLabel ( String color ) {
return ' Color: $ color ' ;
}
@ override
String get studysetSheetInheritColor = > ' Same color as the studyset it is in ' ;
@ override
String get tagSheetColor = > ' Color ' ;
@ override
String get formatAlignCenter = > ' Center ' ;
}