Skip to content

Commit fd6997f

Browse files
authored
Add Dialog.fullscreen and example (#112261)
1 parent c66049f commit fd6997f

File tree

6 files changed

+286
-20
lines changed

6 files changed

+286
-20
lines changed

dev/tools/gen_defaults/bin/gen_defaults.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ Future<void> main(List<String> args) async {
120120
ButtonTemplate('md.comp.text-button', 'TextButton', '$materialLib/text_button.dart', tokens).updateFile();
121121
CardTemplate('Card', '$materialLib/card.dart', tokens).updateFile();
122122
CheckboxTemplate('Checkbox', '$materialLib/checkbox.dart', tokens).updateFile();
123+
DialogFullscreenTemplate('DialogFullscreen', '$materialLib/dialog.dart', tokens).updateFile();
123124
DialogTemplate('Dialog', '$materialLib/dialog.dart', tokens).updateFile();
124125
FABTemplate('FAB', '$materialLib/floating_action_button.dart', tokens).updateFile();
125126
FilterChipTemplate('ChoiceChip', '$materialLib/choice_chip.dart', tokens).updateFile();

dev/tools/gen_defaults/lib/dialog_template.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,19 @@ class _${blockName}DefaultsM3 extends DialogTheme {
4747
}
4848
''';
4949
}
50+
51+
class DialogFullscreenTemplate extends TokenTemplate {
52+
const DialogFullscreenTemplate(super.blockName, super.fileName, super.tokens);
53+
54+
@override
55+
String generate() => '''
56+
class _${blockName}DefaultsM3 extends DialogTheme {
57+
const _${blockName}DefaultsM3(this.context);
58+
59+
final BuildContext context;
60+
61+
@override
62+
Color? get backgroundColor => ${componentColor("md.comp.full-screen-dialog.container")};
63+
}
64+
''';
65+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
/// Flutter code sample for [Dialog].
6+
7+
import 'package:flutter/material.dart';
8+
9+
void main() => runApp(const MyApp());
10+
11+
class MyApp extends StatelessWidget {
12+
const MyApp({super.key});
13+
14+
@override
15+
Widget build(BuildContext context) {
16+
return MaterialApp(
17+
home: Scaffold(
18+
appBar: AppBar(title: const Text('Dialog Sample')),
19+
body: const Center(
20+
child: DialogExample(),
21+
),
22+
),
23+
);
24+
}
25+
}
26+
27+
class DialogExample extends StatelessWidget {
28+
const DialogExample({super.key});
29+
30+
@override
31+
Widget build(BuildContext context) {
32+
return Column(
33+
mainAxisAlignment: MainAxisAlignment.center,
34+
children: <Widget>[
35+
TextButton(
36+
onPressed: () => showDialog<String>(
37+
context: context,
38+
builder: (BuildContext context) => Dialog(
39+
child: Padding(
40+
padding: const EdgeInsets.all(8.0),
41+
child: Column(
42+
mainAxisSize: MainAxisSize.min,
43+
mainAxisAlignment: MainAxisAlignment.center,
44+
children: <Widget>[
45+
const Text('This is a typical dialog.'),
46+
const SizedBox(height: 15),
47+
TextButton(
48+
onPressed: () {
49+
Navigator.pop(context);
50+
},
51+
child: const Text('Close'),
52+
),
53+
],
54+
),
55+
),
56+
),
57+
),
58+
child: const Text('Show Dialog'),
59+
),
60+
const SizedBox(height: 10),
61+
TextButton(
62+
onPressed: () => showDialog<String>(
63+
context: context,
64+
builder: (BuildContext context) => Dialog.fullscreen(
65+
child: Column(
66+
mainAxisSize: MainAxisSize.min,
67+
mainAxisAlignment: MainAxisAlignment.center,
68+
children: <Widget>[
69+
const Text('This is a fullscreen dialog.'),
70+
const SizedBox(height: 15),
71+
TextButton(
72+
onPressed: () {
73+
Navigator.pop(context);
74+
},
75+
child: const Text('Close'),
76+
),
77+
],
78+
),
79+
),
80+
),
81+
child: const Text('Show Fullscreen Dialog'),
82+
),
83+
],
84+
);
85+
}
86+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
import 'package:flutter_api_samples/material/dialog/dialog.0.dart' as example;
7+
import 'package:flutter_test/flutter_test.dart';
8+
9+
void main() {
10+
testWidgets('Show Dialog', (WidgetTester tester) async {
11+
const String dialogText = 'This is a typical dialog.';
12+
13+
await tester.pumpWidget(
14+
const MaterialApp(
15+
home: Scaffold(
16+
body: example.MyApp(),
17+
),
18+
),
19+
);
20+
21+
expect(find.text(dialogText), findsNothing);
22+
23+
await tester.tap(find.text('Show Dialog'));
24+
await tester.pumpAndSettle();
25+
expect(find.text(dialogText), findsOneWidget);
26+
27+
await tester.tap(find.text('Close'));
28+
await tester.pumpAndSettle();
29+
expect(find.text(dialogText), findsNothing);
30+
});
31+
32+
testWidgets('Show Dialog.fullscreen', (WidgetTester tester) async {
33+
const String dialogText = 'This is a fullscreen dialog.';
34+
35+
await tester.pumpWidget(
36+
const MaterialApp(
37+
home: Scaffold(
38+
body: example.MyApp(),
39+
),
40+
),
41+
);
42+
43+
expect(find.text(dialogText), findsNothing);
44+
45+
await tester.tap(find.text('Show Fullscreen Dialog'));
46+
await tester.pumpAndSettle();
47+
expect(find.text(dialogText), findsOneWidget);
48+
49+
await tester.tap(find.text('Close'));
50+
await tester.pumpAndSettle();
51+
expect(find.text(dialogText), findsNothing);
52+
});
53+
}

packages/flutter/lib/src/material/dialog.dart

Lines changed: 82 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ const EdgeInsets _defaultInsetPadding = EdgeInsets.symmetric(horizontal: 40.0, v
3131
/// or [SimpleDialog], which implement specific kinds of Material Design
3232
/// dialogs.
3333
///
34+
/// {@tool dartpad}
35+
/// This sample shows the creation of [Dialog] and [Dialog.fullscreen] widgets.
36+
///
37+
/// ** See code in examples/api/lib/material/dialog/dialog.0.dart **
38+
/// {@end-tool}
39+
///
3440
/// See also:
3541
///
3642
/// * [AlertDialog], for dialogs that have a message and some buttons.
@@ -55,7 +61,26 @@ class Dialog extends StatelessWidget {
5561
this.alignment,
5662
this.child,
5763
}) : assert(clipBehavior != null),
58-
assert(elevation == null || elevation >= 0.0);
64+
assert(elevation == null || elevation >= 0.0),
65+
_fullscreen = false;
66+
67+
/// Creates a fullscreen dialog.
68+
///
69+
/// Typically used in conjunction with [showDialog].
70+
const Dialog.fullscreen({
71+
super.key,
72+
this.backgroundColor,
73+
this.insetAnimationDuration = Duration.zero,
74+
this.insetAnimationCurve = Curves.decelerate,
75+
this.child,
76+
}) : elevation = 0,
77+
shadowColor = null,
78+
surfaceTintColor = null,
79+
insetPadding = EdgeInsets.zero,
80+
clipBehavior = Clip.none,
81+
shape = null,
82+
alignment = null,
83+
_fullscreen = true;
5984

6085
/// {@template flutter.material.dialog.backgroundColor}
6186
/// The background color of the surface of this [Dialog].
@@ -130,7 +155,8 @@ class Dialog extends StatelessWidget {
130155
/// The duration of the animation to show when the system keyboard intrudes
131156
/// into the space that the dialog is placed in.
132157
///
133-
/// Defaults to 100 milliseconds.
158+
/// Defaults to 100 milliseconds when [Dialog] is used, and [Duration.zero]
159+
/// when [Dialog.fullscreen] is used.
134160
/// {@endtemplate}
135161
final Duration insetAnimationDuration;
136162

@@ -184,13 +210,44 @@ class Dialog extends StatelessWidget {
184210
/// {@macro flutter.widgets.ProxyWidget.child}
185211
final Widget? child;
186212

213+
/// This value is used to determine if this is a fullscreen dialog.
214+
final bool _fullscreen;
215+
187216
@override
188217
Widget build(BuildContext context) {
189218
final ThemeData theme = Theme.of(context);
190219
final DialogTheme dialogTheme = DialogTheme.of(context);
191-
final DialogTheme defaults = theme.useMaterial3 ? _DialogDefaultsM3(context) : _DialogDefaultsM2(context);
192-
193220
final EdgeInsets effectivePadding = MediaQuery.of(context).viewInsets + (insetPadding ?? EdgeInsets.zero);
221+
final DialogTheme defaults = theme.useMaterial3
222+
? (_fullscreen ? _DialogFullscreenDefaultsM3(context) : _DialogDefaultsM3(context))
223+
: _DialogDefaultsM2(context);
224+
225+
Widget dialogChild;
226+
227+
if (_fullscreen) {
228+
dialogChild = Material(
229+
color: backgroundColor ?? dialogTheme.backgroundColor ?? defaults.backgroundColor,
230+
child: child,
231+
);
232+
} else {
233+
dialogChild = Align(
234+
alignment: alignment ?? dialogTheme.alignment ?? defaults.alignment!,
235+
child: ConstrainedBox(
236+
constraints: const BoxConstraints(minWidth: 280.0),
237+
child: Material(
238+
color: backgroundColor ?? dialogTheme.backgroundColor ?? Theme.of(context).dialogBackgroundColor,
239+
elevation: elevation ?? dialogTheme.elevation ?? defaults.elevation!,
240+
shadowColor: shadowColor ?? dialogTheme.shadowColor ?? defaults.shadowColor,
241+
surfaceTintColor: surfaceTintColor ?? dialogTheme.surfaceTintColor ?? defaults.surfaceTintColor,
242+
shape: shape ?? dialogTheme.shape ?? defaults.shape!,
243+
type: MaterialType.card,
244+
clipBehavior: clipBehavior,
245+
child: child,
246+
),
247+
),
248+
);
249+
}
250+
194251
return AnimatedPadding(
195252
padding: effectivePadding,
196253
duration: insetAnimationDuration,
@@ -201,22 +258,7 @@ class Dialog extends StatelessWidget {
201258
removeRight: true,
202259
removeBottom: true,
203260
context: context,
204-
child: Align(
205-
alignment: alignment ?? dialogTheme.alignment ?? defaults.alignment!,
206-
child: ConstrainedBox(
207-
constraints: const BoxConstraints(minWidth: 280.0),
208-
child: Material(
209-
color: backgroundColor ?? dialogTheme.backgroundColor ?? Theme.of(context).dialogBackgroundColor,
210-
elevation: elevation ?? dialogTheme.elevation ?? defaults.elevation!,
211-
shadowColor: shadowColor ?? dialogTheme.shadowColor ?? defaults.shadowColor,
212-
surfaceTintColor: surfaceTintColor ?? dialogTheme.surfaceTintColor ?? defaults.surfaceTintColor,
213-
shape: shape ?? dialogTheme.shape ?? defaults.shape!,
214-
type: MaterialType.card,
215-
clipBehavior: clipBehavior,
216-
child: child,
217-
),
218-
),
219-
),
261+
child: dialogChild,
220262
),
221263
);
222264
}
@@ -1426,3 +1468,23 @@ class _DialogDefaultsM3 extends DialogTheme {
14261468
}
14271469

14281470
// END GENERATED TOKEN PROPERTIES - Dialog
1471+
1472+
// BEGIN GENERATED TOKEN PROPERTIES - DialogFullscreen
1473+
1474+
// Do not edit by hand. The code between the "BEGIN GENERATED" and
1475+
// "END GENERATED" comments are generated from data in the Material
1476+
// Design token database by the script:
1477+
// dev/tools/gen_defaults/bin/gen_defaults.dart.
1478+
1479+
// Token database version: v0_132
1480+
1481+
class _DialogFullscreenDefaultsM3 extends DialogTheme {
1482+
const _DialogFullscreenDefaultsM3(this.context);
1483+
1484+
final BuildContext context;
1485+
1486+
@override
1487+
Color? get backgroundColor => Theme.of(context).colorScheme.surface;
1488+
}
1489+
1490+
// END GENERATED TOKEN PROPERTIES - DialogFullscreen

packages/flutter/test/material/dialog_test.dart

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:ui';
66

77
import 'package:flutter/material.dart';
88
import 'package:flutter/rendering.dart';
9+
import 'package:flutter/services.dart';
910
import 'package:flutter_test/flutter_test.dart';
1011

1112
import '../widgets/semantics_tester.dart';
@@ -138,6 +139,53 @@ void main() {
138139
expect(material3Widget.elevation, 6.0);
139140
});
140141

142+
testWidgets('Dialog.fullscreen Defaults', (WidgetTester tester) async {
143+
const String dialogTextM2 = 'Fullscreen Dialog - M2';
144+
const String dialogTextM3 = 'Fullscreen Dialog - M3';
145+
146+
await tester.pumpWidget(_buildAppWithDialog(
147+
theme: material2Theme,
148+
const Dialog.fullscreen(
149+
child: Text(dialogTextM2),
150+
),
151+
));
152+
153+
await tester.tap(find.text('X'));
154+
await tester.pumpAndSettle();
155+
156+
expect(find.text(dialogTextM2), findsOneWidget);
157+
158+
Material materialWidget = _getMaterialFromDialog(tester);
159+
expect(materialWidget.color, Colors.grey[800]);
160+
161+
// Try to dismiss the fullscreen dialog with the escape key.
162+
await tester.sendKeyEvent(LogicalKeyboardKey.escape);
163+
await tester.pumpAndSettle();
164+
165+
expect(find.text(dialogTextM2), findsNothing);
166+
167+
await tester.pumpWidget(_buildAppWithDialog(
168+
theme: material3Theme,
169+
const Dialog.fullscreen(
170+
child: Text(dialogTextM3),
171+
),
172+
));
173+
174+
await tester.tap(find.text('X'));
175+
await tester.pumpAndSettle();
176+
177+
expect(find.text(dialogTextM3), findsOneWidget);
178+
179+
materialWidget = _getMaterialFromDialog(tester);
180+
expect(materialWidget.color, material3Theme.colorScheme.surface);
181+
182+
// Try to dismiss the fullscreen dialog with the escape key.
183+
await tester.sendKeyEvent(LogicalKeyboardKey.escape);
184+
await tester.pumpAndSettle();
185+
186+
expect(find.text(dialogTextM3), findsNothing);
187+
});
188+
141189
testWidgets('Custom dialog elevation', (WidgetTester tester) async {
142190
const double customElevation = 12.0;
143191
const Color shadowColor = Color(0xFF000001);

0 commit comments

Comments
 (0)