diff --git a/examples/api/lib/widgets/routes/popup_route.0.dart b/examples/api/lib/widgets/routes/popup_route.0.dart new file mode 100644 index 0000000000..5a6eaaee05 --- /dev/null +++ b/examples/api/lib/widgets/routes/popup_route.0.dart @@ -0,0 +1,84 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flutter code sample for PopupRoute + +import 'package:flutter/material.dart'; + +void main() => runApp(const PopupRouteApp()); + +class PopupRouteApp extends StatelessWidget { + const PopupRouteApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: PopupRouteExample(), + ); + } +} + +class PopupRouteExample extends StatelessWidget { + const PopupRouteExample({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: OutlinedButton( + onPressed: () { + /// This shows a dismissible dialog. + Navigator.of(context).push(DismissibleDialog()); + }, + child: const Text('Open DismissibleDialog'), + ), + ), + ); + } +} + +class DismissibleDialog extends PopupRoute { + @override + Color? get barrierColor => Colors.black.withAlpha(0x50); + + /// This allows the popup to be dismissed by tapping the scrim or by + /// pressing escape key on the keyboard. + @override + bool get barrierDismissible => true; + + @override + String? get barrierLabel => 'Dismissible Dialog'; + + @override + Duration get transitionDuration => const Duration(milliseconds: 300); + + @override + Widget buildPage(BuildContext context, Animation animation, Animation secondaryAnimation) { + return Center( + /// Provide DefaultTextStyle to ensure that the dialog's text style matches + /// the rest of the text in the app. + child: DefaultTextStyle( + style: Theme.of(context).textTheme.bodyMedium!, + /// `UnconstrainedBox` is used to make the dialog size itself + /// to fit to the size of the content. + child: UnconstrainedBox( + child: Container( + padding: const EdgeInsets.all(20.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.white, + ), + child: Column( + children: [ + Text('Dismissible Dialog', style: Theme.of(context).textTheme.headlineSmall), + const SizedBox(height: 20), + const Text('Tap in the scrim or press escape key to dismiss.'), + ], + ), + ), + ), + ), + ); + } +} diff --git a/examples/api/lib/widgets/routes/show_general_dialog.0.dart b/examples/api/lib/widgets/routes/show_general_dialog.0.dart index a7287c15ea..aef240277a 100644 --- a/examples/api/lib/widgets/routes/show_general_dialog.0.dart +++ b/examples/api/lib/widgets/routes/show_general_dialog.0.dart @@ -6,25 +6,22 @@ import 'package:flutter/material.dart'; -void main() => runApp(const MyApp()); +void main() => runApp(const GeneralDialogApp()); -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - static const String _title = 'Flutter Code Sample'; +class GeneralDialogApp extends StatelessWidget { + const GeneralDialogApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( restorationScopeId: 'app', - title: _title, - home: MyStatelessWidget(), + home: GeneralDialogExample(), ); } } -class MyStatelessWidget extends StatelessWidget { - const MyStatelessWidget({super.key}); +class GeneralDialogExample extends StatelessWidget { + const GeneralDialogExample({super.key}); @override Widget build(BuildContext context) { @@ -32,6 +29,7 @@ class MyStatelessWidget extends StatelessWidget { body: Center( child: OutlinedButton( onPressed: () { + /// This shows an alert dialog. Navigator.of(context).restorablePush(_dialogBuilder); }, child: const Text('Open Dialog'), diff --git a/examples/api/test/widgets/routes/popup_route.0_test.dart b/examples/api/test/widgets/routes/popup_route.0_test.dart new file mode 100644 index 0000000000..9968cea886 --- /dev/null +++ b/examples/api/test/widgets/routes/popup_route.0_test.dart @@ -0,0 +1,40 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_api_samples/widgets/routes/popup_route.0.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Dismiss dialog with tap on the scrim and escape key', (WidgetTester tester) async { + const String dialogText = 'Tap in the scrim or press escape key to dismiss.'; + + await tester.pumpWidget( + const example.PopupRouteApp(), + ); + + expect(find.text(dialogText), findsNothing); + + // Tap on the button to show the dialog. + await tester.tap(find.byType(OutlinedButton)); + await tester.pumpAndSettle(); + expect(find.text(dialogText), findsOneWidget); + + // Try to dismiss the dialog with a tap on the scrim. + await tester.tapAt(const Offset(10.0, 10.0)); + await tester.pumpAndSettle(); + expect(find.text(dialogText), findsNothing); + + // Open the dialog again. + await tester.tap(find.byType(OutlinedButton)); + await tester.pumpAndSettle(); + expect(find.text(dialogText), findsOneWidget); + + // Try to dismiss the dialog with the escape key. + await tester.sendKeyEvent(LogicalKeyboardKey.escape); + await tester.pumpAndSettle(); + expect(find.text(dialogText), findsNothing); + }); +} diff --git a/examples/api/test/widgets/routes/show_general_dialog.0_test.dart b/examples/api/test/widgets/routes/show_general_dialog.0_test.dart new file mode 100644 index 0000000000..f6c859977a --- /dev/null +++ b/examples/api/test/widgets/routes/show_general_dialog.0_test.dart @@ -0,0 +1,29 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_api_samples/widgets/routes/show_general_dialog.0.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Open and dismiss general dialog', (WidgetTester tester) async { + const String dialogText = 'Alert!'; + + await tester.pumpWidget( + const example.GeneralDialogApp(), + ); + + expect(find.text(dialogText), findsNothing); + + // Tap on the button to show the dialog. + await tester.tap(find.byType(OutlinedButton)); + await tester.pumpAndSettle(); + expect(find.text(dialogText), findsOneWidget); + + // Try to dismiss the dialog with a tap on the scrim. + await tester.tapAt(const Offset(10.0, 10.0)); + await tester.pumpAndSettle(); + expect(find.text(dialogText), findsNothing); + }); +} diff --git a/packages/flutter/lib/src/widgets/routes.dart b/packages/flutter/lib/src/widgets/routes.dart index 015e68e53c..45c26f20bc 100644 --- a/packages/flutter/lib/src/widgets/routes.dart +++ b/packages/flutter/lib/src/widgets/routes.dart @@ -1210,8 +1210,10 @@ abstract class ModalRoute extends TransitionRoute with LocalHistoryRoute extends TransitionRoute with LocalHistoryRoute extends TransitionRoute with LocalHistoryRoute extends ModalRoute { /// Initializes the [PopupRoute]. PopupRoute({