Unverified Commit e4b574d3 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Cupertino Dialog Changes (#17676)

This replaces abandoned PR #14824 by @ekbiker, and gives it some love.
parent 89d99f6d
...@@ -15,17 +15,19 @@ class CupertinoDialogDemo extends StatefulWidget { ...@@ -15,17 +15,19 @@ class CupertinoDialogDemo extends StatefulWidget {
class _CupertinoDialogDemoState extends State<CupertinoDialogDemo> { class _CupertinoDialogDemoState extends State<CupertinoDialogDemo> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
void showDemoDialog<T>({ BuildContext context, Widget child }) { void showDemoDialog<T>({BuildContext context, Widget child}) {
showDialog<T>( showDialog<T>(
context: context, context: context,
barrierDismissible: false, barrierDismissible: false,
builder: (BuildContext context) => child, builder: (BuildContext context) => child,
) ).then<void>((T value) {
.then<void>((T value) { // The value passed to Navigator.pop() or null. // The value passed to Navigator.pop() or null.
if (value != null) { if (value != null) {
_scaffoldKey.currentState.showSnackBar(new SnackBar( _scaffoldKey.currentState.showSnackBar(
content: new Text('You selected: $value') new SnackBar(
)); content: new Text('You selected: $value'),
),
);
} }
}); });
} }
...@@ -39,7 +41,7 @@ class _CupertinoDialogDemoState extends State<CupertinoDialogDemo> { ...@@ -39,7 +41,7 @@ class _CupertinoDialogDemoState extends State<CupertinoDialogDemo> {
), ),
body: new ListView( body: new ListView(
padding: const EdgeInsets.symmetric(vertical: 24.0, horizontal: 72.0), padding: const EdgeInsets.symmetric(vertical: 24.0, horizontal: 72.0),
children: <Widget> [ children: <Widget>[
new CupertinoButton( new CupertinoButton(
child: const Text('Alert'), child: const Text('Alert'),
color: CupertinoColors.activeBlue, color: CupertinoColors.activeBlue,
...@@ -47,19 +49,23 @@ class _CupertinoDialogDemoState extends State<CupertinoDialogDemo> { ...@@ -47,19 +49,23 @@ class _CupertinoDialogDemoState extends State<CupertinoDialogDemo> {
showDemoDialog<String>( showDemoDialog<String>(
context: context, context: context,
child: new CupertinoAlertDialog( child: new CupertinoAlertDialog(
content: const Text('Discard draft?'), title: const Text('Discard draft?'),
actions: <Widget>[ actions: <Widget>[
new CupertinoDialogAction( new CupertinoDialogAction(
child: const Text('Discard'), child: const Text('Discard'),
isDestructiveAction: true, isDestructiveAction: true,
onPressed: () { Navigator.pop(context, 'Discard'); } onPressed: () {
Navigator.pop(context, 'Discard');
},
), ),
new CupertinoDialogAction( new CupertinoDialogAction(
child: const Text('Cancel'), child: const Text('Cancel'),
isDefaultAction: true, isDefaultAction: true,
onPressed: () { Navigator.pop(context, 'Cancel'); } onPressed: () {
Navigator.pop(context, 'Cancel');
},
), ),
] ],
), ),
); );
}, },
...@@ -74,26 +80,123 @@ class _CupertinoDialogDemoState extends State<CupertinoDialogDemo> { ...@@ -74,26 +80,123 @@ class _CupertinoDialogDemoState extends State<CupertinoDialogDemo> {
context: context, context: context,
child: new CupertinoAlertDialog( child: new CupertinoAlertDialog(
title: const Text('Allow "Maps" to access your location while you are using the app?'), title: const Text('Allow "Maps" to access your location while you are using the app?'),
content: const Text( content: const Text('Your current location will be displayed on the map and used '
'Your current location will be displayed on the map and used for directions, ' 'for directions, nearby search results, and estimated travel times.'),
'nearby search results, and estimated travel times.'
),
actions: <Widget>[ actions: <Widget>[
new CupertinoDialogAction( new CupertinoDialogAction(
child: const Text('Don\'t Allow'), child: const Text('Don\'t Allow'),
onPressed: () { Navigator.pop(context, 'Disallow'); } onPressed: () {
Navigator.pop(context, 'Disallow');
},
), ),
new CupertinoDialogAction( new CupertinoDialogAction(
child: const Text('Allow'), child: const Text('Allow'),
onPressed: () { Navigator.pop(context, 'Allow'); } onPressed: () {
Navigator.pop(context, 'Allow');
},
), ),
] ],
), ),
); );
}, },
), ),
const Padding(padding: const EdgeInsets.all(8.0)),
new CupertinoButton(
child: const Text('Alert with Buttons'),
color: CupertinoColors.activeBlue,
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 36.0),
onPressed: () {
showDemoDialog<String>(
context: context,
child: const CupertinoDessertDialog(
title: const Text('Select Favorite Dessert'),
content: const Text('Please select your favorite type of dessert from the '
'list below. Your selection will be used to customize the suggested '
'list of eateries in your area.'),
),
);
},
),
const Padding(padding: const EdgeInsets.all(8.0)),
new CupertinoButton(
child: const Text('Alert Buttons Only'),
color: CupertinoColors.activeBlue,
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 36.0),
onPressed: () {
showDemoDialog<String>(
context: context,
child: const CupertinoDessertDialog(),
);
},
),
], ],
), ),
); );
} }
} }
class CupertinoDessertDialog extends StatelessWidget {
const CupertinoDessertDialog({Key key, this.title, this.content}) : super(key: key);
final Widget title;
final Widget content;
@override
Widget build(BuildContext context) {
return new CupertinoAlertDialog(
title: title,
content: content,
actions: <Widget>[
new CupertinoDialogAction(
child: const Text('Cheesecake'),
onPressed: () {
Navigator.pop(context, 'Cheesecake');
},
),
new CupertinoDialogAction(
child: const Text('Tiramisu'),
onPressed: () {
Navigator.pop(context, 'Tiramisu');
},
),
new CupertinoDialogAction(
child: const Text('Apple Pie'),
onPressed: () {
Navigator.pop(context, 'Apple Pie');
},
),
new CupertinoDialogAction(
child: const Text("Devil's food cake"),
onPressed: () {
Navigator.pop(context, "Devil's food cake");
},
),
new CupertinoDialogAction(
child: const Text('Banana Split'),
onPressed: () {
Navigator.pop(context, 'Banana Split');
},
),
new CupertinoDialogAction(
child: const Text('Oatmeal Cookie'),
onPressed: () {
Navigator.pop(context, 'Oatmeal Cookies');
},
),
new CupertinoDialogAction(
child: const Text('Chocolate Brownie'),
onPressed: () {
Navigator.pop(context, 'Chocolate Brownies');
},
),
new CupertinoDialogAction(
child: const Text('Cancel'),
isDestructiveAction: true,
onPressed: () {
Navigator.pop(context, 'Cancel');
},
),
],
);
}
}
...@@ -42,12 +42,19 @@ const TextStyle _kCupertinoDialogActionStyle = const TextStyle( ...@@ -42,12 +42,19 @@ const TextStyle _kCupertinoDialogActionStyle = const TextStyle(
const double _kCupertinoDialogWidth = 270.0; const double _kCupertinoDialogWidth = 270.0;
const BoxDecoration _kCupertinoDialogFrontFillDecoration = const BoxDecoration( const BoxDecoration _kCupertinoDialogFrontFillDecoration = const BoxDecoration(
color: const Color(0xCCFFFFFF), color: const Color(0xccffffff),
); );
const BoxDecoration _kCupertinoDialogBackFill = const BoxDecoration( const BoxDecoration _kCupertinoDialogBackFill = const BoxDecoration(
color: const Color(0x77FFFFFFF), color: const Color(0x77ffffff),
); );
const double _kEdgePadding = 20.0;
const double _kButtonHeight = 45.0;
// TODO(gspencer): This color isn't correct. Instead, we should carve a hole in
// the dialog and show more of the background.
const Color _kButtonDividerColor = const Color(0xffd5d5d5);
/// An iOS-style dialog. /// An iOS-style dialog.
/// ///
/// This dialog widget does not have any opinion about the contents of the /// This dialog widget does not have any opinion about the contents of the
...@@ -112,13 +119,17 @@ class CupertinoDialog extends StatelessWidget { ...@@ -112,13 +119,17 @@ class CupertinoDialog extends StatelessWidget {
/// * <https://developer.apple.com/ios/human-interface-guidelines/views/alerts/> /// * <https://developer.apple.com/ios/human-interface-guidelines/views/alerts/>
class CupertinoAlertDialog extends StatelessWidget { class CupertinoAlertDialog extends StatelessWidget {
/// Creates an iOS-style alert dialog. /// Creates an iOS-style alert dialog.
///
/// The [actions] must not be null.
const CupertinoAlertDialog({ const CupertinoAlertDialog({
Key key, Key key,
this.title, this.title,
this.content, this.content,
this.actions, this.actions: const <Widget>[],
this.scrollController, this.scrollController,
}) : super(key: key); this.actionScrollController,
}) : assert(actions != null),
super(key: key);
/// The (optional) title of the dialog is displayed in a large font at the top /// The (optional) title of the dialog is displayed in a large font at the top
/// of the dialog. /// of the dialog.
...@@ -138,73 +149,56 @@ class CupertinoAlertDialog extends StatelessWidget { ...@@ -138,73 +149,56 @@ class CupertinoAlertDialog extends StatelessWidget {
/// Typically this is a list of [CupertinoDialogAction] widgets. /// Typically this is a list of [CupertinoDialogAction] widgets.
final List<Widget> actions; final List<Widget> actions;
/// A scroll controller that can be used to control the scrolling of the message /// A scroll controller that can be used to control the scrolling of the
/// in the dialog. /// [content] in the dialog.
/// ///
/// Defaults to null, and is typically not needed, since most alert messages are short. /// Defaults to null, and is typically not needed, since most alert messages
/// are short.
///
/// See also:
///
/// * [actionScrollController], which can be used for controlling the actions
/// section when there are many actions.
final ScrollController scrollController; final ScrollController scrollController;
/// A scroll controller that can be used to control the scrolling of the
/// actions in the dialog.
///
/// Defaults to null, and is typically not needed.
///
/// See also:
///
/// * [scrollController], which can be used for controlling the [content]
/// section when it is long.
final ScrollController actionScrollController;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
const double edgePadding = 20.0; final List<Widget> children = <Widget>[];
final List<Widget> titleContentGroup = <Widget>[];
if (title != null) {
titleContentGroup.add(new Padding(
padding: new EdgeInsets.only(
left: edgePadding,
right: edgePadding,
bottom: content == null ? edgePadding : 1.0,
top: edgePadding,
),
child: new DefaultTextStyle(
style: _kCupertinoDialogTitleStyle,
textAlign: TextAlign.center,
child: title,
),
));
}
if (content != null) { if (title != null || content != null) {
titleContentGroup.add( final Widget titleSection = new _CupertinoAlertTitleSection(
new Padding( title: title,
padding: new EdgeInsets.only( content: content,
left: edgePadding, scrollController: scrollController,
right: edgePadding,
bottom: edgePadding,
top: title == null ? edgePadding : 1.0,
),
child: new DefaultTextStyle(
style: _kCupertinoDialogContentStyle,
textAlign: TextAlign.center,
child: content,
),
),
); );
children.add(new Flexible(flex: 3, child: titleSection));
// Add padding between the sections.
children.add(const Padding(padding: const EdgeInsets.only(top: 8.0)));
} }
final List<Widget> children = <Widget>[]; if (actions.isNotEmpty) {
if (titleContentGroup.isNotEmpty) { final Widget actionSection = new _CupertinoAlertActionSection(
children: actions,
scrollController: actionScrollController,
);
children.add( children.add(
new Flexible( new Flexible(child: actionSection),
child: new CupertinoScrollbar(
child: new ListView(
controller: scrollController,
shrinkWrap: true,
children: titleContentGroup,
),
),
),
); );
} }
if (actions != null) {
children.add(new _CupertinoButtonBar(
children: actions,
));
}
return new Padding( return new Padding(
padding: const EdgeInsets.symmetric(vertical: edgePadding), padding: const EdgeInsets.symmetric(vertical: _kEdgePadding),
child: new CupertinoDialog( child: new CupertinoDialog(
child: new Column( child: new Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
...@@ -231,7 +225,8 @@ class CupertinoDialogAction extends StatelessWidget { ...@@ -231,7 +225,8 @@ class CupertinoDialogAction extends StatelessWidget {
@required this.child, @required this.child,
}) : assert(child != null); }) : assert(child != null);
/// The callback that is called when the button is tapped or otherwise activated. /// The callback that is called when the button is tapped or otherwise
/// activated.
/// ///
/// If this is set to null, the button will be disabled. /// If this is set to null, the button will be disabled.
final VoidCallback onPressed; final VoidCallback onPressed;
...@@ -251,8 +246,9 @@ class CupertinoDialogAction extends StatelessWidget { ...@@ -251,8 +246,9 @@ class CupertinoDialogAction extends StatelessWidget {
/// Typically a [Text] widget. /// Typically a [Text] widget.
final Widget child; final Widget child;
/// Whether the button is enabled or disabled. Buttons are disabled by default. To /// Whether the button is enabled or disabled. Buttons are disabled by
/// enable a button, set its [onPressed] property to a non-null value. /// default. To enable a button, set its [onPressed] property to a non-null
/// value.
bool get enabled => onPressed != null; bool get enabled => onPressed != null;
@override @override
...@@ -277,7 +273,7 @@ class CupertinoDialogAction extends StatelessWidget { ...@@ -277,7 +273,7 @@ class CupertinoDialogAction extends StatelessWidget {
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
child: new Container( child: new Container(
alignment: Alignment.center, alignment: Alignment.center,
padding: new EdgeInsets.all(10.0 * textScaleFactor), padding: new EdgeInsets.all(8.0 * textScaleFactor),
child: new DefaultTextStyle( child: new DefaultTextStyle(
style: style, style: style,
child: child, child: child,
...@@ -288,55 +284,197 @@ class CupertinoDialogAction extends StatelessWidget { ...@@ -288,55 +284,197 @@ class CupertinoDialogAction extends StatelessWidget {
} }
} }
const double _kButtonBarHeight = 45.0; // Constructs a text content section typically used in a CupertinoAlertDialog.
//
// This color isn't correct. Instead, we should carve a hole in the dialog and // If title is missing, then only content is added. If content is
// show more of the background. // missing, then only title is added. If both are missing, then it returns
const Color _kButtonDividerColor = const Color(0xFFD5D5D5); // a SingleChildScrollView with a zero-sized Container.
class _CupertinoAlertTitleSection extends StatelessWidget {
class _CupertinoButtonBar extends StatelessWidget { const _CupertinoAlertTitleSection({
const _CupertinoButtonBar({
Key key, Key key,
this.children, this.title,
this.content,
this.scrollController,
}) : super(key: key); }) : super(key: key);
final List<Widget> children; // The (optional) title of the dialog is displayed in a large font at the top
// of the dialog.
//
// Typically a Text widget.
final Widget title;
// The (optional) content of the dialog is displayed in the center of the
// dialog in a lighter font.
//
// Typically a Text widget.
final Widget content;
// A scroll controller that can be used to control the scrolling of the
// content in the dialog.
//
// Defaults to null, and is typically not needed, since most alert contents
// are short.
final ScrollController scrollController;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<Widget> buttons = <Widget>[]; final List<Widget> titleContentGroup = <Widget>[];
if (title != null) {
for (Widget child in children) { titleContentGroup.add(new Padding(
// TODO(abarth): Listen for the buttons being highlighted. padding: new EdgeInsets.only(
// TODO(gspencer): These buttons don't lay out in the same way as iOS. left: _kEdgePadding,
// When they get wide, they should stack vertically instead of in a row. right: _kEdgePadding,
// When they get really big, the vertically-stacked buttons should scroll bottom: content == null ? _kEdgePadding : 1.0,
// (separately from the top message). top: _kEdgePadding,
buttons.add(new Expanded(child: child)); ),
child: new DefaultTextStyle(
style: _kCupertinoDialogTitleStyle,
textAlign: TextAlign.center,
child: title,
),
));
} }
return new CustomPaint( if (content != null) {
painter: new _CupertinoButtonBarPainter(children.length), titleContentGroup.add(
child: new UnconstrainedBox( new Padding(
constrainedAxis: Axis.horizontal, padding: new EdgeInsets.only(
child: new ConstrainedBox( left: _kEdgePadding,
constraints: const BoxConstraints(minHeight: _kButtonBarHeight), right: _kEdgePadding,
child: new Row(children: buttons), bottom: _kEdgePadding,
top: title == null ? _kEdgePadding : 1.0,
),
child: new DefaultTextStyle(
style: _kCupertinoDialogContentStyle,
textAlign: TextAlign.center,
child: content,
),
),
);
}
if (titleContentGroup.isEmpty) {
return new SingleChildScrollView(
controller: scrollController,
child: new Container(width: 0.0, height: 0.0),
);
}
// Add padding between the widgets if necessary.
if (titleContentGroup.length > 1) {
titleContentGroup.insert(1, const Padding(padding: const EdgeInsets.only(top: 8.0)));
}
return new CupertinoScrollbar(
child: new SingleChildScrollView(
controller: scrollController,
child: new Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: titleContentGroup,
), ),
), ),
); );
} }
} }
class _CupertinoButtonBarPainter extends CustomPainter { // An Action Items section typically used in a CupertinoAlertDialog.
_CupertinoButtonBarPainter(this.count); //
// If _layoutActionsVertically is true, they are laid out vertically
// in a column; else they are laid out horizontally in a row. If there isn't
// enough room to show all the children vertically, they are wrapped in a
// CupertinoScrollbar widget. If children is null or empty, it returns null.
class _CupertinoAlertActionSection extends StatelessWidget {
const _CupertinoAlertActionSection({
Key key,
@required this.children,
this.scrollController,
}) : assert(children != null),
super(key: key);
final List<Widget> children;
// A scroll controller that can be used to control the scrolling of the
// actions in the dialog.
//
// Defaults to null, and is typically not needed, since most alert dialogs
// don't have many actions.
final ScrollController scrollController;
bool get _layoutActionsVertically => children.length > 2;
@override
Widget build(BuildContext context) {
if (children.isEmpty) {
return new SingleChildScrollView(
controller: scrollController,
child: new Container(width: 0.0, height: 0.0),
);
}
// TODO(abarth): Listen for the buttons being highlighted.
if (_layoutActionsVertically) {
// Skip the first divider
final List<Widget> buttons = <Widget>[children.first];
buttons.addAll(
children.sublist(1).map<Widget>(
(Widget child) {
return new CustomPaint(
painter: new _CupertinoVerticalDividerPainter(),
child: child,
);
},
),
);
return new CupertinoScrollbar(
child: new SingleChildScrollView(
controller: scrollController,
child: new Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: buttons,
),
),
);
} else {
// For a horizontal layout, we don't need the scrollController in most
// cases, but it still has to be always attached to a scroll view.
return new CupertinoScrollbar(
child: new SingleChildScrollView(
controller: scrollController,
child: new CustomPaint(
painter: new _CupertinoHorizontalDividerPainter(children.length),
child: new UnconstrainedBox(
constrainedAxis: Axis.horizontal,
child: new ConstrainedBox(
constraints: const BoxConstraints(minHeight: _kButtonHeight),
child: new Row(
children: children.map<Widget>((Widget button) {
return new Expanded(child: button);
}).toList(),
),
),
),
),
),
);
}
}
}
// A CustomPainter to draw the divider lines.
//
// Draws the cross-axis divider lines, used when the layout is horizontal.
class _CupertinoHorizontalDividerPainter extends CustomPainter {
_CupertinoHorizontalDividerPainter(this.count);
final int count; final int count;
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
final Paint paint = new Paint() final Paint paint = new Paint()..color = _kButtonDividerColor;
..color = _kButtonDividerColor;
canvas.drawLine(Offset.zero, new Offset(size.width, 0.0), paint); canvas.drawLine(Offset.zero, new Offset(size.width, 0.0), paint);
for (int i = 1; i < count; ++i) { for (int i = 1; i < count; ++i) {
...@@ -348,5 +486,21 @@ class _CupertinoButtonBarPainter extends CustomPainter { ...@@ -348,5 +486,21 @@ class _CupertinoButtonBarPainter extends CustomPainter {
} }
@override @override
bool shouldRepaint(_CupertinoButtonBarPainter other) => count != other.count; bool shouldRepaint(_CupertinoHorizontalDividerPainter other) => count != other.count;
}
// A CustomPainter to draw the divider lines.
//
// Draws the cross-axis divider lines, used when the layout is vertical.
class _CupertinoVerticalDividerPainter extends CustomPainter {
_CupertinoVerticalDividerPainter();
@override
void paint(Canvas canvas, Size size) {
final Paint paint = new Paint()..color = _kButtonDividerColor;
canvas.drawLine(const Offset(0.0, 0.0), new Offset(size.width, 0.0), paint);
}
@override
bool shouldRepaint(_CupertinoVerticalDividerPainter other) => false;
} }
...@@ -148,20 +148,189 @@ void main() { ...@@ -148,20 +148,189 @@ void main() {
expect(scrollController.offset, 0.0); expect(scrollController.offset, 0.0);
scrollController.jumpTo(100.0); scrollController.jumpTo(100.0);
expect(scrollController.offset, 100.0); expect(scrollController.offset, 100.0);
// Set the scroll position back to zero.
scrollController.jumpTo(0.0);
// Find the actual dialog box. The first decorated box is the popup barrier. // Find the actual dialog box. The first decorated box is the popup barrier.
expect(tester.getSize(find.byType(DecoratedBox).at(1)), equals(const Size(270.0, 560.0))); expect(tester.getSize(find.byType(DecoratedBox).at(1)), equals(const Size(270.0, 560.0)));
// Check sizes/locations of the text. // Check sizes/locations of the text.
expect(tester.getSize(find.text('The Title')), equals(const Size(230.0, 171.0))); expect(tester.getSize(find.text('The Title')), equals(const Size(230.0, 171.0)));
expect(tester.getSize(find.text('Cancel')), equals(const Size(75.0, 300.0))); expect(tester.getSize(find.text('Cancel')), equals(const Size(87.0, 300.0)));
expect(tester.getSize(find.text('OK')), equals(const Size(75.0, 100.0))); expect(tester.getSize(find.text('OK')), equals(const Size(87.0, 100.0)));
expect(tester.getTopLeft(find.text('The Title')), equals(const Offset(285.0, 40.0))); expect(tester.getTopLeft(find.text('The Title')), equals(const Offset(285.0, 40.0)));
// The Cancel and OK buttons have different Y values because "Cancel" is // The Cancel and OK buttons have different Y values because "Cancel" is
// wrapping (as it should with large text sizes like this). // wrapping (as it should with large text sizes like this).
expect(tester.getTopLeft(find.text('Cancel')), equals(const Offset(295.0, 250.0))); expect(tester.getTopLeft(find.text('Cancel')), equals(const Offset(289.0, 466.0)));
expect(tester.getTopLeft(find.text('OK')), equals(const Offset(430.0, 350.0))); expect(tester.getTopLeft(find.text('OK')), equals(const Offset(424.0, 566.0)));
});
testWidgets('Button list is scrollable, has correct position with large text sizes.',
(WidgetTester tester) async {
const double textScaleFactor = 3.0;
final ScrollController scrollController = new ScrollController(keepScrollOffset: true);
await tester.pumpWidget(
new MaterialApp(home: new Material(
child: new Center(
child: new Builder(builder: (BuildContext context) {
return new RaisedButton(
onPressed: () {
showDialog<Null>(
context: context,
builder: (BuildContext context) {
return new MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: textScaleFactor),
child: new CupertinoAlertDialog(
title: const Text('The title'),
content: const Text('The content.'),
actions: const <Widget>[
const CupertinoDialogAction(
child: const Text('One'),
),
const CupertinoDialogAction(
child: const Text('Two'),
),
const CupertinoDialogAction(
child: const Text('Three'),
),
const CupertinoDialogAction(
child: const Text('Chocolate Brownies'),
),
const CupertinoDialogAction(
isDestructiveAction: true,
child: const Text('Cancel'),
),
],
actionScrollController: scrollController,
),
);
},
);
},
child: const Text('Go'),
);
}),
),
)),
);
await tester.tap(find.text('Go'));
await tester.pump();
await tester.pump(const Duration(seconds: 1));
// Check that the action buttons list is scrollable.
expect(scrollController.offset, 0.0);
scrollController.jumpTo(100.0);
expect(scrollController.offset, 100.0);
scrollController.jumpTo(0.0);
// Check that the action buttons are aligned vertically.
expect(tester.getCenter(find.widgetWithText(CupertinoDialogAction, 'One')).dx, equals(400.0));
expect(tester.getCenter(find.widgetWithText(CupertinoDialogAction, 'Two')).dx, equals(400.0));
expect(tester.getCenter(find.widgetWithText(CupertinoDialogAction, 'Three')).dx, equals(400.0));
expect(tester.getCenter(find.widgetWithText(CupertinoDialogAction, 'Chocolate Brownies')).dx, equals(400.0));
expect(tester.getCenter(find.widgetWithText(CupertinoDialogAction, 'Cancel')).dx, equals(400.0));
// Check that the action buttons are the correct heights.
expect(tester.getSize(find.widgetWithText(CupertinoDialogAction, 'One')).height, equals(98.0));
expect(tester.getSize(find.widgetWithText(CupertinoDialogAction, 'Two')).height, equals(98.0));
expect(tester.getSize(find.widgetWithText(CupertinoDialogAction, 'Three')).height, equals(148.0));
expect(tester.getSize(find.widgetWithText(CupertinoDialogAction, 'Chocolate Brownies')).height, equals(298.0));
expect(tester.getSize(find.widgetWithText(CupertinoDialogAction, 'Cancel')).height, equals(148.0));
});
testWidgets('Title Section is empty, Button section is not empty.',
(WidgetTester tester) async {
const double textScaleFactor = 1.0;
final ScrollController scrollController = new ScrollController(keepScrollOffset: true);
await tester.pumpWidget(
new MaterialApp(home: new Material(
child: new Center(
child: new Builder(builder: (BuildContext context) {
return new RaisedButton(
onPressed: () {
showDialog<Null>(
context: context,
builder: (BuildContext context) {
return new MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: textScaleFactor),
child: new CupertinoAlertDialog(
actions: const <Widget>[
const CupertinoDialogAction(
child: const Text('One'),
),
const CupertinoDialogAction(
child: const Text('Two'),
),
],
actionScrollController: scrollController,
),
);
},
);
},
child: const Text('Go'),
);
}),
),
)),
);
await tester.tap(find.text('Go'));
await tester.pump();
await tester.pump(const Duration(seconds: 1));
// Check that the title/message section is not displayed
expect(scrollController.offset, 0.0);
expect(tester.getTopLeft(find.widgetWithText(CupertinoDialogAction, 'One')).dy, equals(283.5));
// Check that the button's vertical size is the same.
expect(tester.getSize(find.widgetWithText(CupertinoDialogAction, 'One')).height,
equals(tester.getSize(find.widgetWithText(CupertinoDialogAction, 'Two')).height));
});
testWidgets('Button section is empty, Title section is not empty.',
(WidgetTester tester) async {
const double textScaleFactor = 1.0;
final ScrollController scrollController = new ScrollController(keepScrollOffset: true);
await tester.pumpWidget(
new MaterialApp(home: new Material(
child: new Center(
child: new Builder(builder: (BuildContext context) {
return new RaisedButton(
onPressed: () {
showDialog<Null>(
context: context,
builder: (BuildContext context) {
return new MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: textScaleFactor),
child: new CupertinoAlertDialog(
title: const Text('The title'),
content: const Text('The content.'),
scrollController: scrollController,
),
);
},
);
},
child: const Text('Go'),
);
}),
),
)),
);
await tester.tap(find.text('Go'));
await tester.pump();
await tester.pump(const Duration(seconds: 1));
// Check that there's no button action section.
expect(scrollController.offset, 0.0);
expect(find.widgetWithText(CupertinoDialogAction, 'One'), findsNothing);
}); });
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment