The developer decided on 8 Nov 2026, after trying the variants on the phone: the wave in the colour of the screen in view, without Material's ripple, the indicator kept and there at once, a 1.2 s wave, top corners of 20 dp, the bar's own neutral surface. Learn's bar stays cobalt, the colour its tabs wash in, rather than its icon's purple. So the bar has one look: FlushNavigationBar.waveDuration and cornerRadius are constants, and the debug section goes from the four Settings screens with what it switched (NavigationBarDebugSection, debugNavigationBarStyle, effectiveNavigationBarStyle, the style record and the variants A, C and F, the speeds, the corners and the tinted surface). Their tests go too; those stating the look kept stay. Learn's sign-out helper no longer has to scroll past the section. ADR 0030's amendment of D60, the halkyon_ui README and Learn's AGENTS.md record the decision and the alternatives set aside. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
99 lines
4.5 KiB
Markdown
99 lines
4.5 KiB
Markdown
# halkyon_ui
|
|
|
|
Halkyon's look, shared by its apps (Learn, Todo, Train): one theme around the
|
|
kingfisher's cobalt and copper, the bundled Noto Sans, Material 3's window
|
|
sizes, and the widgets every app's screens are built of, with their texts in
|
|
British English, American English and German.
|
|
|
|
Written as the apps are: Dart 3.13's primary constructors, Material from
|
|
`package:material_ui` (never `package:flutter/material.dart`), Lucide icons,
|
|
LeanCode's lints.
|
|
|
|
## What it holds
|
|
|
|
- **Theme** (`lib/src/theme/`): `appTheme(brightness, extensions: …)`, Material
|
|
3 with hand-written light and dark schemes (primary cobalt, tertiary copper,
|
|
cobalt-tinted neutrals), Material 3 Expressive's 64 dp navigation bar, flat
|
|
app bars, floating snack bars; `Brand`; `ThemeExtras`, the status colours
|
|
Material has no role for (`Theme.of(context).extras`); `PaletteColor`, the
|
|
20 colours a person can give content, each with a text colour held to WCAG
|
|
AA by a test; `FittedSnackBars`.
|
|
- **Fonts**: Noto Sans for Latin, Greek and Cyrillic, every weight in one
|
|
variable file, as `appFontFamily` (`packages/halkyon_ui/Noto Sans`). Chinese,
|
|
Japanese and Korean fall back to `Noto Sans JP`, `SC`, `TC` and `KR`, which
|
|
an app bundles itself if it needs them (`appFontFallbackFor` puts a
|
|
language's own first).
|
|
- **Layout** (`lib/src/layout/`): `WindowSize` (`context.windowSize`) and
|
|
`readableContentMaxWidth`.
|
|
- **Widgets** (`lib/src/widget/`): `showAdaptiveSheet` with `SheetScaffold`;
|
|
`TopGradient`, `ScreenTint`, `ScreenColor`, `TintFrame`, `FramedScreen`
|
|
and `TabVisibility`; `FlushNavigationBar`, and `NavigationFrame`, every
|
|
app's frame around its tabs (the rail with the app's icon, the bar flush
|
|
with the foot with a fade above it, its rounded top corners and the
|
|
wave a tap sends over it in the colour of the screen in view, the tab
|
|
chosen in that colour made legible (`readableTone`), without Material's
|
|
ripple or the indicator growing, on the bar's neutral surface (decided
|
|
8 Nov 2026, ADR 0030's amendment of D60), a tick on every choice, a status such as a recording under way above
|
|
the bar), whichever router drives it;
|
|
`AppLoadingIndicator`, `ButtonLoadingIndicator`, `AppRefreshIndicator`;
|
|
`SectionCard`, `SliverCentered`, `EmptyListMessage`, `CountBadge`,
|
|
`TitleWithKicker`, `CollapsingRow`, `ScrollHidingFab`, `Shimmer` and `SkeletonBox`, `liftWhileDragged`,
|
|
`ReorderToggle`, `ReorderGrip`; `ChevronField`, `requiredDecoration`,
|
|
`FieldInfoButton`, `CountStepper`, `PaletteColorPicker`, `TextLimits` with
|
|
`nearLimitCounter`; `confirmDestructiveAction`, `showUndoSnackBar`,
|
|
`showMoreInfoSheet`; `FoundById`; `KeyboardBackOnResume`; `BrandWordmark`
|
|
and `LaunchScreen`, which take the app's word and icon.
|
|
|
|
What belongs to one app stays in it: its own colours (an extension passed to
|
|
`appTheme`), routes, repositories, editors and texts.
|
|
|
|
## Using it in an app
|
|
|
|
Depend on it by path, within the repository's workspace (an app's packages sit in
|
|
`apps/<app>/`), so a change to the package and to the apps using it is one commit:
|
|
|
|
```yaml
|
|
dependencies:
|
|
halkyon_ui:
|
|
path: ../../../packages/halkyon_ui
|
|
```
|
|
|
|
Then set the app up as `example/lib/main.dart` does:
|
|
|
|
```dart
|
|
MaterialApp(
|
|
theme: appTheme(Brightness.light, extensions: [MyColors.light]),
|
|
darkTheme: appTheme(Brightness.dark, extensions: [MyColors.dark]),
|
|
localizationsDelegates: const [
|
|
AppLocalizations.delegate,
|
|
HalkyonUiLocalizations.delegate,
|
|
// material_ui's: flutter_localizations' serve only the SDK's Material.
|
|
...GlobalMaterialLocalizations.delegates,
|
|
],
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
builder: (context, child) => FittedSnackBars(child: child!),
|
|
…
|
|
);
|
|
```
|
|
|
|
and call `registerFontLicenses()` at start. An app bundling the CJK fonts
|
|
declares them in its own `pubspec.yaml` under exactly the names
|
|
`appFontFallback` lists (no `packages/` prefix) and registers their licence
|
|
itself.
|
|
|
|
## Texts
|
|
|
|
`lib/l10n/halkyon_ui_en.arb` is the template (British English, also the
|
|
fallback); `halkyon_ui_de.arb` German, informal "du"; `halkyon_ui_en_US.arb`
|
|
only what American English spells differently; `halkyon_ui_en_GB.arb` empty. A
|
|
new text goes into the template and German together (`test/l10n/` fails
|
|
otherwise). `flutter pub get` writes `lib/l10n/halkyon_ui_localizations*.dart`,
|
|
which are committed and never edited or
|
|
formatted by hand.
|
|
|
|
## Checks
|
|
|
|
From the workspace root: `dart analyze --fatal-infos`; then `flutter test` in
|
|
`packages/halkyon_ui` and in `packages/halkyon_ui/example`, whose tests check
|
|
what only an app sees (the font under the name an app finds it by, its
|
|
licence).
|