halkyon/halkyon_learn_flutter/lib/src/app/app_router.dart

193 lines
7.4 KiB
Dart
Raw Normal View History

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
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:halkyon_learn_flutter/src/core/dependencies/person_scope.dart';
import 'package:halkyon_learn_flutter/src/core/router/app_shell.dart';
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
import 'package:halkyon_learn_flutter/src/core/router/tab_visibility.dart';
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
import 'package:halkyon_learn_flutter/src/core/widget/found_by_id.dart';
import 'package:halkyon_learn_flutter/src/feature/auth/widget/profile_screen.dart';
import 'package:halkyon_learn_flutter/src/feature/card/bloc/card_composer_bloc.dart';
import 'package:halkyon_learn_flutter/src/feature/card/widget/composer/card_composer_screen.dart';
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
import 'package:halkyon_learn_flutter/src/feature/explore/widget/explore_screen.dart';
import 'package:halkyon_learn_flutter/src/feature/home/widget/home_screen.dart';
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
import 'package:halkyon_learn_flutter/src/feature/note/bloc/note_editor_bloc.dart';
import 'package:halkyon_learn_flutter/src/feature/note/widget/note_editor_screen.dart';
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
import 'package:halkyon_learn_flutter/src/feature/settings/widget/settings_screen.dart';
import 'package:halkyon_learn_flutter/src/feature/study/bloc/study_session_bloc.dart';
import 'package:halkyon_learn_flutter/src/feature/study/widget/study_screen.dart';
import 'package:halkyon_learn_flutter/src/feature/studyset/model/studyset.dart';
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
import 'package:halkyon_learn_flutter/src/feature/studyset/widget/library_screen.dart';
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
import 'package:halkyon_learn_flutter/src/feature/studyset/widget/studyset_screen.dart';
import 'package:material_ui/material_ui.dart';
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
/// The router of one person's app: the four tabs in a shell, each with its own
/// stack of screens, and the focused surfaces over them. Every route is built
/// inside the person's scope, so screens read their repositories from
/// [PersonScope]. The app opens at [initialLocation].
GoRouter buildRouter({String initialLocation = RoutePaths.home}) => GoRouter(
initialLocation: initialLocation,
routes: [
StatefulShellRoute(
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
builder: (context, state, navigationShell) =>
AppShell(navigationShell: navigationShell),
navigatorContainerBuilder: tabsContainer,
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
// In the order of the shell's destinations.
branches: [
StatefulShellBranch(
routes: [
GoRoute(
path: RoutePaths.home,
builder: (context, state) => const HomeScreen(),
),
],
),
StatefulShellBranch(
routes: [
GoRoute(
path: RoutePaths.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
builder: (context, state) => const LibraryScreen(),
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
// Nested, so a studyset's screen sits above the library in the
// tab's stack.
routes: [
GoRoute(
path: 'studysets/:id',
// Keyed by the id: several studysets' screens can be on
// the stack at once, and must not share one page.
pageBuilder: (context, state) {
final id = state.pathParameters['id']!;
return MaterialPage<void>(
key: ValueKey('studyset-$id'),
child: _studysetScreen(context, id),
);
},
),
],
),
],
),
StatefulShellBranch(
routes: [
GoRoute(
path: RoutePaths.explore,
builder: (context, state) => const ExploreScreen(),
),
],
),
StatefulShellBranch(
routes: [
GoRoute(
path: RoutePaths.profile,
builder: (context, state) => const ProfileScreen(),
),
],
),
],
),
// Over the tabs: they need the whole window.
GoRoute(
path: '/study/:studysetId',
builder: (context, state) {
final person = PersonScope.of(context);
final studysetId = state.pathParameters['studysetId']!;
return FoundById<Studyset>(
id: studysetId,
items: person.studysets.watchAll,
idOf: (studyset) => studyset.id,
builder: (studyset, _) => BlocProvider(
create: (context) => StudySessionBloc(
person.studysets,
person.cards,
person.reviews,
person.tags,
person.settings,
studysetId,
)..add(const StudySessionEventStarted()),
child: StudyScreen(
studysetId: studysetId,
title: studyset.content.title,
),
),
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
);
},
),
// The composer: writing a studyset's cards, opened on a new card or on
// one of them. Before the card route: its `new` would otherwise read as
// a card's id.
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
GoRoute(
path: '/studysets/:studysetId/cards/new',
builder: (context, state) =>
_composer(context, state.pathParameters['studysetId']!),
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
),
GoRoute(
path: '/studysets/:studysetId/cards/:cardId',
builder: (context, state) => _composer(
context,
state.pathParameters['studysetId']!,
cardId: state.pathParameters['cardId'],
),
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): 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
// A note, new or stored; the new one's route first, as for cards.
GoRoute(
path: '/studysets/:studysetId/notes/new',
builder: (context, state) =>
_noteEditor(context, state.pathParameters['studysetId']!),
),
GoRoute(
path: '/studysets/:studysetId/notes/:noteId',
builder: (context, state) => _noteEditor(
context,
state.pathParameters['studysetId']!,
noteId: state.pathParameters['noteId'],
),
),
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
GoRoute(
path: RoutePaths.settings,
builder: (context, state) => const SettingsScreen(),
),
],
);
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
Widget _studysetScreen(BuildContext context, String id) => FoundById<Studyset>(
id: id,
items: PersonScope.of(context).studysets.watchAll,
idOf: (studyset) => studyset.id,
builder: (studyset, all) => StudysetScreen(studyset: studyset, all: all),
);
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
/// The note [noteId] of the studyset [studysetId], or a new one without; a
/// studyset gone (deleted, or an old link) says so. Leaving the note closes
/// its bloc, which saves what is unsaved.
Widget _noteEditor(BuildContext context, String studysetId, {String? noteId}) {
final person = PersonScope.of(context);
return FoundById<Studyset>(
id: studysetId,
items: person.studysets.watchAll,
idOf: (studyset) => studyset.id,
builder: (studyset, _) => BlocProvider(
create: (context) =>
NoteEditorBloc(person.notes, person.tags, studysetId, noteId: noteId)
..add(const NoteEditorEventStarted()),
child: NoteEditorScreen(studysetTitle: studyset.content.title),
),
);
}
/// The composer of the studyset [studysetId]'s cards, opened on [cardId] if
/// given; a studyset gone (deleted, or an old link) says so.
Widget _composer(BuildContext context, String studysetId, {String? cardId}) {
final person = PersonScope.of(context);
return FoundById<Studyset>(
id: studysetId,
items: person.studysets.watchAll,
idOf: (studyset) => studyset.id,
builder: (studyset, _) => BlocProvider(
create: (context) =>
CardComposerBloc(person.cards, person.tags, studysetId)
..add(const CardComposerEventStarted()),
child: CardComposerScreen(
studysetTitle: studyset.content.title,
initialCardId: cardId,
),
),
);
}