An operator's DATA_LOCATION and BACKUP_LOCATION may name a subdivision
(ISO 3166-2, US-CA, DE-BY) besides the EU or a country. The app adds
"(EU)" to a member country and the country to a subdivision ("Germany
(EU)", "California, United States", "Bavaria, Germany (EU)"), in
English and German, and shows the most specific flag there is: a
subdivision's own where the flag package has one, else its country's.
Subdivision names come from Unicode CLDR 48.2 (tool/subdivision_names.py),
bundled with their licence, which the app registers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
118 lines
6.1 KiB
Markdown
118 lines
6.1 KiB
Markdown
# 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`): `EU`, a country or a subdivision
|
|
(ISO 3166-2, `US-CA`) as its operator says, "Germany (EU)" for a member of
|
|
the European Union (`europeanUnionMembers`) and "California, United States"
|
|
for a subdivision, with the most specific flag there is (a few subdivisions
|
|
have their own, such as Scotland; else the country's). Countries are named
|
|
by the app's `CountryNames`, subdivisions by Unicode CLDR's names in English
|
|
and German, which come with the package (`AssetSubdivisionNames`; the
|
|
workspace's `tool/subdivision_names.py` writes them).
|
|
- **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 `registerHalkyonAppLicenses()` once at startup (the
|
|
flags' and Unicode CLDR's notices), 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`). The subdivisions' names are Unicode
|
|
CLDR's (release 48.2), under the Unicode License v3
|
|
(`assets/subdivisions/LICENSE-CLDR.txt`, `assets/subdivisions/ATTRIBUTION.md`).
|