feat(todo,meditate): what a setting does behind its question mark

The developer's review of 3 Nov 2026 (ADR 0033, D25): an explanation stands
one tap away, behind halkyon_ui's HelpButton beside the setting's name,
rather than always below it. Todo: resetting sub-tasks, the automatic
reminder (its value stays below), counting a recurrence from completion.
Meditate: keeping the screen on, the streak, the morning's dream prompt.
What is the person's own (a value, a count) stays in view; an empty list's
message and quick add's hint about highlighted words, shown only while
there are some, stay too.

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:02:38 +01:00
commit ef80453fa0
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
4 changed files with 102 additions and 16 deletions

View file

@ -0,0 +1,19 @@
import 'package:halkyon_ui/halkyon_ui.dart';
import 'package:material_ui/material_ui.dart';
/// A setting's [title] with the question mark that explains it ([help],
/// halkyon_ui's [HelpButton]) beside it, rather than the explanation always
/// below (the developer's review, 3 Nov 2026).
class const TitleWithHelp(
final String title,
final String help, {
super.key,
}) extends StatelessWidget {
@override
Widget build(BuildContext context) => Row(
children: [
Flexible(child: Text(title)),
HelpButton(help, title: title),
],
);
}

View file

@ -1,5 +1,6 @@
import 'package:clock/clock.dart';
import 'package:halkyon_todo_flutter/l10n/app_localizations.dart';
import 'package:halkyon_todo_flutter/src/core/widget/title_with_help.dart';
import 'package:halkyon_todo_flutter/src/feature/task/model/repeat.dart';
import 'package:halkyon_todo_flutter/src/feature/task/model/task.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_texts.dart';
@ -352,8 +353,10 @@ class _RepeatEditorState() extends State<_RepeatEditor> {
),
),
SwitchListTile(
title: Text(l10n.repeatCountFromCompletion),
subtitle: Text(l10n.repeatCountFromCompletionHint),
title: TitleWithHelp(
l10n.repeatCountFromCompletion,
l10n.repeatCountFromCompletionHint,
),
value: _fromCompletion,
onChanged: (on) => setState(() => _fromCompletion = on),
),

View file

@ -3,6 +3,7 @@ import 'package:halkyon_app/halkyon_app.dart';
import 'package:halkyon_todo_flutter/l10n/app_localizations.dart';
import 'package:halkyon_todo_flutter/src/app/todo_dependencies.dart';
import 'package:halkyon_todo_flutter/src/core/privacy/todo_keys.dart';
import 'package:halkyon_todo_flutter/src/core/widget/title_with_help.dart';
import 'package:halkyon_todo_flutter/src/feature/reminder/model/snooze.dart';
import 'package:halkyon_todo_flutter/src/feature/reminder/widget/reminder_picker.dart';
import 'package:halkyon_todo_flutter/src/feature/settings/model/todo_settings.dart';
@ -62,8 +63,10 @@ class const TaskSettingsCard({super.key}) extends StatelessWidget {
),
SwitchListTile(
secondary: const Icon(LucideIcons.listRestart),
title: Text(l10n.resetSubTasksTitle),
subtitle: Text(l10n.resetSubTasksHint),
title: TitleWithHelp(
l10n.resetSubTasksTitle,
l10n.resetSubTasksHint,
),
value: tasks.resetSubTasks,
onChanged: (on) => _write(
context,
@ -94,12 +97,11 @@ class const ReminderSettingsCard({super.key}) extends StatelessWidget {
children: [
ListTile(
leading: const Icon(LucideIcons.alarmClockPlus),
title: Text(l10n.automaticReminderTitle),
subtitle: Text(
'${automatic(reminders.automatic)}\n'
'${l10n.automaticReminderHint}',
title: TitleWithHelp(
l10n.automaticReminderTitle,
l10n.automaticReminderHint,
),
isThreeLine: true,
subtitle: Text(automatic(reminders.automatic)),
onTap: () async {
final picked = await _pick<int?>(
context,

View file

@ -2712,6 +2712,28 @@ void main() {
() => SettingsRepository(settingsRows, keys).read(),
))!;
testWidgets('what a setting does stays behind its question mark until '
"asked (the developer's review, 3 Nov 2026)", (tester) async {
await start(tester, usedBefore: true);
await openSettings(tester);
const hint = 'Open them again when a recurring task is done';
expect(find.text(hint), findsNothing);
expect(
find.textContaining('unless it has reminders of its own'),
findsNothing,
);
final help = find.descendant(
of: find.widgetWithText(SwitchListTile, 'Reset sub-tasks'),
matching: find.byType(HelpButton),
);
await tester.ensureVisible(help);
await tester.pumpAndSettle();
await tester.tap(help);
await tester.pumpAndSettle();
expect(find.text(hint), findsOneWidget);
});
testWidgets('Tasks and Reminders come before the account; a week '
"started on Sunday starts Upcoming's strip on it, kept for every "
'device', (tester) async {
@ -2750,7 +2772,13 @@ void main() {
'reminders are snoozed for as long as set', (tester) async {
await start(tester, usedBefore: true);
await openSettings(tester);
expect(find.textContaining(RegExp(r'^None\n')), findsOneWidget);
expect(
find.descendant(
of: find.widgetWithText(ListTile, 'Automatic reminder'),
matching: find.text('None'),
),
findsOneWidget,
);
await choose(tester, 'Automatic reminder', '30 minutes before');
await choose(tester, 'When snoozed', '10 minutes');
final settings = await stored(tester);