feat(todo): dialogs on the desktop, the tabs reordered, a task's path and menu (ADR 0033, D11 to D14)

- halkyon_ui: showAdaptiveSheet shows a dialog on Linux, macOS and Windows
  at every width (bottom sheets stay on phones and tablets below expanded
  windows), so Learn and Train follow; SheetScaffold takes a heading in
  the title's place and may leave out its close button.
- Todo: the tabs are Inbox, Today, Upcoming, Browse, Today still opening;
  `serverpod start` launches the app on Linux, as it has no web build.
- A task's sheet shows where the task is (its project, an icon once tasks
  are above it, and the tasks above it, each opening what it names) and a
  ⋮ menu in place of its close button: when it was added (its UUID v7),
  Duplicate with its sub-tasks, hiding its done sub-tasks (synced in its
  payload), Close in the pane, Delete after asking. The description's icon
  stays by its first line.
- A row names its project and section at the end of its date's line; every
  menu entry has its icon.
- Learn: a studyset's path starts at the Library tab's icon, named by its
  tooltip.

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-01 16:53:38 +01:00
commit b908019969
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -48,7 +48,8 @@ void main() {
await tester.pumpAndSettle();
}
testWidgets('is a bottom sheet below expanded windows', (tester) async {
testWidgets('is a bottom sheet below expanded windows on a phone or a '
'tablet', (tester) async {
await open(tester, width: 839);
expect(find.byType(BottomSheet), findsOneWidget);
@ -62,6 +63,23 @@ void main() {
expect(find.byType(BottomSheet), findsNothing);
});
for (final platform in [
TargetPlatform.linux,
TargetPlatform.macOS,
TargetPlatform.windows,
]) {
testWidgets('is a dialog on the desktop however narrow the window '
'(${platform.name})', (tester) async {
debugDefaultTargetPlatformOverride = platform;
addTearDown(() => debugDefaultTargetPlatformOverride = null);
await open(tester, width: 400);
expect(find.byType(Dialog), findsOneWidget);
expect(find.byType(BottomSheet), findsNothing);
debugDefaultTargetPlatformOverride = null;
});
}
for (final width in [400.0, 1200.0]) {
testWidgets('keeps its close button and footer in view above a long body '
'(${width.round()} wide)', (tester) async {
@ -184,6 +202,43 @@ void main() {
});
});
testWidgets("a heading of its own takes the title's place, and the close "
'button may give way to its action', (tester) async {
tester.view.physicalSize = const Size(400, 900);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
await tester.pumpWidget(
MaterialApp(
localizationsDelegates: HalkyonUiLocalizations.localizationsDelegates,
supportedLocales: HalkyonUiLocalizations.supportedLocales,
home: Scaffold(
body: Builder(
builder: (context) => TextButton(
onPressed: () => showAdaptiveSheet<void>(
context,
builder: (context) => const SheetScaffold(
title: 'Task',
heading: Text('Inbox › Shopping'),
closeButton: false,
action: Text('More'),
body: Text('Body'),
),
),
child: const Text('Open'),
),
),
),
),
);
await tester.tap(find.text('Open'));
await tester.pumpAndSettle();
expect(find.text('Inbox › Shopping'), findsOneWidget);
expect(find.text('Task'), findsNothing);
expect(find.byTooltip('Close'), findsNothing);
expect(find.text('More'), findsOneWidget);
});
testWidgets('a body that fits shows no lines', (tester) async {
await open(tester, width: 400);