feat(ui,learn,todo,train,meditate): the kingfisher on the launch screens, and as Learn's icon

The commissioned kingfisher (tool/kingfisher.svg) becomes Halkyon's
again. Learn's launcher icon is the bird in its own colours on the
white tile, its themed variants the bird's outline, and its launch
screen shows the bird alone (ADR 0031, D12; ADR 0045, D14). Todo,
Train and Meditate keep their glyphs on the launchers, and their launch
screens show the bird in their own colours (D11). tool/app_icons.py
draws all of them; halkyon_ui's LaunchScreen takes an emblem in place
of the icon. Lucide's notice leaves Learn's licence list, as its icon
no longer is Lucide's.

Whats-New: Halkyon's kingfisher is back: Learn's icon, and on the screen while each app opens.
Whats-New-De: Halkyons Eisvogel ist zurück: als Symbol von Learn und auf dem Bildschirm, während jede App startet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-12 13:33:37 +01:00
commit 4eff894128
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
4 changed files with 233 additions and 151 deletions

View file

@ -7,7 +7,7 @@ What holds for every app and package of this repository is in the [root AGENTS.m
- **The app** is halkyon_app's `InstanceGate` and `SessionGate` with Meditate's frame (`meditateFrame`) and `ServerpodInstanceChecker(app: 'meditate')`; `MeditateDependencies` per instance, `MeditatePerson` per person (their sync, `SettingsRepository`, `SittingRepository`, `DreamRepository`, `LibraryRepository`, `LessonRepository`, and the device's `BellPlayer`, `GuidePlayer` and `ScreenAwake`); SimpleLogin's way back on `app.halkyon.meditate://` and the loopback port 43826. The person's app (`MeditatePersonApp`) washes in fern (`ScreenColor`, D3) and holds `PracticeCubit` (settings and sittings, read again on every change), `JournalCubit` (the dreams) and `LibraryCubit` (the instance's library, lessons done, recordings kept) for every screen. go_router (`buildMeditateRouter`, `MeditatePaths`): halkyon_ui's `NavigationFrame` (`MeditateShell`) with Today, Explore, Journal and Profile (D2, D14; a bar below medium windows, a rail with the icon from medium on), a practice's page in Explore's branch (`PracticeScreen`) with a library item's and course's (`/explore/library/:id`, `?course=` for a lesson, `/explore/courses/:id`), and over the tabs the sitting (`SittingScreen`, handed its `SittingPlan` as `extra`), a recording playing (`ListeningScreen`, its `ListeningPlan` as `extra`), the library's credits (`/library-credits`), a dream (`DreamScreen`, `/dream` new, `/dream/:id` kept) and Settings. **Today** (`TodayTab`, D9): greeting by the hour, from 4 to 12 o'clock a card to write down last night's dream while it has none (`dreamPromptDue`, D14), Sit now (5, 10, 20 and the timer's own length), the practice of the day (`practiceOfDay`), the week and the days in a row (`WeekCard`), favourites, recently sat. **Explore** (`ExploreTab`, D10): search folded without case or accents (`matchesSearch`), four goal tiles, kind chips, practice cards. **The Journal** (`JournalTab`, D14; `lib/src/feature/journal/`): dreams by month, the newest first (`dreamsByMonth`), searched over title, text and labels and filtered by marks (`findDreams`), *+* to write one; a dream's night is the date of the morning after it (`defaultNight`: today's date at any hour); its screen writes with the text first and focused, the rest folded, and reads a kept one with Edit and Delete; a microphone under the text dictates where the device recognises speech (D15: halkyon_app's `Dictation` on `MeditatePerson.speech`, `DeviceSpeechInput`: on Android the voice input app first where one is installed, D18, opened once a tap and its text put where the cursor stood, else Android's recogniser, on the device where it can, listening again after each pause until stopped or three silent listens; none on Linux, `NoSpeechInput` in tests that don't say), and Today's card offers *Speak it* (`/dream?speak=1`). The platforms declare the microphone as Learn's do (Android's `RECORD_AUDIO`, the recognition service's and `RECOGNIZE_SPEECH`'s queries, `VoiceInput.kt` answering `app.halkyon/voice_input` from `MainActivity`, iOS's and macOS's usage texts, macOS's `audio-input`), and the privacy page's points are `meditate_base.dart`'s. **Profile** (D2, D8): halkyon_app's `HalkyonProfile` with a *Practice* tab (minutes, sittings, days, the week, the journal's dreams this month and lucid ones once it holds one, every sitting with Delete). **The practices** are `practices` in `lib/src/feature/practice/model/practice.dart` (IDs never change once released), their words in the ARBs by ID (`practiceTexts`), their cues spread evenly over the length (`Practice.cueAt`). **A sitting** (`SittingCubit`, D6): its time from `clock.now()`, the ticks only showing it; the bell at the start (where on), between every interval and at the end; the screen kept on while it runs (where on); kept whole at its end, and ended early kept from a minute on where the person says so. **The figures** (`practiceFigures`, D8) on the device: a sitting's day by the wall clock it was sat by (`Sitting.offsetMinutes`), the streak up to today or yesterday, the week from the language's first day. **Settings** (D11): Sync, the operator's card, the device's appearance and language (halkyon_app's `DeviceLookCard` on `LookCubit`, kept in `<app support>/look.json`, `meditateLanguages`; ADR 0014's amendment of 4 Nov 2026), *Sitting* (bell, starting bell, bells between, screen on, the timer's length), *Practice* (the streak shown), *Journal* (Today's morning card, `dreamPrompt`), the account, What's new, About, the version. Texts in British English (the template) and German (du), `en_US` and `en_GB` empty. Widget tests start the whole app with halkyon_app's fakes (`test/app/meditate_app_test.dart`), the bells and screen recorded (`test/support/recording_device.dart`); a sitting on screen ticks every second, so tests pump seconds (`sit`) rather than `pumpAndSettle`; a test needing the hour (Today's morning card) starts the app inside `withClock` (`startAt`).
- **The operator's library** (D16; `lib/src/library/` on the server, `lib/src/feature/library/` in the app): a folder named by `MEDITATE_LIBRARY_DIR` with recordings, pictures and `library.yaml` (commented sample: the server's `library.example.yaml`), read by `readLibrary` (every check and refusal, with the reason), held in memory by `MeditateLibrary` and reread every ten minutes (`LibraryReloadFutureCall`, `startLibrary`); checked before deploying with `dart run bin/check_library.dart <folder>` in the server package. `LibraryEndpoint.catalogue` for signed-in people alone, with an access (`LibraryFileAccess`: the hour begun plus 12 hours, signed with a key made at start); the web server's `/library/<served name>?access=…` serves the files by ranges (`serveLibraryFile`), never by a path. In the app: Explore's section (none without a library), the item's page (recordings, Play, favourite as `library/<id>` in the settings, keep on the device, a sound under a silent sitting, its credit), the course's (lessons ticked), the player (a finished item is a sitting `library/<id>` and its lesson done), Settings' *Library credits*; the last catalogue in the person's folder (`library.json`), recordings kept in `library/` beside it. Tests: `test/feature/library/library_test.dart` and the app test's group, on `test/support/fake_library.dart` (made-up entries only: nothing of 7Mind's, whose structure alone D16 follows).
- **The bells** (D5) are `assets/bells/*.wav`, synthesised by `apps/meditate/tool/bells.py` (Python's standard library, run from `apps/meditate`; the same bytes every run) and played with `audioplayers` (`AudioplayersBellPlayer`). Only while the app runs: bells with the screen off are for later.
- **The icon** is Lucide's `sprout` from cobalt through pacific into fern (ADR 0031, D5, D7), drawn by the repository's `tool/app_icons.py` (`python3 -I tool/app_icons.py meditate` from the root): `assets/icon/halkyon_meditate_icon.png`, Lucide's licence beside it, every platform's launcher icon.
- **The icon** is Lucide's `sprout` from cobalt through pacific into fern (ADR 0031, D5, D7), drawn by the repository's `tool/app_icons.py` (`python3 -I tool/app_icons.py meditate` from the root): `assets/icon/halkyon_meditate_icon.png`, Lucide's licence beside it, every platform's launcher icon. Its launch screen shows Halkyon's kingfisher in its colours instead (ADR 0031, D11: `assets/icon/halkyon_meditate_kingfisher.png`, `meditateKingfisher`), drawn by the same tool.
- **`serverpod start`** runs in `apps/meditate/halkyon_meditate_server` on ports of its own (8280 to 8282, PostgreSQL 8290), so it can run beside Learn's (8080…) and Train's (8180…); its tests need throwaway passwords in its git-ignored `config/passwords.yaml` (`database`, `serviceSecret`, `serverSideSessionKeyHashPepper`, `emailSignInPepper`) or from the environment, as CI gives them.
- **The deploy** (ADR 0038, step 7; ADR 0039, D14, 8 Nov 2026): the server's `Dockerfile` (as Todo's, with `bin/check_library` compiled beside the server) and `config/production.yaml` (its database `halkyon_meditate` as its own role; the public host from `.env`, `MEDITATE_DOMAIN`), `deploy/compose.meditate.yaml` (a member of the register) with `sidecars/meditate.caddy` (`/library/*` to the web server, the rest to the API server), the library in `deploy/compose.meditate-library.yaml` (`MEDITATE_LIBRARY`, read-only), `redeploy.sh` and `backup.sh` taking it in; `test/deploy/meditate_deploy_test.dart`; the steps in `deploy/sidecars/README.md`, *Meditate*. Not deployed yet: halkyon.li is its first instance.
- **Not yet**: releases (`apps/meditate/release.dart`, its key, F-Droid listing, snap), and what ADR 0038 lists under *Later*.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -8,6 +8,12 @@ import 'package:material_ui/material_ui.dart';
/// pacific into fern, the app's backdrop (ADR 0031, D5).
const meditateIcon = AssetImage('assets/icon/halkyon_meditate_icon.png');
/// Halkyon's kingfisher in Meditate's colours, on its launch screen (ADR
/// 0031, D11), drawn by `tool/app_icons.py`.
const meditateKingfisher = AssetImage(
'assets/icon/halkyon_meditate_kingfisher.png',
);
/// Halkyon Meditate as halkyon_app's own screens know it (the first start, the
/// start screen, the account switcher, signing in): its name and word, its
/// icon, its look and languages, and the sentence only it can say.
@ -32,14 +38,15 @@ String _product(BuildContext context) =>
String _firstStartSubtitle(BuildContext context) =>
AppLocalizations.of(context).instanceFirstStartSubtitle;
/// What shows while the app opens: halkyon_ui's [LaunchScreen] with Meditate's
/// icon, "Meditate" beneath "Halkyon", read out as the app's name.
/// What shows while the app opens: halkyon_ui's [LaunchScreen] with the
/// kingfisher in Meditate's colours (ADR 0031, D11), "Meditate" beneath
/// "Halkyon", read out as the app's name.
class const MeditateLaunchScreen({super.key}) extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return LaunchScreen(
icon: meditateIcon,
emblem: meditateKingfisher,
product: l10n.launchProduct,
name: l10n.appTitle,
);

View file

@ -93,6 +93,7 @@ flutter:
# The app's icon, Lucide's sprout (ADR 0031), and Lucide's licence for the
# licence list (the repository's tool/app_icons.py writes both).
- assets/icon/halkyon_meditate_icon.png
- assets/icon/halkyon_meditate_kingfisher.png
- assets/icon/LICENSE-lucide.txt
# The app's icons, written by the repository's tool/app_icons.py: it prepares the