From e42f7e4e67550f63e225b8e856a254293d17435f Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Mon, 19 Jun 2023 14:04:58 +0200 Subject: [PATCH] Prevent underlying page from stealing focus from ResponsiveDialog. --- lib/app/message.dart | 39 ++++------ lib/widgets/responsive_dialog.dart | 113 +++++++++++++++++------------ 2 files changed, 82 insertions(+), 70 deletions(-) diff --git a/lib/app/message.dart b/lib/app/message.dart index 93b47748..db7fcda8 100755 --- a/lib/app/message.dart +++ b/lib/app/message.dart @@ -32,27 +32,20 @@ Future showBlurDialog({ required BuildContext context, required Widget Function(BuildContext) builder, RouteSettings? routeSettings, -}) async { - const transitionDelay = Duration(milliseconds: 150); - final result = await showGeneralDialog( - context: context, - barrierDismissible: true, - barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, - pageBuilder: (ctx, anim1, anim2) => builder(ctx), - transitionDuration: transitionDelay, - transitionBuilder: (ctx, anim1, anim2, child) => BackdropFilter( - filter: - ImageFilter.blur(sigmaX: 20 * anim1.value, sigmaY: 20 * anim1.value), - child: FadeTransition( - opacity: anim1, - child: child, +}) async => + await showGeneralDialog( + context: context, + barrierDismissible: true, + barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, + pageBuilder: (ctx, anim1, anim2) => builder(ctx), + transitionDuration: const Duration(milliseconds: 150), + transitionBuilder: (ctx, anim1, anim2, child) => BackdropFilter( + filter: ImageFilter.blur( + sigmaX: 20 * anim1.value, sigmaY: 20 * anim1.value), + child: FadeTransition( + opacity: anim1, + child: child, + ), ), - ), - routeSettings: routeSettings, - ); - // Make sure we wait for the dialog to fade out before returning the result. - // This is needed for subsequent dialogs with autofocus. - await Future.delayed(transitionDelay); - - return result; -} + routeSettings: routeSettings, + ); diff --git a/lib/widgets/responsive_dialog.dart b/lib/widgets/responsive_dialog.dart index 0b3d6d17..d5da0006 100755 --- a/lib/widgets/responsive_dialog.dart +++ b/lib/widgets/responsive_dialog.dart @@ -39,56 +39,75 @@ class ResponsiveDialog extends StatefulWidget { class _ResponsiveDialogState extends State { final Key _childKey = GlobalKey(); + final _focus = FocusScopeNode(); + + @override + void dispose() { + super.dispose(); + _focus.dispose(); + } + + Widget _buildFullscreen(BuildContext context) => Scaffold( + appBar: AppBar( + title: widget.title, + actions: widget.actions, + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: widget.allowCancel + ? () { + widget.onCancel?.call(); + Navigator.of(context).pop(); + } + : null), + ), + body: SingleChildScrollView( + child: + SafeArea(child: Container(key: _childKey, child: widget.child)), + ), + ); + + Widget _buildDialog(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + final cancelText = widget.onCancel == null && widget.actions.isEmpty + ? l10n.s_close + : l10n.s_cancel; + return AlertDialog( + title: widget.title, + titlePadding: const EdgeInsets.only(top: 24, left: 18, right: 18), + scrollable: true, + contentPadding: const EdgeInsets.symmetric(vertical: 8), + content: SizedBox( + width: 380, + child: Container(key: _childKey, child: widget.child), + ), + actions: [ + TextButton( + child: Text(cancelText), + onPressed: () { + widget.onCancel?.call(); + Navigator.of(context).pop(); + }, + ), + ...widget.actions + ], + ); + } @override Widget build(BuildContext context) => LayoutBuilder(builder: ((context, constraints) { - final l10n = AppLocalizations.of(context)!; - if (constraints.maxWidth < 540) { - // Fullscreen - return Scaffold( - appBar: AppBar( - title: widget.title, - actions: widget.actions, - leading: IconButton( - icon: const Icon(Icons.close), - onPressed: widget.allowCancel - ? () { - widget.onCancel?.call(); - Navigator.of(context).pop(); - } - : null), - ), - body: SingleChildScrollView( - child: SafeArea( - child: Container(key: _childKey, child: widget.child)), - ), - ); - } else { - // Dialog - final cancelText = widget.onCancel == null && widget.actions.isEmpty - ? l10n.s_close - : l10n.s_cancel; - return AlertDialog( - title: widget.title, - titlePadding: const EdgeInsets.only(top: 24, left: 18, right: 18), - scrollable: true, - contentPadding: const EdgeInsets.symmetric(vertical: 8), - content: SizedBox( - width: 380, - child: Container(key: _childKey, child: widget.child), - ), - actions: [ - TextButton( - child: Text(cancelText), - onPressed: () { - widget.onCancel?.call(); - Navigator.of(context).pop(); - }, - ), - ...widget.actions - ], - ); - } + // This keeps the focus in the dialog, even if the underlying page changes. + return FocusScope( + node: _focus, + autofocus: true, + onFocusChange: (focused) { + if (!focused) { + _focus.requestFocus(); + } + }, + child: constraints.maxWidth < 540 + ? _buildFullscreen(context) + : _buildDialog(context), + ); })); }