fix(ui): on a phone, a snack bar stands above the screen's floating button

Below medium windows the frame's scaffold showed the screens' snack bars,
under the floating button of the screen's own scaffold, so a tap on a
snack bar's Undo hit the button instead (found 9 Nov 2026, Todo's Undo
after deleting the completed tasks). The screens get a messenger of their
own there, and their scaffolds lift the snack bars above their buttons, as
Material places them. From medium windows on the frame's scaffold still
shows them, once: a messenger shows a snack bar on each of its scaffolds
not inside another, as two panes side by side are. As the window grows
past a phone's, the phone's snack bars go, since a scaffold whose
messenger changes keeps the one it showed for good.

Whats-New: On a phone, a message's Undo is no longer hidden under the + button.
Whats-New-De: Auf dem Handy liegt das Rückgängig einer Meldung nicht mehr unter der +-Schaltfläche.
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-09 23:12:29 +01:00
commit 560de2ed93
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -250,6 +250,115 @@ void main() {
);
});
testWidgets("a screen's snack bar stands above its floating button, so "
'the button never takes its Undo (found 9 Nov 2026)', (tester) async {
tester.view.physicalSize = const Size(400, 800);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
var undone = 0;
var added = 0;
await tester.pumpWidget(
testApp(
NavigationFrame(
appName: 'Halkyon Test',
icon: icon,
accent: const Color(0xFFEC3E7C),
selectedIndex: 0,
onSelected: (_) {},
destinations: const [
(label: 'Home', icon: Icon(Icons.home), selectedIcon: null),
(label: 'Maps', icon: Icon(Icons.map), selectedIcon: null),
],
child: Builder(
builder: (context) => Scaffold(
body: Center(
child: TextButton(
onPressed: () => showUndoSnackBar(
context,
message: 'Task completed',
onUndo: () => undone++,
),
child: const Text('Complete'),
),
),
floatingActionButton: Padding(
padding: EdgeInsets.only(
bottom: FlushNavigationBar.buttonLiftOf(context),
),
child: FloatingActionButton(
onPressed: () => added++,
child: const Icon(Icons.add),
),
),
),
),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.text('Complete'));
await tester.pumpAndSettle();
expect(
tester.getRect(find.text('Undo')).bottom,
lessThanOrEqualTo(
tester.getRect(find.byType(FloatingActionButton)).top,
),
);
await tester.tap(find.text('Undo'));
expect((undone, added), (1, 0));
});
testWidgets("a screen's snack bar shown on a phone doesn't stay once the "
'window grows past it', (tester) async {
final screens = GlobalKey();
tester.view.physicalSize = const Size(400, 800);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
await tester.pumpWidget(
testApp(
NavigationFrame(
appName: 'Halkyon Test',
icon: icon,
accent: const Color(0xFFEC3E7C),
selectedIndex: 0,
onSelected: (_) {},
destinations: const [
(label: 'Home', icon: Icon(Icons.home), selectedIcon: null),
(label: 'Maps', icon: Icon(Icons.map), selectedIcon: null),
],
// The screens keep their state as the frame's layout changes, as an
// app's router keeps its navigator by its key.
child: KeyedSubtree(
key: screens,
child: Builder(
builder: (context) => Scaffold(
body: Center(
child: TextButton(
onPressed: () => ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Archived')),
),
child: const Text('Archive'),
),
),
),
),
),
),
),
);
await tester.tap(find.text('Archive'));
await tester.pumpAndSettle();
expect(find.text('Archived'), findsOneWidget);
tester.view.physicalSize = const Size(1200, 800);
await tester.pumpAndSettle();
await tester.pump(const Duration(seconds: 5));
await tester.pumpAndSettle();
expect(find.text('Archived'), findsNothing);
});
testWidgets("outside the frame, content leaves room for the system's bar "
'alone', (tester) async {
tester.view.padding = const FakeViewPadding(bottom: 24);