diff --git a/packages/flutter/lib/src/material/dialog.dart b/packages/flutter/lib/src/material/dialog.dart index 5ffa1b6770..4c3ecc5d2d 100644 --- a/packages/flutter/lib/src/material/dialog.dart +++ b/packages/flutter/lib/src/material/dialog.dart @@ -26,6 +26,8 @@ import 'theme_data.dart'; // enum Department { treasury, state } // BuildContext context; +const EdgeInsets _defaultInsetPadding = EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0); + /// A material design dialog. /// /// This dialog widget does not have any opinion about the contents of the @@ -49,9 +51,12 @@ class Dialog extends StatelessWidget { this.elevation, this.insetAnimationDuration = const Duration(milliseconds: 100), this.insetAnimationCurve = Curves.decelerate, + this.insetPadding = _defaultInsetPadding, + this.clipBehavior = Clip.none, this.shape, this.child, - }) : super(key: key); + }) : assert(clipBehavior != null), + super(key: key); /// {@template flutter.material.dialog.backgroundColor} /// The background color of the surface of this [Dialog]. @@ -87,6 +92,26 @@ class Dialog extends StatelessWidget { /// {@endtemplate} final Curve insetAnimationCurve; + /// {@template flutter.material.dialog.insetPadding} + /// The amount of padding added to [MediaQueryData.viewInsets] on the outside + /// of the dialog. This defines the minimum space between the screen's edges + /// and the dialog. + /// + /// Defaults to `EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0)`. + /// {@endtemplate} + final EdgeInsets insetPadding; + + /// {@template flutter.material.dialog.clipBehavior} + /// Controls how the contents of the dialog are clipped (or not) to the given + /// [shape]. + /// + /// See the enum [Clip] for details of all possible options and their common + /// use cases. + /// + /// Defaults to [Clip.none], and must not be null. + /// {@endtemplate} + final Clip clipBehavior; + /// {@template flutter.material.dialog.shape} /// The shape of this dialog's border. /// @@ -109,8 +134,9 @@ class Dialog extends StatelessWidget { @override Widget build(BuildContext context) { final DialogTheme dialogTheme = DialogTheme.of(context); + final EdgeInsets effectivePadding = MediaQuery.of(context).viewInsets + (insetPadding ?? const EdgeInsets.all(0.0)); return AnimatedPadding( - padding: MediaQuery.of(context).viewInsets + const EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0), + padding: effectivePadding, duration: insetAnimationDuration, curve: insetAnimationCurve, child: MediaQuery.removeViewInsets( @@ -127,6 +153,7 @@ class Dialog extends StatelessWidget { elevation: elevation ?? dialogTheme.elevation ?? _defaultElevation, shape: shape ?? dialogTheme.shape ?? _defaultDialogShape, type: MaterialType.card, + clipBehavior: clipBehavior, child: child, ), ), @@ -229,9 +256,12 @@ class AlertDialog extends StatelessWidget { this.backgroundColor, this.elevation, this.semanticLabel, + this.insetPadding = _defaultInsetPadding, + this.clipBehavior = Clip.none, this.shape, this.scrollable = false, }) : assert(contentPadding != null), + assert(clipBehavior != null), super(key: key); /// The (optional) title of the dialog is displayed in a large font at the top @@ -394,6 +424,12 @@ class AlertDialog extends StatelessWidget { /// value is used. final String semanticLabel; + /// {@macro flutter.material.dialog.insetPadding} + final EdgeInsets insetPadding; + + /// {@macro flutter.material.dialog.clipBehavior} + final Clip clipBehavior; + /// {@macro flutter.material.dialog.shape} final ShapeBorder shape; @@ -518,6 +554,8 @@ class AlertDialog extends StatelessWidget { return Dialog( backgroundColor: backgroundColor, elevation: elevation, + insetPadding: insetPadding, + clipBehavior: clipBehavior, shape: shape, child: dialogChild, ); diff --git a/packages/flutter/test/material/dialog_test.dart b/packages/flutter/test/material/dialog_test.dart index 061b6cc4e6..79f606e578 100644 --- a/packages/flutter/test/material/dialog_test.dart +++ b/packages/flutter/test/material/dialog_test.dart @@ -155,6 +155,20 @@ void main() { expect(content.text.style, contentTextStyle); }); + testWidgets('Custom clipBehavior', (WidgetTester tester) async { + const AlertDialog dialog = AlertDialog( + actions: [], + clipBehavior: Clip.antiAlias, + ); + await tester.pumpWidget(_buildAppWithDialog(dialog)); + + await tester.tap(find.text('X')); + await tester.pumpAndSettle(); + + final Material materialWidget = _getMaterialFromDialog(tester); + expect(materialWidget.clipBehavior, Clip.antiAlias); + }); + testWidgets('Custom dialog shape', (WidgetTester tester) async { const RoundedRectangleBorder customBorder = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0))); @@ -750,6 +764,47 @@ void main() { ); }); + testWidgets('Dialog insetPadding added to outside of dialog', (WidgetTester tester) async { + // The default testing screen (800, 600) + const Rect screenRect = Rect.fromLTRB(0.0, 0.0, 800.0, 600.0); + + // Test with no padding + await tester.pumpWidget( + const MediaQuery( + data: MediaQueryData( + viewInsets: EdgeInsets.all(0.0), + ), + child: Dialog( + child: Placeholder(), + insetPadding: null, + ), + ), + ); + await tester.pumpAndSettle(); + expect(tester.getRect(find.byType(Placeholder)), screenRect); + + // Test with an insetPadding + await tester.pumpWidget( + const MediaQuery( + data: MediaQueryData( + viewInsets: EdgeInsets.all(0.0), + ), + child: Dialog( + insetPadding: EdgeInsets.fromLTRB(10.0, 20.0, 30.0, 40.0), + child: Placeholder(), + ), + ), + ); + await tester.pumpAndSettle(); + expect(tester.getRect(find.byType(Placeholder)), + Rect.fromLTRB( + screenRect.left + 10.0, + screenRect.top + 20.0, + screenRect.right - 30.0, + screenRect.bottom - 40.0, + )); + }); + testWidgets('Dialog widget contains route semantics from title', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget(