mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
CupertinoActionSheet (#19232)
Adding CupertinoActionSheet, showCupertinoModalPopup
This commit is contained in:
parent
21f22ed3ba
commit
96326d4743
@ -7,6 +7,7 @@
|
||||
/// To use, import `package:flutter/cupertino.dart`.
|
||||
library cupertino;
|
||||
|
||||
export 'src/cupertino/action_sheet.dart';
|
||||
export 'src/cupertino/activity_indicator.dart';
|
||||
export 'src/cupertino/app.dart';
|
||||
export 'src/cupertino/bottom_tab_bar.dart';
|
||||
|
1267
packages/flutter/lib/src/cupertino/action_sheet.dart
Normal file
1267
packages/flutter/lib/src/cupertino/action_sheet.dart
Normal file
File diff suppressed because it is too large
Load Diff
@ -14,6 +14,9 @@ const double _kMinFlingVelocity = 1.0; // Screen widths per second.
|
||||
// Barrier color for a Cupertino modal barrier.
|
||||
const Color _kModalBarrierColor = Color(0x6604040F);
|
||||
|
||||
// The duration of the transition used when a modal popup is shown.
|
||||
const Duration _kModalPopupTransitionDuration = Duration(milliseconds: 335);
|
||||
|
||||
// Offset from offscreen to the right to fully on screen.
|
||||
final Tween<Offset> _kRightMiddleTween = new Tween<Offset>(
|
||||
begin: const Offset(1.0, 0.0),
|
||||
@ -715,6 +718,102 @@ class _CupertinoEdgeShadowPainter extends BoxPainter {
|
||||
}
|
||||
}
|
||||
|
||||
class _CupertinoModalPopupRoute<T> extends PopupRoute<T> {
|
||||
_CupertinoModalPopupRoute({
|
||||
this.builder,
|
||||
this.barrierLabel,
|
||||
RouteSettings settings,
|
||||
}) : super(settings: settings);
|
||||
|
||||
final WidgetBuilder builder;
|
||||
|
||||
@override
|
||||
final String barrierLabel;
|
||||
|
||||
@override
|
||||
Color get barrierColor => _kModalBarrierColor;
|
||||
|
||||
@override
|
||||
bool get barrierDismissible => true;
|
||||
|
||||
@override
|
||||
bool get semanticsDismissible => false;
|
||||
|
||||
@override
|
||||
Duration get transitionDuration => _kModalPopupTransitionDuration;
|
||||
|
||||
Animation<double> _animation;
|
||||
|
||||
Tween<Offset> _offsetTween;
|
||||
|
||||
@override
|
||||
Animation<double> createAnimation() {
|
||||
assert(_animation == null);
|
||||
_animation = new CurvedAnimation(
|
||||
parent: super.createAnimation(),
|
||||
curve: Curves.ease,
|
||||
reverseCurve: Curves.ease.flipped,
|
||||
);
|
||||
_offsetTween = new Tween<Offset>(
|
||||
begin: const Offset(0.0, 1.0),
|
||||
end: const Offset(0.0, 0.0),
|
||||
);
|
||||
return _animation;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
|
||||
return builder(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
|
||||
return new Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: new FractionalTranslation(
|
||||
translation: _offsetTween.evaluate(_animation),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Shows a modal iOS-style popup that slides up from the bottom of the screen.
|
||||
///
|
||||
/// Such a popup is an alternative to a menu or a dialog and prevents the user
|
||||
/// from interacting with the rest of the app.
|
||||
///
|
||||
/// The `context` argument is used to look up the [Navigator] for the popup.
|
||||
/// It is only used when the method is called. Its corresponding widget can be
|
||||
/// safely removed from the tree before the popup is closed.
|
||||
///
|
||||
/// The `builder` argument typically builds a [CupertinoActionSheet] widget.
|
||||
/// Content below the widget is dimmed with a [ModalBarrier]. The widget built
|
||||
/// by the `builder` does not share a context with the location that
|
||||
/// `showCupertinoModalPopup` is originally called from. Use a
|
||||
/// [StatefulBuilder] or a custom [StatefulWidget] if the widget needs to
|
||||
/// update dynamically.
|
||||
///
|
||||
/// Returns a `Future` that resolves to the value that was passed to
|
||||
/// [Navigator.pop] when the popup was closed.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ActionSheet], which is the widget usually returned by the `builder`
|
||||
/// argument to [showCupertinoModalPopup].
|
||||
/// * <https://developer.apple.com/design/human-interface-guidelines/ios/views/action-sheets/>
|
||||
Future<T> showCupertinoModalPopup<T>({
|
||||
@required BuildContext context,
|
||||
@required WidgetBuilder builder,
|
||||
}) {
|
||||
return Navigator.of(context, rootNavigator: true).push(
|
||||
new _CupertinoModalPopupRoute<T>(
|
||||
builder: builder,
|
||||
barrierLabel: 'Dismiss',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCupertinoDialogTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
|
||||
final CurvedAnimation fadeAnimation = new CurvedAnimation(
|
||||
parent: animation,
|
||||
|
@ -70,6 +70,7 @@ class BoxDecoration extends Decoration {
|
||||
/// [BoxShape.circle].
|
||||
/// * If [boxShadow] is null, this decoration does not paint a shadow.
|
||||
/// * If [gradient] is null, this decoration does not paint gradients.
|
||||
/// * If [backgroundBlendMode] is null, this decoration paints with [BlendMode.srcOver]
|
||||
///
|
||||
/// The [shape] argument must not be null.
|
||||
const BoxDecoration({
|
||||
@ -79,13 +80,20 @@ class BoxDecoration extends Decoration {
|
||||
this.borderRadius,
|
||||
this.boxShadow,
|
||||
this.gradient,
|
||||
this.backgroundBlendMode,
|
||||
this.shape = BoxShape.rectangle,
|
||||
}) : assert(shape != null);
|
||||
}) : assert(shape != null),
|
||||
// TODO(mattcarroll): Use "backgroundBlendMode == null" when Dart #31140 is in.
|
||||
assert(
|
||||
identical(backgroundBlendMode, null) || color != null || gradient != null,
|
||||
'backgroundBlendMode applies to BoxDecoration\'s background color or'
|
||||
'gradient, but no color or gradient were provided.'
|
||||
);
|
||||
|
||||
@override
|
||||
bool debugAssertIsValid() {
|
||||
assert(shape != BoxShape.circle ||
|
||||
borderRadius == null); // Can't have a border radius if you're a circle.
|
||||
borderRadius == null); // Can't have a border radius if you're a circle.
|
||||
return super.debugAssertIsValid();
|
||||
}
|
||||
|
||||
@ -136,6 +144,14 @@ class BoxDecoration extends Decoration {
|
||||
/// The [gradient] is drawn under the [image].
|
||||
final Gradient gradient;
|
||||
|
||||
/// The blend mode applied to the [color] or [gradient] background of the box.
|
||||
///
|
||||
/// If no [backgroundBlendMode] is provided, then the default painting blend
|
||||
/// mode is used.
|
||||
///
|
||||
/// If no [color] or [gradient] is provided, then blend mode has no impact.
|
||||
final BlendMode backgroundBlendMode;
|
||||
|
||||
/// The shape to fill the background [color], [gradient], and [image] into and
|
||||
/// to cast as the [boxShadow].
|
||||
///
|
||||
@ -332,6 +348,8 @@ class _BoxDecorationPainter extends BoxPainter {
|
||||
if (_cachedBackgroundPaint == null ||
|
||||
(_decoration.gradient != null && _rectForCachedBackgroundPaint != rect)) {
|
||||
final Paint paint = new Paint();
|
||||
if (_decoration.backgroundBlendMode != null)
|
||||
paint.blendMode = _decoration.backgroundBlendMode;
|
||||
if (_decoration.color != null)
|
||||
paint.color = _decoration.color;
|
||||
if (_decoration.gradient != null) {
|
||||
|
@ -33,6 +33,7 @@ class ModalBarrier extends StatelessWidget {
|
||||
this.color,
|
||||
this.dismissible = true,
|
||||
this.semanticsLabel,
|
||||
this.barrierSemanticsDismissible = true,
|
||||
}) : super(key: key);
|
||||
|
||||
/// If non-null, fill the barrier with this color.
|
||||
@ -51,6 +52,13 @@ class ModalBarrier extends StatelessWidget {
|
||||
/// [ModalBarrier] built by [ModalRoute] pages.
|
||||
final bool dismissible;
|
||||
|
||||
/// Whether the modal barrier semantics are included in the semantics tree.
|
||||
///
|
||||
/// See also:
|
||||
/// * [ModalRoute.semanticsDismissible], which controls this property for
|
||||
/// the [ModalBarrier] built by [ModalRoute] pages.
|
||||
final bool barrierSemanticsDismissible;
|
||||
|
||||
/// Semantics label used for the barrier if it is [dismissable].
|
||||
///
|
||||
/// The semantics label is read out by accessibility tools (e.g. TalkBack
|
||||
@ -66,10 +74,12 @@ class ModalBarrier extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
assert(!dismissible || semanticsLabel == null || debugCheckHasDirectionality(context));
|
||||
final bool semanticsDismissible = dismissible && defaultTargetPlatform != TargetPlatform.android;
|
||||
final bool modalBarrierSemanticsDismissible = barrierSemanticsDismissible ?? semanticsDismissible;
|
||||
return new BlockSemantics(
|
||||
child: new ExcludeSemantics(
|
||||
// On Android, the back button is used to dismiss a modal.
|
||||
excluding: !semanticsDismissible,
|
||||
// On Android, the back button is used to dismiss a modal. On iOS, some
|
||||
// modal barriers are not dismissible in accessibility mode.
|
||||
excluding: !semanticsDismissible || !modalBarrierSemanticsDismissible,
|
||||
child: new GestureDetector(
|
||||
onTapDown: (TapDownDetails details) {
|
||||
if (dismissible)
|
||||
@ -117,6 +127,7 @@ class AnimatedModalBarrier extends AnimatedWidget {
|
||||
Animation<Color> color,
|
||||
this.dismissible = true,
|
||||
this.semanticsLabel,
|
||||
this.barrierSemanticsDismissible,
|
||||
}) : super(key: key, listenable: color);
|
||||
|
||||
/// If non-null, fill the barrier with this color.
|
||||
@ -145,12 +156,20 @@ class AnimatedModalBarrier extends AnimatedWidget {
|
||||
/// [ModalBarrier] built by [ModalRoute] pages.
|
||||
final String semanticsLabel;
|
||||
|
||||
/// Whether the modal barrier semantics are included in the semantics tree.
|
||||
///
|
||||
/// See also:
|
||||
/// * [ModalRoute.semanticsDismissible], which controls this property for
|
||||
/// the [ModalBarrier] built by [ModalRoute] pages.
|
||||
final bool barrierSemanticsDismissible;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new ModalBarrier(
|
||||
color: color?.value,
|
||||
dismissible: dismissible,
|
||||
semanticsLabel: semanticsLabel,
|
||||
barrierSemanticsDismissible: barrierSemanticsDismissible,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -898,6 +898,21 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
|
||||
/// * [ModalBarrier], the widget that implements this feature.
|
||||
bool get barrierDismissible;
|
||||
|
||||
/// Whether the semantics of the modal barrier are included in the
|
||||
/// semantics tree.
|
||||
///
|
||||
/// The modal barrier is the scrim that is rendered behind each route, which
|
||||
/// generally prevents the user from interacting with the route below the
|
||||
/// current route, and normally partially obscures such routes.
|
||||
///
|
||||
/// If [semanticsDismissible] is true, then modal barrier semantics are
|
||||
/// included in the semantics tree.
|
||||
///
|
||||
/// If [semanticsDismissible] is false, then modal barrier semantics are
|
||||
/// excluded from the the semantics tree and tapping on the modal barrier
|
||||
/// has no effect.
|
||||
bool get semanticsDismissible => true;
|
||||
|
||||
/// The color to use for the modal barrier. If this is null, the barrier will
|
||||
/// be transparent.
|
||||
///
|
||||
@ -1173,11 +1188,13 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
|
||||
color: color,
|
||||
dismissible: barrierDismissible, // changedInternalState is called if this updates
|
||||
semanticsLabel: barrierLabel, // changedInternalState is called if this updates
|
||||
barrierSemanticsDismissible: semanticsDismissible,
|
||||
);
|
||||
} else {
|
||||
barrier = new ModalBarrier(
|
||||
dismissible: barrierDismissible, // changedInternalState is called if this updates
|
||||
semanticsLabel: barrierLabel, // changedInternalState is called if this updates
|
||||
barrierSemanticsDismissible: semanticsDismissible,
|
||||
);
|
||||
}
|
||||
return new IgnorePointer(
|
||||
|
967
packages/flutter/test/cupertino/action_sheet_test.dart
Normal file
967
packages/flutter/test/cupertino/action_sheet_test.dart
Normal file
@ -0,0 +1,967 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../widgets/semantics_tester.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Verify that a tap on modal barrier dismisses an action sheet',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
const CupertinoActionSheet(
|
||||
title: Text('Action Sheet'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Go'));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Action Sheet'), findsOneWidget);
|
||||
|
||||
await tester.tapAt(const Offset(20.0, 20.0));
|
||||
await tester.pump();
|
||||
expect(find.text('Action Sheet'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Verify that a tap on title section (not buttons) does not dismiss an action sheet',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
const CupertinoActionSheet(
|
||||
title: Text('Action Sheet'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Go'));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Action Sheet'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Action Sheet'));
|
||||
await tester.pump();
|
||||
expect(find.text('Action Sheet'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Action sheet destructive text style', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
boilerplate(
|
||||
new CupertinoActionSheetAction(
|
||||
isDestructiveAction: true,
|
||||
child: const Text('Ok'),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final DefaultTextStyle widget = tester.widget(find.widgetWithText(DefaultTextStyle, 'Ok'));
|
||||
|
||||
expect(widget.style.color, CupertinoColors.destructiveRed);
|
||||
});
|
||||
|
||||
testWidgets('Action sheet default text style', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
boilerplate(
|
||||
new CupertinoActionSheetAction(
|
||||
isDefaultAction: true,
|
||||
child: const Text('Ok'),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final DefaultTextStyle widget = tester.widget(find.widgetWithText(DefaultTextStyle, 'Ok'));
|
||||
|
||||
expect(widget.style.fontWeight, equals(FontWeight.w600));
|
||||
});
|
||||
|
||||
testWidgets('Action sheet text styles are correct when both title and message are included',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
const CupertinoActionSheet(
|
||||
title: Text('Action Sheet'),
|
||||
message: Text('An action sheet')
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Go'));
|
||||
await tester.pump();
|
||||
|
||||
final DefaultTextStyle titleStyle = tester.firstWidget(find.widgetWithText(DefaultTextStyle,
|
||||
'Action Sheet'));
|
||||
final DefaultTextStyle messageStyle = tester.firstWidget(find.widgetWithText(DefaultTextStyle,
|
||||
'An action sheet'));
|
||||
|
||||
expect(titleStyle.style.fontWeight, FontWeight.w600);
|
||||
expect(messageStyle.style.fontWeight, FontWeight.w400);
|
||||
});
|
||||
|
||||
testWidgets('Action sheet text styles are correct when title but no message is included',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
const CupertinoActionSheet(
|
||||
title: Text('Action Sheet'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Go'));
|
||||
await tester.pump();
|
||||
|
||||
final DefaultTextStyle titleStyle = tester.firstWidget(find.widgetWithText(DefaultTextStyle,
|
||||
'Action Sheet'));
|
||||
|
||||
expect(titleStyle.style.fontWeight, FontWeight.w400);
|
||||
});
|
||||
|
||||
testWidgets('Action sheet text styles are correct when message but no title is included',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
const CupertinoActionSheet(
|
||||
message: Text('An action sheet'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Go'));
|
||||
await tester.pump();
|
||||
|
||||
final DefaultTextStyle messageStyle = tester.firstWidget(find.widgetWithText(DefaultTextStyle,
|
||||
'An action sheet'));
|
||||
|
||||
expect(messageStyle.style.fontWeight, FontWeight.w600);
|
||||
});
|
||||
|
||||
testWidgets('Content section but no actions', (WidgetTester tester) async {
|
||||
final ScrollController scrollController = new ScrollController();
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
new CupertinoActionSheet(
|
||||
title: const Text('The title'),
|
||||
message: const Text('The message.'),
|
||||
messageScrollController: scrollController,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Go'));
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
// Content section should be at the bottom left of action sheet
|
||||
// (minus padding).
|
||||
expect(tester.getBottomLeft(find.byType(ClipRRect)),
|
||||
tester.getBottomLeft(find.byType(CupertinoActionSheet)) - const Offset(-8.0, 10.0));
|
||||
|
||||
// Check that the dialog size is the same as the content section size
|
||||
// (minus padding).
|
||||
expect(
|
||||
tester.getSize(find.byType(ClipRRect)).height,
|
||||
tester.getSize(find.byType(CupertinoActionSheet)).height - 20.0,
|
||||
);
|
||||
|
||||
expect(
|
||||
tester.getSize(find.byType(ClipRRect)).width,
|
||||
tester.getSize(find.byType(CupertinoActionSheet)).width - 16.0,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Actions but no content section', (WidgetTester tester) async {
|
||||
final ScrollController actionScrollController = new ScrollController();
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
new CupertinoActionSheet(
|
||||
actions: <Widget>[
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('One'),
|
||||
onPressed: () {},
|
||||
),
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('Two'),
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
actionScrollController: actionScrollController,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Go'));
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
final Finder finder = find.byElementPredicate(
|
||||
(Element element) {
|
||||
return element.widget.runtimeType.toString() == '_CupertinoAlertActionSection';
|
||||
},
|
||||
);
|
||||
|
||||
// Check that the title/message section is not displayed (action section is
|
||||
// at the top of the action sheet + padding).
|
||||
expect(tester.getTopLeft(finder),
|
||||
tester.getTopLeft(find.byType(CupertinoActionSheet)) + const Offset(8.0, 10.0));
|
||||
|
||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)) + const Offset(8.0, 10.0),
|
||||
tester.getTopLeft(find.widgetWithText(CupertinoActionSheetAction, 'One')));
|
||||
expect(tester.getBottomLeft(find.byType(CupertinoActionSheet)) + const Offset(8.0, -10.0),
|
||||
tester.getBottomLeft(find.widgetWithText(CupertinoActionSheetAction, 'Two')));
|
||||
});
|
||||
|
||||
testWidgets('Action section is scrollable', (WidgetTester tester) async {
|
||||
final ScrollController actionScrollController = new ScrollController();
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
new Builder(builder: (BuildContext context) {
|
||||
return new MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(textScaleFactor: 3.0),
|
||||
child: new CupertinoActionSheet(
|
||||
title: const Text('The title'),
|
||||
message: const Text('The message.'),
|
||||
actions: <Widget>[
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('One'),
|
||||
onPressed: () {},
|
||||
),
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('Two'),
|
||||
onPressed: () {},
|
||||
),
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('Three'),
|
||||
onPressed: () {},
|
||||
),
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('Four'),
|
||||
onPressed: () {},
|
||||
),
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('Five'),
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
actionScrollController: actionScrollController,
|
||||
),
|
||||
);
|
||||
}),
|
||||
)
|
||||
);
|
||||
|
||||
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(actionScrollController.offset, 0.0);
|
||||
actionScrollController.jumpTo(100.0);
|
||||
expect(actionScrollController.offset, 100.0);
|
||||
actionScrollController.jumpTo(0.0);
|
||||
|
||||
// Check that the action buttons are aligned vertically.
|
||||
expect(tester.getCenter(find.widgetWithText(CupertinoActionSheetAction, 'One')).dx, equals(400.0));
|
||||
expect(tester.getCenter(find.widgetWithText(CupertinoActionSheetAction, 'Two')).dx, equals(400.0));
|
||||
expect(tester.getCenter(find.widgetWithText(CupertinoActionSheetAction, 'Three')).dx, equals(400.0));
|
||||
expect(tester.getCenter(find.widgetWithText(CupertinoActionSheetAction, 'Four')).dx, equals(400.0));
|
||||
expect(tester.getCenter(find.widgetWithText(CupertinoActionSheetAction, 'Five')).dx, equals(400.0));
|
||||
|
||||
// Check that the action buttons are the correct heights.
|
||||
expect(tester.getSize(find.widgetWithText(CupertinoActionSheetAction, 'One')).height, equals(92.0));
|
||||
expect(tester.getSize(find.widgetWithText(CupertinoActionSheetAction, 'Two')).height, equals(92.0));
|
||||
expect(tester.getSize(find.widgetWithText(CupertinoActionSheetAction, 'Three')).height, equals(92.0));
|
||||
expect(tester.getSize(find.widgetWithText(CupertinoActionSheetAction, 'Four')).height, equals(92.0));
|
||||
expect(tester.getSize(find.widgetWithText(CupertinoActionSheetAction, 'Five')).height, equals(92.0));
|
||||
});
|
||||
|
||||
testWidgets('Content section is scrollable', (WidgetTester tester) async {
|
||||
final ScrollController messageScrollController = new ScrollController();
|
||||
double screenHeight;
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
new Builder(builder: (BuildContext context) {
|
||||
screenHeight = MediaQuery.of(context).size.height;
|
||||
return new MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(textScaleFactor: 3.0),
|
||||
child: new CupertinoActionSheet(
|
||||
title: const Text('The title'),
|
||||
message: new Text('Very long content' * 200),
|
||||
actions: <Widget>[
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('One'),
|
||||
onPressed: () {},
|
||||
),
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('Two'),
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
messageScrollController: messageScrollController,
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Go'));
|
||||
await tester.pump();
|
||||
|
||||
expect(messageScrollController.offset, 0.0);
|
||||
messageScrollController.jumpTo(100.0);
|
||||
expect(messageScrollController.offset, 100.0);
|
||||
// Set the scroll position back to zero.
|
||||
messageScrollController.jumpTo(0.0);
|
||||
|
||||
// Expect the action sheet to take all available height.
|
||||
expect(tester.getSize(find.byType(CupertinoActionSheet)).height, screenHeight);
|
||||
});
|
||||
|
||||
testWidgets('Tap on button calls onPressed', (WidgetTester tester) async {
|
||||
bool wasPressed = false;
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
new Builder(builder: (BuildContext context) {
|
||||
return new CupertinoActionSheet(
|
||||
actions: <Widget>[
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('One'),
|
||||
onPressed: () {
|
||||
wasPressed = true;
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Go'));
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
expect(wasPressed, isFalse);
|
||||
|
||||
await tester.tap(find.text('One'));
|
||||
|
||||
expect(wasPressed, isTrue);
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
expect(find.text('One'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Action sheet width is correct when given infinite horizontal space',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
new Row(
|
||||
children: <Widget>[
|
||||
new CupertinoActionSheet(
|
||||
actions: <Widget>[
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('One'),
|
||||
onPressed: () {},
|
||||
),
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('Two'),
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Go'));
|
||||
await tester.pump();
|
||||
|
||||
expect(tester.getSize(find.byType(CupertinoActionSheet)).width, 600.0);
|
||||
});
|
||||
|
||||
testWidgets('Action sheet height is correct when given infinite vertical space',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
new Column(
|
||||
children: <Widget>[
|
||||
new CupertinoActionSheet(
|
||||
actions: <Widget>[
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('One'),
|
||||
onPressed: () {},
|
||||
),
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('Two'),
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Go'));
|
||||
await tester.pump();
|
||||
|
||||
expect(tester.getSize(find.byType(CupertinoActionSheet)).height,
|
||||
moreOrLessEquals(132.33333333333334));
|
||||
});
|
||||
|
||||
testWidgets('1 action button with cancel button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
new CupertinoActionSheet(
|
||||
title: const Text('The title'),
|
||||
message: new Text('Very long content' * 200),
|
||||
actions: <Widget>[
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('One'),
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
cancelButton: new CupertinoActionSheetAction(
|
||||
child: const Text('Cancel'),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Go'));
|
||||
await tester.pump();
|
||||
|
||||
// Action section is size of one action button.
|
||||
expect(findScrollableActionsSectionRenderBox(tester).size.height, 56.0);
|
||||
});
|
||||
|
||||
testWidgets('2 action buttons with cancel button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
new CupertinoActionSheet(
|
||||
title: const Text('The title'),
|
||||
message: new Text('Very long content' * 200),
|
||||
actions: <Widget>[
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('One'),
|
||||
onPressed: () {},
|
||||
),
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('Two'),
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
cancelButton: new CupertinoActionSheetAction(
|
||||
child: const Text('Cancel'),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Go'));
|
||||
await tester.pump();
|
||||
|
||||
expect(findScrollableActionsSectionRenderBox(tester).size.height,
|
||||
moreOrLessEquals(112.33333333333331));
|
||||
});
|
||||
|
||||
testWidgets('3 action buttons with cancel button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
new CupertinoActionSheet(
|
||||
title: const Text('The title'),
|
||||
message: new Text('Very long content' * 200),
|
||||
actions: <Widget>[
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('One'),
|
||||
onPressed: () {},
|
||||
),
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('Two'),
|
||||
onPressed: () {},
|
||||
),
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('Three'),
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
cancelButton: new CupertinoActionSheetAction(
|
||||
child: const Text('Cancel'),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Go'));
|
||||
await tester.pump();
|
||||
|
||||
expect(findScrollableActionsSectionRenderBox(tester).size.height,
|
||||
moreOrLessEquals(168.66666666666669));
|
||||
});
|
||||
|
||||
testWidgets('4+ action buttons with cancel button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
new CupertinoActionSheet(
|
||||
title: const Text('The title'),
|
||||
message: new Text('Very long content' * 200),
|
||||
actions: <Widget>[
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('One'),
|
||||
onPressed: () {},
|
||||
),
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('Two'),
|
||||
onPressed: () {},
|
||||
),
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('Three'),
|
||||
onPressed: () {},
|
||||
),
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('Four'),
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
cancelButton: new CupertinoActionSheetAction(
|
||||
child: const Text('Cancel'),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Go'));
|
||||
await tester.pump();
|
||||
|
||||
expect(findScrollableActionsSectionRenderBox(tester).size.height,
|
||||
moreOrLessEquals(84.33333333333337));
|
||||
});
|
||||
|
||||
testWidgets('1 action button without cancel button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
new CupertinoActionSheet(
|
||||
title: const Text('The title'),
|
||||
message: new Text('Very long content' * 200),
|
||||
actions: <Widget>[
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('One'),
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Go'));
|
||||
await tester.pump();
|
||||
|
||||
expect(findScrollableActionsSectionRenderBox(tester).size.height, 56.0);
|
||||
});
|
||||
|
||||
testWidgets('2+ action buttons without cancel button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
new CupertinoActionSheet(
|
||||
title: const Text('The title'),
|
||||
message: new Text('Very long content' * 200),
|
||||
actions: <Widget>[
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('One'),
|
||||
onPressed: () {},
|
||||
),
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('Two'),
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Go'));
|
||||
await tester.pump();
|
||||
|
||||
expect(findScrollableActionsSectionRenderBox(tester).size.height,
|
||||
moreOrLessEquals(84.33333333333337));
|
||||
});
|
||||
|
||||
testWidgets('Action sheet with just cancel button is correct', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
new CupertinoActionSheet(
|
||||
cancelButton: new CupertinoActionSheetAction(
|
||||
child: const Text('Cancel'),
|
||||
onPressed: (){},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Go'));
|
||||
await tester.pump();
|
||||
|
||||
// Height should be cancel button height + padding
|
||||
expect(tester.getSize(find.byType(CupertinoActionSheet)).height, 76.0);
|
||||
expect(tester.getSize(find.byType(CupertinoActionSheet)).width, 600.0);
|
||||
});
|
||||
|
||||
testWidgets('Cancel button tap calls onPressed', (WidgetTester tester) async {
|
||||
bool wasPressed = false;
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
new Builder(builder: (BuildContext context) {
|
||||
return new CupertinoActionSheet(
|
||||
cancelButton: new CupertinoActionSheetAction(
|
||||
child: const Text('Cancel'),
|
||||
onPressed: () {
|
||||
wasPressed = true;
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Go'));
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
expect(wasPressed, isFalse);
|
||||
|
||||
await tester.tap(find.text('Cancel'));
|
||||
|
||||
expect(wasPressed, isTrue);
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
expect(find.text('Cancel'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Layout is correct when cancel button is present', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
new CupertinoActionSheet(
|
||||
title: const Text('The title'),
|
||||
message: const Text('The message'),
|
||||
actions: <Widget>[
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('One'),
|
||||
onPressed: () {},
|
||||
),
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('Two'),
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
cancelButton: new CupertinoActionSheetAction(
|
||||
child: const Text('Cancel'),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Go'));
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
expect(tester.getBottomLeft(find.widgetWithText(CupertinoActionSheetAction, 'Cancel')).dy, 590.0);
|
||||
expect(tester.getBottomLeft(find.widgetWithText(CupertinoActionSheetAction, 'One')).dy,
|
||||
moreOrLessEquals(469.66666666666663));
|
||||
expect(tester.getBottomLeft(find.widgetWithText(CupertinoActionSheetAction, 'Two')).dy, 526.0);
|
||||
});
|
||||
|
||||
testWidgets('Enter/exit animation is correct', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
new CupertinoActionSheet(
|
||||
title: const Text('The title'),
|
||||
message: const Text('The message'),
|
||||
actions: <Widget>[
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('One'),
|
||||
onPressed: () {},
|
||||
),
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('Two'),
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
cancelButton: new CupertinoActionSheetAction(
|
||||
child: const Text('Cancel'),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Enter animation
|
||||
await tester.tap(find.text('Go'));
|
||||
|
||||
await tester.pump();
|
||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, 600.0);
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, closeTo(530.9, 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, closeTo(426.7, 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, closeTo(365.0, 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, closeTo(334.0, 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, closeTo(321.0, 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, closeTo(319.3, 0.1));
|
||||
|
||||
// Action sheet has reached final height
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, closeTo(319.3, 0.1));
|
||||
|
||||
// Exit animation
|
||||
await tester.tapAt(const Offset(20.0, 20.0));
|
||||
await tester.pump();
|
||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, closeTo(319.3, 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, closeTo(388.4, 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, closeTo(492.6, 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, closeTo(554.2, 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, closeTo(585.2, 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, closeTo(598.2, 0.1));
|
||||
|
||||
// Action sheet has disappeared
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(find.byType(CupertinoActionSheet), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Modal barrier is pressed during transition', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
new CupertinoActionSheet(
|
||||
title: const Text('The title'),
|
||||
message: const Text('The message'),
|
||||
actions: <Widget>[
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('One'),
|
||||
onPressed: () {},
|
||||
),
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('Two'),
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
cancelButton: new CupertinoActionSheetAction(
|
||||
child: const Text('Cancel'),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Enter animation
|
||||
await tester.tap(find.text('Go'));
|
||||
|
||||
await tester.pump();
|
||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, 600.0);
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, closeTo(530.9, 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, closeTo(426.7, 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, closeTo(365.0, 0.1));
|
||||
|
||||
// Exit animation
|
||||
await tester.tapAt(const Offset(20.0, 20.0));
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, closeTo(426.7, 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, closeTo(530.9, 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, 600.0);
|
||||
|
||||
// Action sheet has disappeared
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(find.byType(CupertinoActionSheet), findsNothing);
|
||||
});
|
||||
|
||||
|
||||
testWidgets('Action sheet semantics', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(
|
||||
createAppWithButtonThatLaunchesActionSheet(
|
||||
new CupertinoActionSheet(
|
||||
title: const Text('The title'),
|
||||
message: const Text('The message'),
|
||||
actions: <Widget>[
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('One'),
|
||||
onPressed: () {},
|
||||
),
|
||||
new CupertinoActionSheetAction(
|
||||
child: const Text('Two'),
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
cancelButton: new CupertinoActionSheetAction(
|
||||
child: const Text('Cancel'),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Go'));
|
||||
await tester.pump();
|
||||
|
||||
expect(
|
||||
semantics,
|
||||
hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.scopesRoute,
|
||||
SemanticsFlag.namesRoute,
|
||||
],
|
||||
label: 'Alert',
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
label: 'The title',
|
||||
),
|
||||
new TestSemantics(
|
||||
label: 'The message',
|
||||
),
|
||||
],
|
||||
),
|
||||
new TestSemantics(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isButton,
|
||||
],
|
||||
actions: <SemanticsAction>[
|
||||
SemanticsAction.tap,
|
||||
],
|
||||
label: 'One',
|
||||
),
|
||||
new TestSemantics(
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isButton,
|
||||
],
|
||||
actions: <SemanticsAction>[
|
||||
SemanticsAction.tap,
|
||||
],
|
||||
label: 'Two',
|
||||
),
|
||||
],
|
||||
),
|
||||
new TestSemantics(
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isButton,
|
||||
],
|
||||
actions: <SemanticsAction>[
|
||||
SemanticsAction.tap,
|
||||
],
|
||||
label: 'Cancel',
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
ignoreId: true,
|
||||
ignoreRect: true,
|
||||
ignoreTransform: true,
|
||||
),
|
||||
);
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
RenderBox findScrollableActionsSectionRenderBox(WidgetTester tester) {
|
||||
final RenderObject actionsSection = tester.renderObject(find.byElementPredicate(
|
||||
(Element element) {
|
||||
return element.widget.runtimeType.toString() == '_CupertinoAlertActionSection';
|
||||
}),
|
||||
);
|
||||
assert(actionsSection is RenderBox);
|
||||
return actionsSection;
|
||||
}
|
||||
|
||||
Widget createAppWithButtonThatLaunchesActionSheet(Widget actionSheet) {
|
||||
return new CupertinoApp(
|
||||
home: new Center(
|
||||
child: new Builder(builder: (BuildContext context) {
|
||||
return new CupertinoButton(
|
||||
onPressed: () {
|
||||
showCupertinoModalPopup<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return actionSheet;
|
||||
},
|
||||
);
|
||||
},
|
||||
child: const Text('Go'),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget boilerplate(Widget child) {
|
||||
return new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: child,
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user