Add `Dialog.fullscreen` and example (#112261)
diff --git a/dev/tools/gen_defaults/bin/gen_defaults.dart b/dev/tools/gen_defaults/bin/gen_defaults.dart
index ef1f32a..9c24b27 100644
--- a/dev/tools/gen_defaults/bin/gen_defaults.dart
+++ b/dev/tools/gen_defaults/bin/gen_defaults.dart
@@ -120,6 +120,7 @@
ButtonTemplate('md.comp.text-button', 'TextButton', '$materialLib/text_button.dart', tokens).updateFile();
CardTemplate('Card', '$materialLib/card.dart', tokens).updateFile();
CheckboxTemplate('Checkbox', '$materialLib/checkbox.dart', tokens).updateFile();
+ DialogFullscreenTemplate('DialogFullscreen', '$materialLib/dialog.dart', tokens).updateFile();
DialogTemplate('Dialog', '$materialLib/dialog.dart', tokens).updateFile();
FABTemplate('FAB', '$materialLib/floating_action_button.dart', tokens).updateFile();
FilterChipTemplate('ChoiceChip', '$materialLib/choice_chip.dart', tokens).updateFile();
diff --git a/dev/tools/gen_defaults/lib/dialog_template.dart b/dev/tools/gen_defaults/lib/dialog_template.dart
index 2f2ab65..5bb28a2 100644
--- a/dev/tools/gen_defaults/lib/dialog_template.dart
+++ b/dev/tools/gen_defaults/lib/dialog_template.dart
@@ -47,3 +47,19 @@
}
''';
}
+
+class DialogFullscreenTemplate extends TokenTemplate {
+ const DialogFullscreenTemplate(super.blockName, super.fileName, super.tokens);
+
+ @override
+ String generate() => '''
+class _${blockName}DefaultsM3 extends DialogTheme {
+ const _${blockName}DefaultsM3(this.context);
+
+ final BuildContext context;
+
+ @override
+ Color? get backgroundColor => ${componentColor("md.comp.full-screen-dialog.container")};
+}
+''';
+}
diff --git a/examples/api/lib/material/dialog/dialog.0.dart b/examples/api/lib/material/dialog/dialog.0.dart
new file mode 100644
index 0000000..be8dbd5
--- /dev/null
+++ b/examples/api/lib/material/dialog/dialog.0.dart
@@ -0,0 +1,86 @@
+// 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 [Dialog].
+
+import 'package:flutter/material.dart';
+
+void main() => runApp(const MyApp());
+
+class MyApp extends StatelessWidget {
+ const MyApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ home: Scaffold(
+ appBar: AppBar(title: const Text('Dialog Sample')),
+ body: const Center(
+ child: DialogExample(),
+ ),
+ ),
+ );
+ }
+}
+
+class DialogExample extends StatelessWidget {
+ const DialogExample({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ TextButton(
+ onPressed: () => showDialog<String>(
+ context: context,
+ builder: (BuildContext context) => Dialog(
+ child: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ const Text('This is a typical dialog.'),
+ const SizedBox(height: 15),
+ TextButton(
+ onPressed: () {
+ Navigator.pop(context);
+ },
+ child: const Text('Close'),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ child: const Text('Show Dialog'),
+ ),
+ const SizedBox(height: 10),
+ TextButton(
+ onPressed: () => showDialog<String>(
+ context: context,
+ builder: (BuildContext context) => Dialog.fullscreen(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ const Text('This is a fullscreen dialog.'),
+ const SizedBox(height: 15),
+ TextButton(
+ onPressed: () {
+ Navigator.pop(context);
+ },
+ child: const Text('Close'),
+ ),
+ ],
+ ),
+ ),
+ ),
+ child: const Text('Show Fullscreen Dialog'),
+ ),
+ ],
+ );
+ }
+}
diff --git a/examples/api/test/material/dialog/dialog.0_test.dart b/examples/api/test/material/dialog/dialog.0_test.dart
new file mode 100644
index 0000000..b6fb588
--- /dev/null
+++ b/examples/api/test/material/dialog/dialog.0_test.dart
@@ -0,0 +1,53 @@
+// 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/material/dialog/dialog.0.dart' as example;
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+ testWidgets('Show Dialog', (WidgetTester tester) async {
+ const String dialogText = 'This is a typical dialog.';
+
+ await tester.pumpWidget(
+ const MaterialApp(
+ home: Scaffold(
+ body: example.MyApp(),
+ ),
+ ),
+ );
+
+ expect(find.text(dialogText), findsNothing);
+
+ await tester.tap(find.text('Show Dialog'));
+ await tester.pumpAndSettle();
+ expect(find.text(dialogText), findsOneWidget);
+
+ await tester.tap(find.text('Close'));
+ await tester.pumpAndSettle();
+ expect(find.text(dialogText), findsNothing);
+ });
+
+ testWidgets('Show Dialog.fullscreen', (WidgetTester tester) async {
+ const String dialogText = 'This is a fullscreen dialog.';
+
+ await tester.pumpWidget(
+ const MaterialApp(
+ home: Scaffold(
+ body: example.MyApp(),
+ ),
+ ),
+ );
+
+ expect(find.text(dialogText), findsNothing);
+
+ await tester.tap(find.text('Show Fullscreen Dialog'));
+ await tester.pumpAndSettle();
+ expect(find.text(dialogText), findsOneWidget);
+
+ await tester.tap(find.text('Close'));
+ await tester.pumpAndSettle();
+ expect(find.text(dialogText), findsNothing);
+ });
+}
diff --git a/packages/flutter/lib/src/material/dialog.dart b/packages/flutter/lib/src/material/dialog.dart
index 7d125e4..5b69144 100644
--- a/packages/flutter/lib/src/material/dialog.dart
+++ b/packages/flutter/lib/src/material/dialog.dart
@@ -31,6 +31,12 @@
/// or [SimpleDialog], which implement specific kinds of Material Design
/// dialogs.
///
+/// {@tool dartpad}
+/// This sample shows the creation of [Dialog] and [Dialog.fullscreen] widgets.
+///
+/// ** See code in examples/api/lib/material/dialog/dialog.0.dart **
+/// {@end-tool}
+///
/// See also:
///
/// * [AlertDialog], for dialogs that have a message and some buttons.
@@ -55,7 +61,26 @@
this.alignment,
this.child,
}) : assert(clipBehavior != null),
- assert(elevation == null || elevation >= 0.0);
+ assert(elevation == null || elevation >= 0.0),
+ _fullscreen = false;
+
+ /// Creates a fullscreen dialog.
+ ///
+ /// Typically used in conjunction with [showDialog].
+ const Dialog.fullscreen({
+ super.key,
+ this.backgroundColor,
+ this.insetAnimationDuration = Duration.zero,
+ this.insetAnimationCurve = Curves.decelerate,
+ this.child,
+ }) : elevation = 0,
+ shadowColor = null,
+ surfaceTintColor = null,
+ insetPadding = EdgeInsets.zero,
+ clipBehavior = Clip.none,
+ shape = null,
+ alignment = null,
+ _fullscreen = true;
/// {@template flutter.material.dialog.backgroundColor}
/// The background color of the surface of this [Dialog].
@@ -130,7 +155,8 @@
/// The duration of the animation to show when the system keyboard intrudes
/// into the space that the dialog is placed in.
///
- /// Defaults to 100 milliseconds.
+ /// Defaults to 100 milliseconds when [Dialog] is used, and [Duration.zero]
+ /// when [Dialog.fullscreen] is used.
/// {@endtemplate}
final Duration insetAnimationDuration;
@@ -184,13 +210,44 @@
/// {@macro flutter.widgets.ProxyWidget.child}
final Widget? child;
+ /// This value is used to determine if this is a fullscreen dialog.
+ final bool _fullscreen;
+
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final DialogTheme dialogTheme = DialogTheme.of(context);
- final DialogTheme defaults = theme.useMaterial3 ? _DialogDefaultsM3(context) : _DialogDefaultsM2(context);
-
final EdgeInsets effectivePadding = MediaQuery.of(context).viewInsets + (insetPadding ?? EdgeInsets.zero);
+ final DialogTheme defaults = theme.useMaterial3
+ ? (_fullscreen ? _DialogFullscreenDefaultsM3(context) : _DialogDefaultsM3(context))
+ : _DialogDefaultsM2(context);
+
+ Widget dialogChild;
+
+ if (_fullscreen) {
+ dialogChild = Material(
+ color: backgroundColor ?? dialogTheme.backgroundColor ?? defaults.backgroundColor,
+ child: child,
+ );
+ } else {
+ dialogChild = Align(
+ alignment: alignment ?? dialogTheme.alignment ?? defaults.alignment!,
+ child: ConstrainedBox(
+ constraints: const BoxConstraints(minWidth: 280.0),
+ child: Material(
+ color: backgroundColor ?? dialogTheme.backgroundColor ?? Theme.of(context).dialogBackgroundColor,
+ elevation: elevation ?? dialogTheme.elevation ?? defaults.elevation!,
+ shadowColor: shadowColor ?? dialogTheme.shadowColor ?? defaults.shadowColor,
+ surfaceTintColor: surfaceTintColor ?? dialogTheme.surfaceTintColor ?? defaults.surfaceTintColor,
+ shape: shape ?? dialogTheme.shape ?? defaults.shape!,
+ type: MaterialType.card,
+ clipBehavior: clipBehavior,
+ child: child,
+ ),
+ ),
+ );
+ }
+
return AnimatedPadding(
padding: effectivePadding,
duration: insetAnimationDuration,
@@ -201,22 +258,7 @@
removeRight: true,
removeBottom: true,
context: context,
- child: Align(
- alignment: alignment ?? dialogTheme.alignment ?? defaults.alignment!,
- child: ConstrainedBox(
- constraints: const BoxConstraints(minWidth: 280.0),
- child: Material(
- color: backgroundColor ?? dialogTheme.backgroundColor ?? Theme.of(context).dialogBackgroundColor,
- elevation: elevation ?? dialogTheme.elevation ?? defaults.elevation!,
- shadowColor: shadowColor ?? dialogTheme.shadowColor ?? defaults.shadowColor,
- surfaceTintColor: surfaceTintColor ?? dialogTheme.surfaceTintColor ?? defaults.surfaceTintColor,
- shape: shape ?? dialogTheme.shape ?? defaults.shape!,
- type: MaterialType.card,
- clipBehavior: clipBehavior,
- child: child,
- ),
- ),
- ),
+ child: dialogChild,
),
);
}
@@ -1426,3 +1468,23 @@
}
// END GENERATED TOKEN PROPERTIES - Dialog
+
+// BEGIN GENERATED TOKEN PROPERTIES - DialogFullscreen
+
+// Do not edit by hand. The code between the "BEGIN GENERATED" and
+// "END GENERATED" comments are generated from data in the Material
+// Design token database by the script:
+// dev/tools/gen_defaults/bin/gen_defaults.dart.
+
+// Token database version: v0_132
+
+class _DialogFullscreenDefaultsM3 extends DialogTheme {
+ const _DialogFullscreenDefaultsM3(this.context);
+
+ final BuildContext context;
+
+ @override
+ Color? get backgroundColor => Theme.of(context).colorScheme.surface;
+}
+
+// END GENERATED TOKEN PROPERTIES - DialogFullscreen
diff --git a/packages/flutter/test/material/dialog_test.dart b/packages/flutter/test/material/dialog_test.dart
index 9a94021..091f64e 100644
--- a/packages/flutter/test/material/dialog_test.dart
+++ b/packages/flutter/test/material/dialog_test.dart
@@ -6,6 +6,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
+import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import '../widgets/semantics_tester.dart';
@@ -138,6 +139,53 @@
expect(material3Widget.elevation, 6.0);
});
+ testWidgets('Dialog.fullscreen Defaults', (WidgetTester tester) async {
+ const String dialogTextM2 = 'Fullscreen Dialog - M2';
+ const String dialogTextM3 = 'Fullscreen Dialog - M3';
+
+ await tester.pumpWidget(_buildAppWithDialog(
+ theme: material2Theme,
+ const Dialog.fullscreen(
+ child: Text(dialogTextM2),
+ ),
+ ));
+
+ await tester.tap(find.text('X'));
+ await tester.pumpAndSettle();
+
+ expect(find.text(dialogTextM2), findsOneWidget);
+
+ Material materialWidget = _getMaterialFromDialog(tester);
+ expect(materialWidget.color, Colors.grey[800]);
+
+ // Try to dismiss the fullscreen dialog with the escape key.
+ await tester.sendKeyEvent(LogicalKeyboardKey.escape);
+ await tester.pumpAndSettle();
+
+ expect(find.text(dialogTextM2), findsNothing);
+
+ await tester.pumpWidget(_buildAppWithDialog(
+ theme: material3Theme,
+ const Dialog.fullscreen(
+ child: Text(dialogTextM3),
+ ),
+ ));
+
+ await tester.tap(find.text('X'));
+ await tester.pumpAndSettle();
+
+ expect(find.text(dialogTextM3), findsOneWidget);
+
+ materialWidget = _getMaterialFromDialog(tester);
+ expect(materialWidget.color, material3Theme.colorScheme.surface);
+
+ // Try to dismiss the fullscreen dialog with the escape key.
+ await tester.sendKeyEvent(LogicalKeyboardKey.escape);
+ await tester.pumpAndSettle();
+
+ expect(find.text(dialogTextM3), findsNothing);
+ });
+
testWidgets('Custom dialog elevation', (WidgetTester tester) async {
const double customElevation = 12.0;
const Color shadowColor = Color(0xFF000001);