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 @@ ...@@ -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';
......
This diff is collapsed.
...@@ -14,6 +14,9 @@ const double _kMinFlingVelocity = 1.0; // Screen widths per second. ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 ...@@ -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 ...@@ -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(
......
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