halkyon/packages/halkyon_app/README.md

127 lines
6.8 KiB
Markdown
Raw Normal View History

# 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 and
subdivisions are named by Unicode CLDR's names in English and German, which
come with the package (`AssetCountryNames`, `AssetSubdivisionNames`; the
package's `tool/country_names.py` and `tool/subdivision_names.py` write
them, from one release); tests give names of their own (`CountryNames`,
`SubdivisionNames`).
- **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. `resolveAppLocale`
picks the app's language as every Halkyon app does: the first preferred
language it speaks, never by country alone, British English otherwise.
- **About** (`showAppAbout`): the app's name (its frame's), its version,
icon and legalese where it gives them, where the source of what the
instance runs is (`InstanceSourceCode`, from `AppDependencies.sourceCodeUrl`;
both licences require offering it), the app's own parts (its credits), and
the licence list.
- **The account** (`AccountTiles`): whose account on which instance,
switching to the account on another, signing out (which of an anonymous
account deletes it, so asks first) and deleting it (after asking), for an
app's settings, in the app's words where only it can say them.
- **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 path, within the repository's workspace, as on
`halkyon_ui`:
```yaml
dependencies:
halkyon_app:
path: ../../../packages/halkyon_app
```
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 (`resolveAppLocale` as
its resolution) and launch screen, and the three sentences only it can
say: what the first start's instance keeps of the person's, what a switch
to another account leaves behind, and where GitHub is added later.
5. **The dependencies**: the app's composition root implements
`AppDependencies<P>` (`serverName`, `auth`, `signedOut`, `sourceCodeUrl`,
`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'),
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`).