feat(train): the settings' explanations behind a question mark (ADR 0030, D63)

What a switch or a section explains, rather than what is the person's
own, is one tap away: `HelpTitle`, a tile's title with halkyon_ui's
`HelpButton` beside it, in place of the explaining subtitles of Moving
more's reminders and suggestions, the music's playlists and smaller
copies, and the Loop's detection, firmware check and firmware through
the instance. Health Connect's short hints stay (they name what each
switch does, and its "taken back" state takes their place), as do its
rationale, the Loop's warnings before an update or a reset and the
settings' one-line hints.

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-03 22:11:57 +01:00
commit b7db078c8e
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
4 changed files with 123 additions and 19 deletions

View file

@ -0,0 +1,19 @@
import 'package:halkyon_ui/halkyon_ui.dart';
import 'package:material_ui/material_ui.dart';
/// A tile's [title] with its explanation, [help], behind halkyon_ui's
/// [HelpButton] beside it rather than under it (ADR 0030, D63): what
/// explains is one tap away, what is the person's own stays in sight.
class const HelpTitle(
final String title, {
required final String help,
super.key,
}) extends StatelessWidget {
@override
Widget build(BuildContext context) => Row(
children: [
Flexible(child: Text(title)),
HelpButton(help, title: title),
],
);
}

View file

@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:halkyon_app/halkyon_app.dart';
import 'package:halkyon_train_flutter/l10n/app_localizations.dart';
import 'package:halkyon_train_flutter/src/app/train_dependencies.dart';
import 'package:halkyon_train_flutter/src/core/widget/help_title.dart';
import 'package:halkyon_train_flutter/src/feature/loop/bloc/loop_cubit.dart';
import 'package:halkyon_train_flutter/src/feature/loop/bloc/loop_setup_cubit.dart';
import 'package:halkyon_train_flutter/src/feature/loop/data/loop_platform.dart';
@ -207,8 +208,7 @@ class const LoopScreen({super.key}) extends StatelessWidget {
return [
SwitchListTile(
secondary: const Icon(LucideIcons.activity),
title: Text(l10n.loopDetection),
subtitle: Text(l10n.loopDetectionHint),
title: HelpTitle(l10n.loopDetection, help: l10n.loopDetectionHint),
value: on,
onChanged: usable
? (on) => unawaited(
@ -303,9 +303,11 @@ class const LoopScreen({super.key}) extends StatelessWidget {
),
ListTile(
leading: const Icon(LucideIcons.search),
title: Text(l10n.loopFirmwareCheck),
subtitle: Text(
through ? l10n.loopFirmwareViaInstance : l10n.loopFirmwareViaPolar,
title: HelpTitle(
l10n.loopFirmwareCheck,
help: through
? l10n.loopFirmwareViaInstance
: l10n.loopFirmwareViaPolar,
),
onTap: state.busy || stage != null || !state.status.connected
? null
@ -314,8 +316,10 @@ class const LoopScreen({super.key}) extends StatelessWidget {
if (state.status.relayOffered || through)
SwitchListTile(
secondary: const Icon(LucideIcons.server),
title: Text(l10n.loopFirmwareThroughInstance),
subtitle: Text(l10n.loopFirmwareThroughInstanceHint),
title: HelpTitle(
l10n.loopFirmwareThroughInstance,
help: l10n.loopFirmwareThroughInstanceHint,
),
value: through,
onChanged: stage != null
? null

View file

@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:halkyon_app/halkyon_app.dart';
import 'package:halkyon_train_flutter/l10n/app_localizations.dart';
import 'package:halkyon_train_flutter/src/app/train_dependencies.dart';
import 'package:halkyon_train_flutter/src/core/widget/help_title.dart';
import 'package:halkyon_train_flutter/src/feature/movement/bloc/movement_cubit.dart';
import 'package:halkyon_train_flutter/src/feature/movement/model/movement_settings.dart';
import 'package:halkyon_ui/halkyon_ui.dart';
@ -98,8 +99,10 @@ class const MovementScreen({super.key}) extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SwitchListTile(
title: Text(l10n.movementRemind),
subtitle: Text(l10n.movementRemindHint),
title: HelpTitle(
l10n.movementRemind,
help: l10n.movementRemindHint,
),
value: settings.remind,
onChanged: usable
? (on) => change(settings.copyWith(remind: on))
@ -194,8 +197,10 @@ class const MovementScreen({super.key}) extends StatelessWidget {
SectionCard(
padding: const EdgeInsets.symmetric(vertical: 8),
child: SwitchListTile(
title: Text(l10n.movementSuggest),
subtitle: Text(l10n.movementSuggestHint),
title: HelpTitle(
l10n.movementSuggest,
help: l10n.movementSuggestHint,
),
value: settings.suggest,
onChanged: usable
? (on) => change(settings.copyWith(suggest: on))

View file

@ -2,6 +2,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:halkyon_app/halkyon_app.dart';
import 'package:halkyon_train_flutter/l10n/app_localizations.dart';
import 'package:halkyon_train_flutter/src/app/train_dependencies.dart';
import 'package:halkyon_train_flutter/src/core/widget/help_title.dart';
import 'package:halkyon_train_flutter/src/feature/music/bloc/music_cubit.dart';
import 'package:halkyon_train_flutter/src/feature/music/data/jellyfin_client.dart';
import 'package:halkyon_train_flutter/src/feature/music/data/music_downloads.dart';
@ -262,11 +263,13 @@ class const _Playlists({
),
const Divider(),
ListTile(
title: Text(
l10n.musicPlaylists,
title: DefaultTextStyle.merge(
style: Theme.of(context).textTheme.titleSmall,
child: HelpTitle(
l10n.musicPlaylists,
help: l10n.musicPlaylistsHint,
),
),
subtitle: Text(l10n.musicPlaylistsHint),
),
if (failure != null)
ListTile(
@ -287,8 +290,7 @@ class const _Playlists({
_Kept(kept: kept),
SwitchListTile(
secondary: const Icon(LucideIcons.fileAudio),
title: Text(l10n.musicSmaller),
subtitle: Text(l10n.musicSmallerHint),
title: HelpTitle(l10n.musicSmaller, help: l10n.musicSmallerHint),
value: settings.smaller,
onChanged: (on) => cubit().keepSmaller(on: on),
),