feat(ui): a question mark that keeps a section's explanation one tap away

HelpButton, beside a section's or a setting's title: the explanation opens
in a small dialog, under its title where given, rather than standing in the
everyday view (the developer's review of 3 Nov 2026). FieldInfoButton's
tooltip stays for a field's short hint.

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 21:27:16 +01:00
commit 55fc245859
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
8 changed files with 85 additions and 3 deletions

View file

@ -26,6 +26,7 @@ export 'src/widget/field_label.dart';
export 'src/widget/floating_navigation_bar.dart';
export 'src/widget/folding_tabs.dart';
export 'src/widget/found_by_id.dart';
export 'src/widget/help_button.dart';
export 'src/widget/keyboard_back_on_resume.dart';
export 'src/widget/launch_screen.dart';
export 'src/widget/lift_while_dragged.dart';

View file

@ -1,6 +1,5 @@
{
"@@locale": "de",
"brand": "Halkyon",
"close": "Schließen",
"cancel": "Abbrechen",
@ -17,5 +16,6 @@
"copiedToClipboard": "In die Zwischenablage kopiert",
"copy": "Kopieren",
"paletteColorName": "{name, select, carrot{Karotte} cerise{Kirschrot} cobalt{Kobalt} copper{Kupfer} enzian{Enzian} fern{Farn} forest{Wald} ocean{Ozean} olive{Olive} pacific{Pazifik} pickle{Essiggurke} pine{Kiefer} pink{Rosa} plum{Pflaume} purple{Lila} reef{Riff} sahara{Sahara} slateblue{Schieferblau} soil{Erde} strawberry{Erdbeere} other{{name}}}",
"colorPickerLabel": "Farbe: {color}"
"colorPickerLabel": "Farbe: {color}",
"explain": "Was ist das?"
}

View file

@ -1,6 +1,5 @@
{
"@@locale": "en",
"brand": "Halkyon",
"@brand": {
"description": "The brand's word, above an app's own in its wordmark (\"Halkyon\" over \"Learn\")."
@ -38,5 +37,9 @@
"type": "String"
}
}
},
"explain": "What's this?",
"@explain": {
"description": "Tooltip of the question mark that opens a text explaining a section or setting (HelpButton)."
}
}

View file

@ -204,6 +204,12 @@ abstract class HalkyonUiLocalizations {
/// In en, this message translates to:
/// **'Colour: {color}'**
String colorPickerLabel(String color);
/// Tooltip of the question mark that opens a text explaining a section or setting (HelpButton).
///
/// In en, this message translates to:
/// **'What\'s this?'**
String get explain;
}
class _HalkyonUiLocalizationsDelegate

View file

@ -90,4 +90,7 @@ class HalkyonUiLocalizationsDe extends HalkyonUiLocalizations {
String colorPickerLabel(String color) {
return 'Farbe: $color';
}
@override
String get explain => 'Was ist das?';
}

View file

@ -90,6 +90,9 @@ class HalkyonUiLocalizationsEn extends HalkyonUiLocalizations {
String colorPickerLabel(String color) {
return 'Colour: $color';
}
@override
String get explain => 'What\'s this?';
}
/// The translations for English, as used in the United Kingdom (`en_GB`).

View file

@ -0,0 +1,37 @@
import 'package:halkyon_ui/l10n/halkyon_ui_localizations.dart';
import 'package:halkyon_ui/src/widget/field_label.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import 'package:material_ui/material_ui.dart';
/// A question mark beside a section's or a setting's title that keeps its
/// explanation out of the everyday view: a tap opens [message] in a small
/// dialog, under [title] where given (decided 3 Nov 2026: what explains,
/// rather than what is the person's own, is one tap away). For a field's
/// short hint, [FieldInfoButton]'s tooltip.
class const HelpButton(
final String message, {
final String? title,
super.key,
}) extends StatelessWidget {
@override
Widget build(BuildContext context) => IconButton(
icon: const Icon(LucideIcons.circleQuestionMark, size: 20),
color: Theme.of(context).colorScheme.onSurfaceVariant,
visualDensity: VisualDensity.compact,
tooltip: HalkyonUiLocalizations.of(context).explain,
onPressed: () => showDialog<void>(
context: context,
builder: (context) => AlertDialog(
icon: const Icon(LucideIcons.circleQuestionMark),
title: title == null ? null : Text(title!),
content: SingleChildScrollView(child: Text(message)),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(MaterialLocalizations.of(context).okButtonLabel),
),
],
),
),
);
}

View file

@ -0,0 +1,29 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:halkyon_ui/halkyon_ui.dart';
import 'package:material_ui/material_ui.dart';
import '../support/test_app.dart';
void main() {
testWidgets('the explanation stays hidden until the question mark is '
'tapped, then shows under its title and closes again', (tester) async {
await tester.pumpWidget(
testApp(
const Scaffold(
body: HelpButton('How sources are cited.', title: 'Citation style'),
),
),
);
expect(find.text('How sources are cited.'), findsNothing);
expect(find.byTooltip("What's this?"), findsOneWidget);
await tester.tap(find.byType(HelpButton));
await tester.pumpAndSettle();
expect(find.text('Citation style'), findsOneWidget);
expect(find.text('How sources are cited.'), findsOneWidget);
await tester.tap(find.text('OK'));
await tester.pumpAndSettle();
expect(find.text('How sources are cited.'), findsNothing);
});
}