From 3f179d80f62b228fe604c5e768ae368988bf122f Mon Sep 17 00:00:00 2001 From: MH Johnson Date: Fri, 26 Oct 2018 08:35:07 -0400 Subject: [PATCH] [Material] Add custom shape parameter for Dialogs. (#23443) * [Material] Add custom shape parameter for Dialogs. * [Material] Add custom shape parameter for Dialogs. * Address Hans' first round comments. * Address Hans' first round comments. * Address Hans' Second round comments. --- packages/flutter/lib/src/material/dialog.dart | 27 ++- .../flutter/test/material/dialog_test.dart | 171 +++++++++++------- 2 files changed, 128 insertions(+), 70 deletions(-) diff --git a/packages/flutter/lib/src/material/dialog.dart b/packages/flutter/lib/src/material/dialog.dart index 4ed65a5f5d..70b1f66b43 100644 --- a/packages/flutter/lib/src/material/dialog.dart +++ b/packages/flutter/lib/src/material/dialog.dart @@ -42,6 +42,7 @@ class Dialog extends StatelessWidget { this.child, this.insetAnimationDuration = const Duration(milliseconds: 100), this.insetAnimationCurve = Curves.decelerate, + this.shape, }) : super(key: key); /// The widget below this widget in the tree. @@ -61,10 +62,23 @@ class Dialog extends StatelessWidget { /// Defaults to [Curves.fastOutSlowIn]. final Curve insetAnimationCurve; + /// {@template flutter.material.dialog.shape} + /// The shape of this dialog's border. + /// + /// Defines the dialog's [Material.shape]. + /// + /// The default shape is a [RoundedRectangleBorder] with a radius of 2.0. + /// {@endtemplate} + final ShapeBorder shape; + Color _getColor(BuildContext context) { return Theme.of(context).dialogBackgroundColor; } + // TODO(johnsonmh): Update default dialog border radius to 4.0 to match material spec. + static const RoundedRectangleBorder _defaultDialogShape = + RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2.0))); + @override Widget build(BuildContext context) { return AnimatedPadding( @@ -85,6 +99,7 @@ class Dialog extends StatelessWidget { color: _getColor(context), type: MaterialType.card, child: child, + shape: shape ?? _defaultDialogShape, ), ), ), @@ -172,6 +187,7 @@ class AlertDialog extends StatelessWidget { this.contentPadding = const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0), this.actions, this.semanticLabel, + this.shape, }) : assert(contentPadding != null), super(key: key); @@ -235,6 +251,9 @@ class AlertDialog extends StatelessWidget { /// value is used. final String semanticLabel; + /// {@macro flutter.material.dialog.shape} + final ShapeBorder shape; + @override Widget build(BuildContext context) { assert(debugCheckHasMaterialLocalizations(context)); @@ -295,7 +314,7 @@ class AlertDialog extends StatelessWidget { child: dialogChild ); - return Dialog(child: dialogChild); + return Dialog(child: dialogChild, shape: shape); } } @@ -440,6 +459,7 @@ class SimpleDialog extends StatelessWidget { this.children, this.contentPadding = const EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 16.0), this.semanticLabel, + this.shape, }) : assert(titlePadding != null), assert(contentPadding != null), super(key: key); @@ -494,6 +514,9 @@ class SimpleDialog extends StatelessWidget { /// value is used. final String semanticLabel; + /// {@macro flutter.material.dialog.shape} + final ShapeBorder shape; + @override Widget build(BuildContext context) { assert(debugCheckHasMaterialLocalizations(context)); @@ -546,7 +569,7 @@ class SimpleDialog extends StatelessWidget { label: label, child: dialogChild, ); - return Dialog(child: dialogChild); + return Dialog(child: dialogChild, shape: shape); } } diff --git a/packages/flutter/test/material/dialog_test.dart b/packages/flutter/test/material/dialog_test.dart index 10ed47be7b..92158cf420 100644 --- a/packages/flutter/test/material/dialog_test.dart +++ b/packages/flutter/test/material/dialog_test.dart @@ -10,47 +10,52 @@ import 'package:matcher/matcher.dart'; import '../widgets/semantics_tester.dart'; +MaterialApp _appWithAlertDialog(WidgetTester tester, AlertDialog dialog, {ThemeData theme}) { + return MaterialApp( + theme: theme, + home: Material( + child: Builder( + builder: (BuildContext context) { + return Center( + child: RaisedButton( + child: const Text('X'), + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return dialog; + }, + ); + } + ) + ); + } + ) + ), + ); +} + +const ShapeBorder _defaultDialogShape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2.0))); + void main() { testWidgets('Dialog is scrollable', (WidgetTester tester) async { bool didPressOk = false; - - await tester.pumpWidget( - MaterialApp( - home: Material( - child: Builder( - builder: (BuildContext context) { - return Center( - child: RaisedButton( - child: const Text('X'), - onPressed: () { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - content: Container( - height: 5000.0, - width: 300.0, - color: Colors.green[500], - ), - actions: [ - FlatButton( - onPressed: () { - didPressOk = true; - }, - child: const Text('OK') - ) - ], - ); - }, - ); - } - ) - ); - } - ) + final AlertDialog dialog = AlertDialog( + content: Container( + height: 5000.0, + width: 300.0, + color: Colors.green[500], + ), + actions: [ + FlatButton( + onPressed: () { + didPressOk = true; + }, + child: const Text('OK') ) - ) + ], ); + await tester.pumpWidget(_appWithAlertDialog(tester, dialog)); await tester.tap(find.text('X')); await tester.pump(); // start animation @@ -62,46 +67,76 @@ void main() { }); testWidgets('Dialog background color', (WidgetTester tester) async { - - await tester.pumpWidget( - MaterialApp( - theme: ThemeData(brightness: Brightness.dark), - home: Material( - child: Builder( - builder: (BuildContext context) { - return Center( - child: RaisedButton( - child: const Text('X'), - onPressed: () { - showDialog( - context: context, - builder: (BuildContext context) { - return const AlertDialog( - title: Text('Title'), - content: Text('Y'), - actions: [ ], - ); - }, - ); - }, - ), - ); - }, - ), - ), - ), + const AlertDialog dialog = AlertDialog( + title: Text('Title'), + content: Text('Y'), + actions: [ ], ); + await tester.pumpWidget(_appWithAlertDialog(tester, dialog, theme: ThemeData(brightness: Brightness.dark))); await tester.tap(find.text('X')); await tester.pump(); // start animation await tester.pump(const Duration(seconds: 1)); - final StatefulElement widget = tester.element(find.byType(Material).last); + final StatefulElement widget = tester.element( + find.descendant(of: find.byType(AlertDialog), matching: find.byType(Material))); final Material materialWidget = widget.state.widget; - //first and second expect check that the material is the dialog's one - expect(materialWidget.type, MaterialType.card); - expect(materialWidget.elevation, 24); expect(materialWidget.color, Colors.grey[800]); + expect(materialWidget.shape, _defaultDialogShape); + }); + + testWidgets('Custom dialog shape', (WidgetTester tester) async { + const RoundedRectangleBorder customBorder = + RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0))); + const AlertDialog dialog = AlertDialog( + actions: [ ], + shape: customBorder, + ); + await tester.pumpWidget(_appWithAlertDialog(tester, dialog)); + + await tester.tap(find.text('X')); + await tester.pump(); // start animation + await tester.pump(const Duration(seconds: 1)); + + final StatefulElement widget = tester.element( + find.descendant(of: find.byType(AlertDialog), matching: find.byType(Material))); + final Material materialWidget = widget.state.widget; + expect(materialWidget.shape, customBorder); + }); + + testWidgets('Null dialog shape', (WidgetTester tester) async { + const AlertDialog dialog = AlertDialog( + actions: [ ], + shape: null, + ); + await tester.pumpWidget(_appWithAlertDialog(tester, dialog)); + + await tester.tap(find.text('X')); + await tester.pump(); // start animation + await tester.pump(const Duration(seconds: 1)); + + final StatefulElement widget = tester.element( + find.descendant(of: find.byType(AlertDialog), matching: find.byType(Material))); + final Material materialWidget = widget.state.widget; + expect(materialWidget.shape, _defaultDialogShape); + }); + + testWidgets('Rectangular dialog shape', (WidgetTester tester) async { + const ShapeBorder customBorder = Border(); + const AlertDialog dialog = AlertDialog( + actions: [ ], + shape: customBorder, + ); + await tester.pumpWidget(_appWithAlertDialog(tester, dialog)); + + await tester.tap(find.text('X')); + await tester.pump(); // start animation + await tester.pump(const Duration(seconds: 1)); + + final StatefulElement widget = tester.element( + find.descendant(of: find.byType(AlertDialog), matching: find.byType(Material))); + final Material materialWidget = widget.state.widget; + expect(materialWidget.shape, customBorder); }); testWidgets('Simple dialog control test', (WidgetTester tester) async {