2026-10-18 21:46:55 +02:00
|
|
|
# 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`
|
feat(learn,todo,train,meditate,ui): the navigation bar flush with the foot, a fade above it
Below medium windows the frame's bar is Material 3 Expressive's again,
flush with the window's foot (decided 7 Nov 2026 by the developer): the
theme's NavigationBar across the whole width, 64 dp in its own colour,
running on beneath the system's bar as Material's does. Right above it a
fade 32 dp high, from the background of the screen in view (the
ScreenTint's, the theme's surface before a screen claims one) made
see-through to that background, so what scrolls on beneath the bar fades
out rather than being cut off; taps pass through it. The rail of wider
windows stays as it was.
halkyon_ui's FloatingNavigationBar becomes FlushNavigationBar, its side
margin, lift and rounded card gone. Its coverOf is now the whole room
content leaves at its end, the system's bar included (the body's bottom
padding: bar, fade and system's bar inside the frame, the system's bar
alone outside it), and floating buttons rise by buttonLiftOf, the same
without the system's bar, which a Scaffold keeps them clear of itself.
Lists, Todo's hour grid and the Plan's sheet keep using coverOf; the
Profile's list no longer adds the system's bar a second time.
Whats-New: The navigation bar sits at the bottom edge again, with a soft fade above it.
Whats-New-De: Die Navigationsleiste sitzt wieder am unteren Rand, mit einem sanften Übergang darüber.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 00:04:18 +01:00
|
|
|
and `TabVisibility`; `FlushNavigationBar`, and `NavigationFrame`, every
|
feat: the rail opens into a sidebar with labels from expanded windows on
Decided by the developer on 9 and 10 Nov 2026: phones keep the bar,
medium windows (600 to 840 dp) the rail alone, and from 840 dp a toggle
at the rail's foot opens it into a sidebar 256 dp wide, as Proton
Drive's: the app named beside its icon, each tab its label beside its
icon, the icons where the rail had them, so nothing moves under the
pointer. Its labels come in one after another, top to bottom, all at once
where animations are off. Open or closed is kept per device, in
halkyon_app's Look (look.json, `sidebar`), which Learn keeps in its
DeviceSettings (`sidebarOpen`), as it does the appearance and language.
NavigationFrame takes sidebarOpen and onSidebarChanged (no toggle without
it), and sidebarSections, what the app lists below the tabs, which Todo's
favourites and projects will be. The closed rail keeps Flutter's widget,
its toggle pinned at the foot (trailingAtBottom); the open one is the
frame's own, as the staggered labels need. Learn, Todo, Train and
Meditate keep the choice.
Whats-New: In wide windows, the button at the foot of the side rail opens it into a sidebar with names, and it stays open.
Whats-New-De: In breiten Fenstern öffnet der Knopf unten an der Seitenleiste sie mit Namen, und sie bleibt offen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 16:44:08 +01:00
|
|
|
app's frame around its tabs (the rail with the app's icon, opening from
|
|
|
|
|
expanded windows on into a sidebar with labels and what the app lists
|
|
|
|
|
there (`sidebarOpen`, `onSidebarChanged`, `sidebarSections`), the bar flush
|
feat(ui,learn,todo,train,meditate): a wave in the app's colour over the navigation bar
The developer's choice of 7 Nov 2026 (the mockup's variant E): a tap on
another destination sends a radial glow in the app's colour over the bar
from that destination's centre, solid to 35 % of its radius and gone at
70 %, growing to cover the bar (cubic(.2, .7, .2, 1)) while it fades from
half its strength in dark mode and a third in light, over 1.2 s, behind
the destinations and clipped to the bar. None for the destination already
chosen, none with animations off, and none for a tab changed without a
tap (a link, back): the wave answers the finger. The destination chosen
takes the app's colour, its indicator tinted 22 % with it, without
Material's ripple or the indicator growing; the bar's top corners are
rounded, 20 dp, the screen showing through them.
Each app gives the frame its colour (NavigationFrame's accent, from
PaletteColor): Learn's purple, its icon's last colour, Todo's strawberry,
Train's carrot and Meditate's fern, their screens' wash.
For trying it on a phone, a debug build's Settings has a section
Navigation bar (debug), halkyon_ui's NavigationBarDebugSection, switching
live, in memory alone: the variant (A Material only, C the wave with
Material's fill, E without it, F without the indicator too), the wave's
time (0.7 to 2 s) and the corners (0 to 28 dp). Release builds never read
it and show shippingNavigationBarStyle, the debug section's start: to
remove it, delete the section from the four Settings screens and
debugNavigationBarStyle, and let effectiveNavigationBarStyle return the
shipping style.
Whats-New: Tapping a tab sends a soft wave in the app's colour across the navigation bar, now with rounded corners.
Whats-New-De: Ein Tipp auf einen Tab schickt eine sanfte Welle in der Farbe der App über die Navigationsleiste, jetzt mit abgerundeten Ecken.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 00:29:14 +01:00
|
|
|
with the foot with a fade above it, its rounded top corners and the
|
feat(ui,learn): the navigation bar in the colour of the screen in view
The developer's review of 8 Nov 2026: the bar follows the screen in view
rather than one colour per app, so a Todo project of its own colour gives
the bar that colour while it shows. The wave, the chosen destination's
icon and label and its indicator take ScreenTint.color, the colour the
screen in view claims through its TopGradient; the frame's accent, the
app's colour, stays only for the moment before any screen claims one,
since ScreenTint itself starts at cobalt, which would be wrong for Todo,
Train and Meditate. Learn's tabs claim cobalt, their wash, so its bar is
cobalt there now, not purple.
A colour changing on the same tab (a project opening) passes into the
next over 300 ms, at once with animations off. A wave paints in that
colour as it goes: it starts in the colour of the screen being left, as
the arriving one claims its own only once it shows, a frame later, and
passes into it with the rest of the bar.
A person's colours may be light, so the chosen destination's colour is
made legible (readableTone, halkyon_ui): a palette colour starts from its
own text tone as Todo's dates take it (its outline in light mode, its
fill in dark), any other colour from itself, then darkens towards black
in light mode and lightens towards white in dark, only until it reads at
4.5:1 on the bar and on its indicator. Every palette colour is tested in
both themes, on the neutral and the tinted surface. This also lifts
Learn's label, which read at about 3:1.
The debug section gains Bar surface, neutral or tinted: tinted mixes 6 %
of the screen's colour into the bar's own surface in light mode and 8 %
in dark, changing with it. Neutral by default, and in the shipping style.
Whats-New: The navigation bar takes the colour of the screen you're on, such as a project's own.
Whats-New-De: Die Navigationsleiste nimmt die Farbe des Bildschirms an, auf dem du bist, etwa die eines Projekts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 01:12:08 +01:00
|
|
|
wave a tap sends over it in the colour of the screen in view, the tab
|
refactor(ui,learn,todo,train,meditate): the navigation bar's one look, the debug section gone
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>
2026-11-08 11:40:35 +01:00
|
|
|
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
|
2026-10-31 12:12:55 +01:00
|
|
|
the bar), whichever router drives it;
|
2026-10-30 23:52:39 +01:00
|
|
|
`AppLoadingIndicator`, `ButtonLoadingIndicator`, `AppRefreshIndicator`;
|
|
|
|
|
`SectionCard`, `SliverCentered`, `EmptyListMessage`, `CountBadge`,
|
feat(learn): Explore by what a licence allows, a skeleton while the library loads, and an assistant's agreement per provider (ADR 0005, rule 12; ADR 0021, D66)
- Explore filters by what a studyset's licence lets a learner do: adapt
it (no NoDerivatives) and use it commercially (no NonCommercial),
which the instance reads from the release's licence
(ExploreQuery.adaptable, commercial) and the app the same way
(LicenseUse). An instance from before ignores both.
- While the studysets are read, the Library shows shimmering skeletons
of their cards rather than a spinner: halkyon_ui's new Shimmer and
SkeletonBox, still where the system asks for no animations.
- The scope's "Include private studysets" is offered only while the
person has a private studyset.
- An agreement to what goes where is per provider (protocol and host):
agreeing to one assistant agrees to every assistant of that provider,
those granted later too, and the agreement sheet says so. No
migration: it is written on every grant of the provider, so taking
one back keeps it for the others, and read per provider from any of
them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-02 01:09:04 +01:00
|
|
|
`TitleWithKicker`, `CollapsingRow`, `ScrollHidingFab`, `Shimmer` and `SkeletonBox`, `liftWhileDragged`,
|
2026-10-30 23:52:39 +01:00
|
|
|
`ReorderToggle`, `ReorderGrip`; `ChevronField`, `requiredDecoration`,
|
|
|
|
|
`FieldInfoButton`, `CountStepper`, `PaletteColorPicker`, `TextLimits` with
|
2026-10-18 21:46:55 +02:00
|
|
|
`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
|
|
|
|
|
|
2026-10-19 20:39:09 +02:00
|
|
|
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:
|
2026-10-18 21:46:55 +02:00
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
dependencies:
|
|
|
|
|
halkyon_ui:
|
2026-10-19 20:39:09 +02:00
|
|
|
path: ../../../packages/halkyon_ui
|
2026-10-18 21:46:55 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
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`,
|
2026-10-19 20:39:09 +02:00
|
|
|
which are committed and never edited or
|
2026-10-18 21:46:55 +02:00
|
|
|
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).
|