Unverified Commit 96326d47 authored by Natalie Sampsell's avatar Natalie Sampsell Committed by GitHub

CupertinoActionSheet (#19232)

Adding CupertinoActionSheet, showCupertinoModalPopup
parent 21f22ed3
......@@ -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';
......
This diff is collapsed.
......@@ -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(
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment