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`.
|
/// To use, import `package:flutter/cupertino.dart`.
|
||||||
library cupertino;
|
library cupertino;
|
||||||
|
|
||||||
|
export 'src/cupertino/action_sheet.dart';
|
||||||
export 'src/cupertino/activity_indicator.dart';
|
export 'src/cupertino/activity_indicator.dart';
|
||||||
export 'src/cupertino/app.dart';
|
export 'src/cupertino/app.dart';
|
||||||
export 'src/cupertino/bottom_tab_bar.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.
|
// Barrier color for a Cupertino modal barrier.
|
||||||
const Color _kModalBarrierColor = Color(0x6604040F);
|
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.
|
// Offset from offscreen to the right to fully on screen.
|
||||||
final Tween<Offset> _kRightMiddleTween = new Tween<Offset>(
|
final Tween<Offset> _kRightMiddleTween = new Tween<Offset>(
|
||||||
begin: const Offset(1.0, 0.0),
|
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) {
|
Widget _buildCupertinoDialogTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
|
||||||
final CurvedAnimation fadeAnimation = new CurvedAnimation(
|
final CurvedAnimation fadeAnimation = new CurvedAnimation(
|
||||||
parent: animation,
|
parent: animation,
|
||||||
|
@ -70,6 +70,7 @@ class BoxDecoration extends Decoration {
|
|||||||
/// [BoxShape.circle].
|
/// [BoxShape.circle].
|
||||||
/// * If [boxShadow] is null, this decoration does not paint a shadow.
|
/// * If [boxShadow] is null, this decoration does not paint a shadow.
|
||||||
/// * If [gradient] is null, this decoration does not paint gradients.
|
/// * 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.
|
/// The [shape] argument must not be null.
|
||||||
const BoxDecoration({
|
const BoxDecoration({
|
||||||
@ -79,13 +80,20 @@ class BoxDecoration extends Decoration {
|
|||||||
this.borderRadius,
|
this.borderRadius,
|
||||||
this.boxShadow,
|
this.boxShadow,
|
||||||
this.gradient,
|
this.gradient,
|
||||||
|
this.backgroundBlendMode,
|
||||||
this.shape = BoxShape.rectangle,
|
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
|
@override
|
||||||
bool debugAssertIsValid() {
|
bool debugAssertIsValid() {
|
||||||
assert(shape != BoxShape.circle ||
|
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();
|
return super.debugAssertIsValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,6 +144,14 @@ class BoxDecoration extends Decoration {
|
|||||||
/// The [gradient] is drawn under the [image].
|
/// The [gradient] is drawn under the [image].
|
||||||
final Gradient gradient;
|
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
|
/// The shape to fill the background [color], [gradient], and [image] into and
|
||||||
/// to cast as the [boxShadow].
|
/// to cast as the [boxShadow].
|
||||||
///
|
///
|
||||||
@ -332,6 +348,8 @@ class _BoxDecorationPainter extends BoxPainter {
|
|||||||
if (_cachedBackgroundPaint == null ||
|
if (_cachedBackgroundPaint == null ||
|
||||||
(_decoration.gradient != null && _rectForCachedBackgroundPaint != rect)) {
|
(_decoration.gradient != null && _rectForCachedBackgroundPaint != rect)) {
|
||||||
final Paint paint = new Paint();
|
final Paint paint = new Paint();
|
||||||
|
if (_decoration.backgroundBlendMode != null)
|
||||||
|
paint.blendMode = _decoration.backgroundBlendMode;
|
||||||
if (_decoration.color != null)
|
if (_decoration.color != null)
|
||||||
paint.color = _decoration.color;
|
paint.color = _decoration.color;
|
||||||
if (_decoration.gradient != null) {
|
if (_decoration.gradient != null) {
|
||||||
|
@ -33,6 +33,7 @@ class ModalBarrier extends StatelessWidget {
|
|||||||
this.color,
|
this.color,
|
||||||
this.dismissible = true,
|
this.dismissible = true,
|
||||||
this.semanticsLabel,
|
this.semanticsLabel,
|
||||||
|
this.barrierSemanticsDismissible = true,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
/// If non-null, fill the barrier with this color.
|
/// If non-null, fill the barrier with this color.
|
||||||
@ -51,6 +52,13 @@ class ModalBarrier extends StatelessWidget {
|
|||||||
/// [ModalBarrier] built by [ModalRoute] pages.
|
/// [ModalBarrier] built by [ModalRoute] pages.
|
||||||
final bool dismissible;
|
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].
|
/// Semantics label used for the barrier if it is [dismissable].
|
||||||
///
|
///
|
||||||
/// The semantics label is read out by accessibility tools (e.g. TalkBack
|
/// The semantics label is read out by accessibility tools (e.g. TalkBack
|
||||||
@ -66,10 +74,12 @@ class ModalBarrier extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
assert(!dismissible || semanticsLabel == null || debugCheckHasDirectionality(context));
|
assert(!dismissible || semanticsLabel == null || debugCheckHasDirectionality(context));
|
||||||
final bool semanticsDismissible = dismissible && defaultTargetPlatform != TargetPlatform.android;
|
final bool semanticsDismissible = dismissible && defaultTargetPlatform != TargetPlatform.android;
|
||||||
|
final bool modalBarrierSemanticsDismissible = barrierSemanticsDismissible ?? semanticsDismissible;
|
||||||
return new BlockSemantics(
|
return new BlockSemantics(
|
||||||
child: new ExcludeSemantics(
|
child: new ExcludeSemantics(
|
||||||
// On Android, the back button is used to dismiss a modal.
|
// On Android, the back button is used to dismiss a modal. On iOS, some
|
||||||
excluding: !semanticsDismissible,
|
// modal barriers are not dismissible in accessibility mode.
|
||||||
|
excluding: !semanticsDismissible || !modalBarrierSemanticsDismissible,
|
||||||
child: new GestureDetector(
|
child: new GestureDetector(
|
||||||
onTapDown: (TapDownDetails details) {
|
onTapDown: (TapDownDetails details) {
|
||||||
if (dismissible)
|
if (dismissible)
|
||||||
@ -117,6 +127,7 @@ class AnimatedModalBarrier extends AnimatedWidget {
|
|||||||
Animation<Color> color,
|
Animation<Color> color,
|
||||||
this.dismissible = true,
|
this.dismissible = true,
|
||||||
this.semanticsLabel,
|
this.semanticsLabel,
|
||||||
|
this.barrierSemanticsDismissible,
|
||||||
}) : super(key: key, listenable: color);
|
}) : super(key: key, listenable: color);
|
||||||
|
|
||||||
/// If non-null, fill the barrier with this color.
|
/// If non-null, fill the barrier with this color.
|
||||||
@ -145,12 +156,20 @@ class AnimatedModalBarrier extends AnimatedWidget {
|
|||||||
/// [ModalBarrier] built by [ModalRoute] pages.
|
/// [ModalBarrier] built by [ModalRoute] pages.
|
||||||
final String semanticsLabel;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return new ModalBarrier(
|
return new ModalBarrier(
|
||||||
color: color?.value,
|
color: color?.value,
|
||||||
dismissible: dismissible,
|
dismissible: dismissible,
|
||||||
semanticsLabel: semanticsLabel,
|
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.
|
/// * [ModalBarrier], the widget that implements this feature.
|
||||||
bool get barrierDismissible;
|
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
|
/// The color to use for the modal barrier. If this is null, the barrier will
|
||||||
/// be transparent.
|
/// be transparent.
|
||||||
///
|
///
|
||||||
@ -1173,11 +1188,13 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
|
|||||||
color: color,
|
color: color,
|
||||||
dismissible: barrierDismissible, // changedInternalState is called if this updates
|
dismissible: barrierDismissible, // changedInternalState is called if this updates
|
||||||
semanticsLabel: barrierLabel, // changedInternalState is called if this updates
|
semanticsLabel: barrierLabel, // changedInternalState is called if this updates
|
||||||
|
barrierSemanticsDismissible: semanticsDismissible,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
barrier = new ModalBarrier(
|
barrier = new ModalBarrier(
|
||||||
dismissible: barrierDismissible, // changedInternalState is called if this updates
|
dismissible: barrierDismissible, // changedInternalState is called if this updates
|
||||||
semanticsLabel: barrierLabel, // changedInternalState is called if this updates
|
semanticsLabel: barrierLabel, // changedInternalState is called if this updates
|
||||||
|
barrierSemanticsDismissible: semanticsDismissible,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return new IgnorePointer(
|
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