feat(app): sign in anonymously on first launch
The app's first patterns. Dependencies (lib/src/core/dependencies) is the composition root, built at startup and handed down by DependenciesScope; it replaces the scaffold's global client and restores a stored session before the first frame, so a restart never creates a second anonymous account. AuthGateway with ServerpodAuthGateway is the boundary to the server's authentication, AuthRepository signs in anonymously when nobody is signed in, and SessionBloc (flutter_bloc) keeps a person signed in, also after signing out or a revoked session. SessionGate shows a spinner while signing in, offers to try again when that fails, and builds the app anew for every person. AccountButton explains that progress is saved on this device and asks before signing out of an anonymous account, which can't be restored. AppBlocObserver logs all blocs.
Tests: the repository, the bloc's sequences (a stored session is kept, a failed sign-in is retried, signing out starts anew), and the widgets, including that cancelling the sign-out keeps the account. test/support holds the fake gateway and testDependencies(). AGENTS.md describes the composition root and the test factory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 09:04:02 +02:00
|
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
|
|
|
import 'package:halkyon_learn_flutter/src/core/bloc/app_bloc_observer.dart';
|
|
|
|
|
import 'package:halkyon_learn_flutter/src/core/dependencies/dependencies.dart';
|
|
|
|
|
import 'package:halkyon_learn_flutter/src/core/dependencies/dependencies_scope.dart';
|
|
|
|
|
import 'package:halkyon_learn_flutter/src/feature/auth/widget/session_gate.dart';
|
feat(app): studysets, offline first and synced on triggers
The first synced feature. Every person gets their own local database (<app support>/people/<authUserId>/sync.sqlite): PersonScope opens it inside SessionGate's per-person subtree through Dependencies.openPerson, together with its sync and the person's repositories (PersonDependencies), and closes it when the person changes. SessionGate then also closes pushed screens, which live above the person's subtree; a screen's route() hands its bloc the repository it needs.
SyncStudysetGateway reads and writes the studyset table through the sync session, names the payload column in every update and asks for a sync after every write. StudysetRepository encodes and decodes the payload (studyset_payload.dart: UTF-8 JSON with a schema version; a newer schema or garbage is skipped with a log), re-reads after its own writes and after merges, and blanks the payload before deleting, since the engine keeps deleted rows until the space is deleted (ADR 0002, rule 2). A failed read reaches the list as an error, and the next change reads again. SyncService syncs on triggers: at start, 2 s after the last local write, on resume and every 5 minutes, one sync at a time, and lets a running sync finish before the database closes.
Screens: the list, now the home screen in place of the editor playground, creates studysets through a dialog; a studyset's screen edits its title and description, saves on request and asks before deleting.
Tests: the payload codec, the repository against a fake gateway (including a failed read and rows from a newer app), the sync triggers under fakeAsync, both blocs' sequences, and the screens, including that cancelling the delete keeps the studyset. The session tests now settle until the person's data is open. AGENTS.md describes the per-person graph, synced data and the envelope as it now is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 10:11:24 +02:00
|
|
|
import 'package:halkyon_learn_flutter/src/feature/studyset/widget/studyset_list_screen.dart';
|
2026-09-29 23:11:00 +02:00
|
|
|
import 'package:material_ui/material_ui.dart';
|
2026-09-16 19:24:59 +02:00
|
|
|
|
feat(app): sign in anonymously on first launch
The app's first patterns. Dependencies (lib/src/core/dependencies) is the composition root, built at startup and handed down by DependenciesScope; it replaces the scaffold's global client and restores a stored session before the first frame, so a restart never creates a second anonymous account. AuthGateway with ServerpodAuthGateway is the boundary to the server's authentication, AuthRepository signs in anonymously when nobody is signed in, and SessionBloc (flutter_bloc) keeps a person signed in, also after signing out or a revoked session. SessionGate shows a spinner while signing in, offers to try again when that fails, and builds the app anew for every person. AccountButton explains that progress is saved on this device and asks before signing out of an anonymous account, which can't be restored. AppBlocObserver logs all blocs.
Tests: the repository, the bloc's sequences (a stored session is kept, a failed sign-in is retried, signing out starts anew), and the widgets, including that cancelling the sign-out keeps the account. test/support holds the fake gateway and testDependencies(). AGENTS.md describes the composition root and the test factory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 09:04:02 +02:00
|
|
|
Future<void> main() async {
|
2026-09-16 19:24:59 +02:00
|
|
|
WidgetsFlutterBinding.ensureInitialized();
|
feat(app): sign in anonymously on first launch
The app's first patterns. Dependencies (lib/src/core/dependencies) is the composition root, built at startup and handed down by DependenciesScope; it replaces the scaffold's global client and restores a stored session before the first frame, so a restart never creates a second anonymous account. AuthGateway with ServerpodAuthGateway is the boundary to the server's authentication, AuthRepository signs in anonymously when nobody is signed in, and SessionBloc (flutter_bloc) keeps a person signed in, also after signing out or a revoked session. SessionGate shows a spinner while signing in, offers to try again when that fails, and builds the app anew for every person. AccountButton explains that progress is saved on this device and asks before signing out of an anonymous account, which can't be restored. AppBlocObserver logs all blocs.
Tests: the repository, the bloc's sequences (a stored session is kept, a failed sign-in is retried, signing out starts anew), and the widgets, including that cancelling the sign-out keeps the account. test/support holds the fake gateway and testDependencies(). AGENTS.md describes the composition root and the test factory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 09:04:02 +02:00
|
|
|
Bloc.observer = const AppBlocObserver();
|
|
|
|
|
final dependencies = await Dependencies.create();
|
|
|
|
|
runApp(
|
|
|
|
|
DependenciesScope(
|
|
|
|
|
dependencies: dependencies,
|
|
|
|
|
child: const HalkyonLearnApp(),
|
|
|
|
|
),
|
|
|
|
|
);
|
2026-09-16 19:24:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Builds a theme for the given [brightness].
|
|
|
|
|
ThemeData _buildTheme(Brightness brightness) {
|
|
|
|
|
return ThemeData(
|
|
|
|
|
colorScheme: ColorScheme.fromSeed(
|
|
|
|
|
seedColor: Colors.blue,
|
|
|
|
|
brightness: brightness,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
chore(lint): LeanCode's strict rules and a pre-push check
The workspace root's analysis_options.yaml includes leancode_lint 27 and enables its analyzer plugin (rules for blocs, Equatable and Flutter layout); the server and the app include that file, while the generated client keeps its own options. Plugins can only be enabled at the root of a pub workspace and only reach packages whose options include the root file, which was verified before settling on this layout. Most rules are warnings, casts, inference and raw types are strict, and the check treats infos as fatal too. lints and flutter_lints are no longer needed.
tool/check.sh checks formatting, runs dart analyze --fatal-infos and the server tests; .git/hooks/pre-push links to it, so every push has to pass it. AGENTS.md describes the checklist and how to install the hook in a fresh clone.
Fixes for the new rules: package imports, run() returns Future<void>, the server setup as one cascade, a primary constructor for the app widget, a const and a redundant argument. The workspace root keeps its conventional name `_` with a documented ignore.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-29 23:06:29 +02:00
|
|
|
class const HalkyonLearnApp({super.key}) extends StatelessWidget {
|
2026-09-16 19:24:59 +02:00
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
return MaterialApp(
|
2026-09-24 14:16:12 +02:00
|
|
|
title: 'Halkyon Learn',
|
2026-09-16 19:24:59 +02:00
|
|
|
theme: _buildTheme(Brightness.light),
|
|
|
|
|
darkTheme: _buildTheme(Brightness.dark),
|
2026-09-29 23:11:00 +02:00
|
|
|
// Packages that still import package:flutter/material.dart (Serverpod's sign-in
|
|
|
|
|
// widgets, the rich-text editor) see this app's theme and localizations only
|
|
|
|
|
// through the bridge, since material_ui is a separate copy of that library. The
|
|
|
|
|
// bridge is deprecated on purpose, as a reminder: remove it once they have moved.
|
|
|
|
|
// ignore: deprecated_member_use
|
|
|
|
|
builder: (context, child) => MaterialUiCompatibilityBridge(child: child!),
|
feat(app): studysets, offline first and synced on triggers
The first synced feature. Every person gets their own local database (<app support>/people/<authUserId>/sync.sqlite): PersonScope opens it inside SessionGate's per-person subtree through Dependencies.openPerson, together with its sync and the person's repositories (PersonDependencies), and closes it when the person changes. SessionGate then also closes pushed screens, which live above the person's subtree; a screen's route() hands its bloc the repository it needs.
SyncStudysetGateway reads and writes the studyset table through the sync session, names the payload column in every update and asks for a sync after every write. StudysetRepository encodes and decodes the payload (studyset_payload.dart: UTF-8 JSON with a schema version; a newer schema or garbage is skipped with a log), re-reads after its own writes and after merges, and blanks the payload before deleting, since the engine keeps deleted rows until the space is deleted (ADR 0002, rule 2). A failed read reaches the list as an error, and the next change reads again. SyncService syncs on triggers: at start, 2 s after the last local write, on resume and every 5 minutes, one sync at a time, and lets a running sync finish before the database closes.
Screens: the list, now the home screen in place of the editor playground, creates studysets through a dialog; a studyset's screen edits its title and description, saves on request and asks before deleting.
Tests: the payload codec, the repository against a fake gateway (including a failed read and rows from a newer app), the sync triggers under fakeAsync, both blocs' sequences, and the screens, including that cancelling the delete keeps the studyset. The session tests now settle until the person's data is open. AGENTS.md describes the per-person graph, synced data and the envelope as it now is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 10:11:24 +02:00
|
|
|
home: const SessionGate(child: StudysetListScreen()),
|
2026-09-16 19:24:59 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|