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 {
class _CupertinoDialogDemoState extends State<CupertinoDialogDemo> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
void showDemoDialog<T>({ BuildContext context, Widget child }) {
void showDemoDialog<T>({BuildContext context, Widget child}) {
showDialog<T>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) => child,
)
.then<void>((T value) { // The value passed to Navigator.pop() or null.
).then<void>((T value) {
// The value passed to Navigator.pop() or null.
if (value != null) {
_scaffoldKey.currentState.showSnackBar(new SnackBar(
content: new Text('You selected: $value')
));
_scaffoldKey.currentState.showSnackBar(
new SnackBar(
content: new Text('You selected: $value'),
),
);
}
});
}
......@@ -39,7 +41,7 @@ class _CupertinoDialogDemoState extends State<CupertinoDialogDemo> {
),
body: new ListView(
padding: const EdgeInsets.symmetric(vertical: 24.0, horizontal: 72.0),
children: <Widget> [
children: <Widget>[
new CupertinoButton(
child: const Text('Alert'),
color: CupertinoColors.activeBlue,
......@@ -47,19 +49,23 @@ class _CupertinoDialogDemoState extends State<CupertinoDialogDemo> {
showDemoDialog<String>(
context: context,
child: new CupertinoAlertDialog(
content: const Text('Discard draft?'),
title: const Text('Discard draft?'),
actions: <Widget>[
new CupertinoDialogAction(
child: const Text('Discard'),
isDestructiveAction: true,
onPressed: () { Navigator.pop(context, 'Discard'); }
onPressed: () {
Navigator.pop(context, 'Discard');
},
),
new CupertinoDialogAction(
child: const Text('Cancel'),
isDefaultAction: true,
onPressed: () { Navigator.pop(context, 'Cancel'); }
onPressed: () {
Navigator.pop(context, 'Cancel');
},
),
]
],
),
);
},
......@@ -74,26 +80,123 @@ class _CupertinoDialogDemoState extends State<CupertinoDialogDemo> {
context: context,
child: new CupertinoAlertDialog(
title: const Text('Allow "Maps" to access your location while you are using the app?'),
content: const Text(
'Your current location will be displayed on the map and used for directions, '
'nearby search results, and estimated travel times.'
),
content: const Text('Your current location will be displayed on the map and used '
'for directions, nearby search results, and estimated travel times.'),
actions: <Widget>[
new CupertinoDialogAction(
child: const Text('Don\'t Allow'),
onPressed: () { Navigator.pop(context, 'Disallow'); }
onPressed: () {
Navigator.pop(context, 'Disallow');
},
),
new CupertinoDialogAction(
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(
const double _kCupertinoDialogWidth = 270.0;
const BoxDecoration _kCupertinoDialogFrontFillDecoration = const BoxDecoration(
color: const Color(0xCCFFFFFF),
color: const Color(0xccffffff),
);
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.
///
/// This dialog widget does not have any opinion about the contents of the
......@@ -112,13 +119,17 @@ class CupertinoDialog extends StatelessWidget {
/// * <https://developer.apple.com/ios/human-interface-guidelines/views/alerts/>
class CupertinoAlertDialog extends StatelessWidget {
/// Creates an iOS-style alert dialog.
///
/// The [actions] must not be null.
const CupertinoAlertDialog({
Key key,
this.title,
this.content,
this.actions,
this.actions: const <Widget>[],
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
/// of the dialog.
......@@ -138,73 +149,56 @@ class CupertinoAlertDialog extends StatelessWidget {
/// Typically this is a list of [CupertinoDialogAction] widgets.
final List<Widget> actions;
/// A scroll controller that can be used to control the scrolling of the message
/// in the dialog.
/// 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 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;
/// 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
Widget build(BuildContext context) {
const double edgePadding = 20.0;
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,
),
));
}
final List<Widget> children = <Widget>[];
if (content != null) {
titleContentGroup.add(
new Padding(
padding: new EdgeInsets.only(
left: edgePadding,
right: edgePadding,
bottom: edgePadding,
top: title == null ? edgePadding : 1.0,
),
child: new DefaultTextStyle(
style: _kCupertinoDialogContentStyle,
textAlign: TextAlign.center,
child: content,
),
),
if (title != null || content != null) {
final Widget titleSection = new _CupertinoAlertTitleSection(
title: title,
content: content,
scrollController: scrollController,
);
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 (titleContentGroup.isNotEmpty) {
if (actions.isNotEmpty) {
final Widget actionSection = new _CupertinoAlertActionSection(
children: actions,
scrollController: actionScrollController,
);
children.add(
new Flexible(
child: new CupertinoScrollbar(
child: new ListView(
controller: scrollController,
shrinkWrap: true,
children: titleContentGroup,
),
),
),
new Flexible(child: actionSection),
);
}
if (actions != null) {
children.add(new _CupertinoButtonBar(
children: actions,
));
}
return new Padding(
padding: const EdgeInsets.symmetric(vertical: edgePadding),
padding: const EdgeInsets.symmetric(vertical: _kEdgePadding),
child: new CupertinoDialog(
child: new Column(
mainAxisSize: MainAxisSize.min,
......@@ -231,7 +225,8 @@ class CupertinoDialogAction extends StatelessWidget {
@required this.child,
}) : 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.
final VoidCallback onPressed;
......@@ -251,8 +246,9 @@ class CupertinoDialogAction extends StatelessWidget {
/// Typically a [Text] widget.
final Widget child;
/// Whether the button is enabled or disabled. Buttons are disabled by default. To
/// enable a button, set its [onPressed] property to a non-null value.
/// Whether the button is enabled or disabled. Buttons are disabled by
/// default. To enable a button, set its [onPressed] property to a non-null
/// value.
bool get enabled => onPressed != null;
@override
......@@ -277,7 +273,7 @@ class CupertinoDialogAction extends StatelessWidget {
behavior: HitTestBehavior.opaque,
child: new Container(
alignment: Alignment.center,
padding: new EdgeInsets.all(10.0 * textScaleFactor),
padding: new EdgeInsets.all(8.0 * textScaleFactor),
child: new DefaultTextStyle(
style: style,
child: child,
......@@ -288,55 +284,197 @@ class CupertinoDialogAction extends StatelessWidget {
}
}
const double _kButtonBarHeight = 45.0;
// 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);
class _CupertinoButtonBar extends StatelessWidget {
const _CupertinoButtonBar({
// Constructs a text content section typically used in a CupertinoAlertDialog.
//
// If title is missing, then only content is added. If content is
// missing, then only title is added. If both are missing, then it returns
// a SingleChildScrollView with a zero-sized Container.
class _CupertinoAlertTitleSection extends StatelessWidget {
const _CupertinoAlertTitleSection({
Key key,
this.children,
this.title,
this.content,
this.scrollController,
}) : 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
Widget build(BuildContext context) {
final List<Widget> buttons = <Widget>[];
for (Widget child in children) {
// TODO(abarth): Listen for the buttons being highlighted.
// TODO(gspencer): These buttons don't lay out in the same way as iOS.
// When they get wide, they should stack vertically instead of in a row.
// When they get really big, the vertically-stacked buttons should scroll
// (separately from the top message).
buttons.add(new Expanded(child: child));
final List<Widget> titleContentGroup = <Widget>[];
if (title != null) {
titleContentGroup.add(new Padding(
padding: new EdgeInsets.only(
left: _kEdgePadding,
right: _kEdgePadding,
bottom: content == null ? _kEdgePadding : 1.0,
top: _kEdgePadding,
),
child: new DefaultTextStyle(
style: _kCupertinoDialogTitleStyle,
textAlign: TextAlign.center,
child: title,
),
));
}
return new CustomPaint(
painter: new _CupertinoButtonBarPainter(children.length),
child: new UnconstrainedBox(
constrainedAxis: Axis.horizontal,
child: new ConstrainedBox(
constraints: const BoxConstraints(minHeight: _kButtonBarHeight),
child: new Row(children: buttons),
if (content != null) {
titleContentGroup.add(
new Padding(
padding: new EdgeInsets.only(
left: _kEdgePadding,
right: _kEdgePadding,
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 {
_CupertinoButtonBarPainter(this.count);
// An Action Items section typically used in a CupertinoAlertDialog.
//
// 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;
@override
void paint(Canvas canvas, Size size) {
final Paint paint = new Paint()
..color = _kButtonDividerColor;
final Paint paint = new Paint()..color = _kButtonDividerColor;
canvas.drawLine(Offset.zero, new Offset(size.width, 0.0), paint);
for (int i = 1; i < count; ++i) {
......@@ -348,5 +486,21 @@ class _CupertinoButtonBarPainter extends CustomPainter {
}
@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() {
expect(scrollController.offset, 0.0);
scrollController.jumpTo(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.
expect(tester.getSize(find.byType(DecoratedBox).at(1)), equals(const Size(270.0, 560.0)));
// 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('Cancel')), equals(const Size(75.0, 300.0)));
expect(tester.getSize(find.text('OK')), equals(const Size(75.0, 100.0)));
expect(tester.getSize(find.text('Cancel')), equals(const Size(87.0, 300.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)));
// The Cancel and OK buttons have different Y values because "Cancel" is
// 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('OK')), equals(const Offset(430.0, 350.0)));
expect(tester.getTopLeft(find.text('Cancel')), equals(const Offset(289.0, 466.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