From 07ac20e8b7f31b4c15d1d442caee1dc3df313108 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Mon, 27 Jul 2026 21:22:15 +0300 Subject: [PATCH] fix(ui): honor barrierDismissible Escape on showAppDialog Pass the flag through to showGeneralDialog so ModalRoute DismissIntent closes on Escape when dismissible; keep frosted backdrop tap handling. Closes #446 --- lib/shared/widgets/app_dialog.dart | 6 ++++- test/shared/app_dialog_test.dart | 42 ++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/lib/shared/widgets/app_dialog.dart b/lib/shared/widgets/app_dialog.dart index d47fc7d0..1a92ceaa 100644 --- a/lib/shared/widgets/app_dialog.dart +++ b/lib/shared/widgets/app_dialog.dart @@ -10,6 +10,10 @@ import 'package:querya_desktop/core/motion/querya_spring.dart'; /// /// Use instead of [showDialog] so every overlay has consistent blur. /// Enter: fade + slight slide; exit uses [QueryaMotion.exit] via reverseCurve. +/// +/// When [barrierDismissible] is true (default), Escape and backdrop tap dismiss. +/// Escape is handled by the modal route; backdrop tap by [_BlurredDialogScaffold] +/// (the route barrier stays transparent under the frosted layer). Future showAppDialog({ required BuildContext context, required WidgetBuilder builder, @@ -17,7 +21,7 @@ Future showAppDialog({ }) { return showGeneralDialog( context: context, - barrierDismissible: false, + barrierDismissible: barrierDismissible, barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, barrierColor: Colors.transparent, transitionDuration: context.motionDuration(QueryaMotion.standard), diff --git a/test/shared/app_dialog_test.dart b/test/shared/app_dialog_test.dart index 6f39ddee..6a6b8bb4 100644 --- a/test/shared/app_dialog_test.dart +++ b/test/shared/app_dialog_test.dart @@ -1,6 +1,7 @@ import 'dart:ui' show ImageFilter; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:querya_desktop/core/motion/querya_motion.dart'; import 'package:querya_desktop/core/motion/querya_motion_scope.dart'; @@ -70,6 +71,47 @@ void main() { await future; }); + testWidgets('barrierDismissible true closes dialog on Escape', (tester) async { + final ctx = await pumpHost(tester); + var completed = false; + + final future = showAppDialog( + context: ctx, + barrierDismissible: true, + builder: (c) => const AlertDialog(title: Text('Escapable')), + ).whenComplete(() => completed = true); + + await tester.pumpAndSettle(); + expect(find.text('Escapable'), findsOneWidget); + + await tester.sendKeyEvent(LogicalKeyboardKey.escape); + await tester.pumpAndSettle(); + + expect(find.text('Escapable'), findsNothing); + expect(completed, isTrue); + await future; + }); + + testWidgets('barrierDismissible false ignores Escape', (tester) async { + final ctx = await pumpHost(tester); + + final future = showAppDialog( + context: ctx, + barrierDismissible: false, + builder: (c) => const AlertDialog(title: Text('No escape')), + ); + + await tester.pumpAndSettle(); + await tester.sendKeyEvent(LogicalKeyboardKey.escape); + await tester.pumpAndSettle(); + + expect(find.text('No escape'), findsOneWidget); + + Navigator.of(ctx, rootNavigator: true).pop(); + await tester.pumpAndSettle(); + await future; + }); + testWidgets('showAppDialog uses fade-slide (not scale) with BackdropFilter', (tester) async { final ctx = await pumpHost(tester);