feat(app): what's new after an update, drafted by the release tool (ADR 0030, D61)

halkyon_app's WhatsNew reads what each release changed from the app's
assets/whats_new/ (<version>.<language>.txt, English and German, a change
a line): after an update a sheet lists every release since the one the
device showed last, newest first, once; Settings shows the running
version's again, above About. Nothing on a fresh install.

tool/release.dart drafts the missing texts from the commits that count
after "Go on?" (German marked TRANSLATE:), opens them in the editor until
they fit (no mark left, F-Droid's 500 characters) and commits them with
the version. The English is the Forgejo notes' What's new, each language
F-Droid's changelog of the build, written at publishing and copied in by
tool/fdroid_repo.sh. Learn and Train bundle their texts, Learn's 0.22.0
and Train's 0.1.0 written to start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-31 19:36:20 +01:00
commit 7727317c68
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
7 changed files with 1272 additions and 76 deletions

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
- Die erste Version von Halkyon Train: Zeichne dein Training auf, plane Routen und Intervallpläne und behalte deine Tage vom Polar Loop, alles versiegelt.

View file

@ -0,0 +1 @@
- The first release of Halkyon Train: record your workouts, plan routes and interval plans, and keep your days from the Polar Loop, everything sealed.

View file

@ -1,5 +1,6 @@
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:halkyon_app/halkyon_app.dart';
import 'package:halkyon_train_flutter/src/app/licences.dart';
import 'package:halkyon_train_flutter/src/app/train_app.dart';
@ -32,12 +33,21 @@ Future<void> main() async {
);
await instances.load(usedBefore: device.storage.usedBefore());
runApp(
TrainApp(
choice: instances,
// Only an instance of Halkyon Train: one of another Halkyon app's
// answers too, but holds nothing of this app's.
checker: ServerpodInstanceChecker(app: 'train'),
open: (instance) => TrainDependencies.create(instance, device),
WhatsNewScope(
whatsNew: WhatsNew(
AssetWhatsNewSource(rootBundle),
FileWhatsNewStore(File(p.join(device.support, 'whats_new.json'))),
version: device.appVersion,
),
// An instance chosen before: the app ran on this device already.
usedBefore: instances.current != null,
child: TrainApp(
choice: instances,
// Only an instance of Halkyon Train: one of another Halkyon app's
// answers too, but holds nothing of this app's.
checker: ServerpodInstanceChecker(app: 'train'),
open: (instance) => TrainDependencies.create(instance, device),
),
),
);
}

View file

@ -133,20 +133,28 @@ class const SettingsScreen({super.key}) extends StatelessWidget {
const SizedBox(height: 12),
SectionCard(
padding: const EdgeInsets.symmetric(vertical: 8),
child: ListTile(
leading: const Icon(LucideIcons.info),
title: Text(l10n.aboutTitle),
// halkyon_app's About: the app's name and icon, the
// source of what the instance runs, which both
// licences ask to offer, and the licence list.
onTap: () => showAppAbout(
context,
icon: const Image(
image: trainIcon,
width: 48,
height: 48,
child: Column(
children: [
// What changed in this version, as shown once after
// the update (halkyon_app's, D61).
const WhatsNewTile(),
ListTile(
leading: const Icon(LucideIcons.info),
title: Text(l10n.aboutTitle),
// halkyon_app's About: the app's name and icon,
// the source of what the instance runs, which
// both licences ask to offer, and the licence
// list.
onTap: () => showAppAbout(
context,
icon: const Image(
image: trainIcon,
width: 48,
height: 48,
),
),
),
),
],
),
),
const SizedBox(height: 24),

View file

@ -21,7 +21,8 @@ import 'package:material_ui/material_ui.dart';
/// off as the app opens is offered to be saved or discarded, whichever tab
/// shows, and one under way stands above the bar on every tab
/// ([RecordingBar]); GPX files Android hands Train are brought in as routes (D45), and
/// Health Connect's links to Train's rationale are followed (D31).
/// Health Connect's links to Train's rationale are followed (D31). After an
/// update, what's new shows once (halkyon_app's [WhatsNewOnUpdate], D61).
class const TrainShell({
/// The tabs' navigators, from `StatefulShellRoute`.
required final StatefulNavigationShell shell,
@ -81,7 +82,7 @@ class const TrainShell({
child: shell,
);
final listening = HealthConnectListener(
child: RouteFilesListener(child: frame),
child: RouteFilesListener(child: WhatsNewOnUpdate(child: frame)),
);
if (!records) {
return listening;

View file

@ -97,6 +97,10 @@ flutter:
# The instance the build names, unless --dart-define=SERVER_URL=… does
# (serverpod_flutter's getServerUrl).
- assets/config.json
# What each release changed, one file a release and language
# (<version>.<language>.txt), drafted by tool/release.dart and edited
# (ADR 0030, D61): halkyon_app's What's new shows them.
- assets/whats_new/
# The app's icon, Lucide's bike (ADR 0031), and Lucide's licence for the
# licence list (the repository's tool/app_icons.py writes both).
- assets/icon/halkyon_train_icon.png