halkyon_app: instances, sign-in and sync plumbing for Halkyon's apps
What every Halkyon app's shell needs, taken from Halkyon Learn's and set on halkyon_base's client: the instances a device keeps an account on, the first start's tiles and where an instance keeps people's data, the account switcher; signing in anonymously, with GitHub or an email code, switching person and the start screen; the gates that build an app's dependencies per instance and per person; and the sync service with its status, Settings card and banner. Texts of its own in English and German take the app's name; what only the app can say, it passes in. Fakes for apps' tests are in testing.dart. An instance's basics now name the app it serves (`app`, set by the host's configuration), and an app accepts only an instance of its own. SerialQueue and CoalescingRunner move into halkyon_core. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
af1de799f4
commit
e23cc77f7c
1 changed files with 10761 additions and 10 deletions
109
packages/halkyon_app/README.md
Normal file
109
packages/halkyon_app/README.md
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
# halkyon_app
|
||||
|
||||
What every Halkyon app (Learn, Todo, Train) is built around, on an instance's
|
||||
`halkyon_base` module: the instances a device keeps an account on, signing in,
|
||||
each person's data opened and closed, and syncing it. Taken from Halkyon Learn,
|
||||
which it was written for, so it behaves as Learn's own did; the texts are in
|
||||
British English, American English and German, and where they name the app,
|
||||
they name yours.
|
||||
|
||||
Written as the apps are: Dart 3.13's primary constructors, Material from
|
||||
`package:material_ui`, Lucide icons, blocs (`flutter_bloc`), LeanCode's lints.
|
||||
|
||||
## What it holds
|
||||
|
||||
- **Instances** (`lib/src/instance/`): `Instance` (an address as people type
|
||||
it), `InstanceChoice` (those the device keeps an account on, one in use;
|
||||
`FileInstanceStore`), `InstanceStorage` (each instance's people's folders and
|
||||
session key), `InstanceChecker` (does an address answer, where does it keep
|
||||
people's data; `ServerpodInstanceChecker` asks its `instance.basics()`),
|
||||
`InstanceScope`. The screens: `FirstStartScreen` (the build's instance and
|
||||
another, as tiles, `InstanceTile`), `InstanceStep` (an address, checked),
|
||||
`showAccountSwitcher` and `AddAccountScreen`, and where an instance keeps
|
||||
people's data, as a flag and words (`InstanceLocationBuilder`,
|
||||
`InstanceFlag`, `instanceLocationWords`, countries named by the app's
|
||||
`CountryNames`).
|
||||
- **Gates** (`lib/src/app/`): `InstanceGate` opens the app's dependencies on
|
||||
the instance in use (`AppDependencies`, handed down by `AppScope`), anew on
|
||||
a switch, the previous ones closed first; `SessionGate` keeps a person
|
||||
signed in, signing in anonymously at a first start and asking how to start
|
||||
once signed out (`StartScreen`); `PersonScope` opens each person's own
|
||||
dependencies and closes them when the person goes.
|
||||
- **Signing in** (`lib/src/auth/`): `AuthRepository` on an `AuthGateway`;
|
||||
`ServerpodAuthGateway` signs in anonymously, with GitHub (the system
|
||||
browser and a loopback address on Linux and Windows, `LoopbackWebAuth`) and
|
||||
with a code by mail, through the instance's `halkyon_base`; `SessionBloc`,
|
||||
`GitHubSignInBloc`, `EmailSignInBloc` (with `showEmailSignInSheet`) and
|
||||
`SignInWaysCubit`; `SignedOutMark`, `SignInAddresses`, `PersonFolders`.
|
||||
- **Sync** (`lib/src/sync/`): `SyncService` (once at start, shortly after
|
||||
local changes, on resume and every few minutes, one sync at a time, counting
|
||||
the changes not on the server yet, `PendingChangesStore`), `AppCompatibility`
|
||||
and `compatibleSync` (the instance's minimum app build, the engine's table
|
||||
check), `SyncStatusBloc`, `syncAndWait`, and the widgets: `SyncStatusCard`,
|
||||
`AppMismatchBanner`, `SyncTroubleBadge`.
|
||||
- **A client of `halkyon_base` alone** (`BaseClient`): to ask an instance
|
||||
about itself, and to sign in as another person than the app's client holds.
|
||||
- **Testing** (`package:halkyon_app/testing.dart`): fakes of every boundary
|
||||
(`FakeAuthGateway`, `FakeInstanceChecker`, the stores in memory), a frame
|
||||
(`testFrame`), `testApp`, and `TestAppDependencies`.
|
||||
|
||||
## Using it in an app
|
||||
|
||||
1. **The server** includes `halkyon_base` with the nickname `base` (its
|
||||
README), so the app's generated client has `client.modules.base`.
|
||||
2. **Depend on it** by git, pinned to a signed commit of halkyon_shared's
|
||||
main, as `halkyon_ui` (whose README shows the `pubspec_overrides.yaml` for
|
||||
working on both at once):
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
halkyon_app:
|
||||
git:
|
||||
url: https://code.halkyon.app/project-halkyon/halkyon_shared.git
|
||||
path: packages/halkyon_app
|
||||
ref: <signed commit>
|
||||
```
|
||||
|
||||
3. **Texts**: add `HalkyonAppLocalizations.delegate` to every `MaterialApp`'s
|
||||
delegates, beside `HalkyonUiLocalizations.delegate` and material_ui's
|
||||
`GlobalMaterialLocalizations.delegates`.
|
||||
4. **The frame** (`AppFrame`): the app's name and wordmark word, its icon, an
|
||||
example address, its theme, delegates, languages and launch screen, and the
|
||||
two sentences only it can say: what the first start's instance keeps of
|
||||
the person's, and what a switch to another account leaves behind.
|
||||
5. **The dependencies**: the app's composition root implements
|
||||
`AppDependencies<P>` (`serverName`, `auth`, `signedOut`, `openPerson`,
|
||||
`dispose`), its person's dependencies `Disposable`. Its `auth` is an
|
||||
`AuthRepository` on `ServerpodAuthGateway(client, base:
|
||||
client.modules.base, callbackScheme: …, appName: …)`; the scheme is the
|
||||
app's own (`app.halkyon.learn`), registered with each platform and in the
|
||||
instance's GitHub OAuth app, whose loopback address on Linux and Windows is
|
||||
`http://127.0.0.1/github-callback` (port 43823 unless given).
|
||||
6. **The tree**:
|
||||
|
||||
```dart
|
||||
InstanceGate(
|
||||
frame: frame,
|
||||
choice: choice,
|
||||
checker: ServerpodInstanceChecker(app: 'todo'),
|
||||
countryNames: …,
|
||||
readLook: …, // the device's theme mode and language
|
||||
open: Dependencies.create,
|
||||
app: SessionGate(
|
||||
look: (context) => …,
|
||||
app: (person) => PersonApp(person: person), // with PersonScope inside
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
A person's widgets find their dependencies with
|
||||
`PersonScope.of<PersonDependencies>(context)` and the app's with
|
||||
`AppScope.of<Dependencies>(context)`.
|
||||
7. **Licences**: call `registerFlagIconsLicense()` once at startup, beside
|
||||
halkyon_ui's `registerFontLicenses()`.
|
||||
|
||||
## Licence
|
||||
|
||||
GNU Affero General Public License 3.0 or later (`LICENSE`). The flags are
|
||||
lipis' flag-icons, drawn by the `flag` package, under the MIT licence
|
||||
(`assets/flags/LICENSE-flag-icons.txt`).
|
||||
Loading…
Reference in a new issue