blob: ec61a288739a7409c67b1ef05bcf732bc4faddc2 [file] [log] [blame]
// Copyright 2019 The Flutter 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/material.dart';
/// Signature for a function that creates a widget that builds a
/// transition.
///
/// Used by [PopupRoute].
typedef _ModalTransitionBuilder = Widget Function(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
);
/// Displays a modal above the current contents of the app.
///
/// Content below the modal is dimmed with a [ModalBarrier].
///
/// The `context` argument is used to look up the [Navigator] for the
/// modal. It is only used when the method is called. Its corresponding widget
/// can be safely removed from the tree before the modal is closed.
///
/// The `configuration` argument is used to determine characteristics of the
/// modal route that will be displayed, such as the enter and exit
/// transitions, the duration of the transitions, and modal barrier
/// properties.
///
/// The `useRootNavigator` argument is used to determine whether to push the
/// modal to the [Navigator] furthest from or nearest to the given `context`.
/// By default, `useRootNavigator` is `true` and the modal route created by
/// this method is pushed to the root navigator. If the application has
/// multiple [Navigator] objects, it may be necessary to call
/// `Navigator.of(context, rootNavigator: true).pop(result)` to close the
/// modal rather than just `Navigator.pop(context, result)`.
///
/// Returns a [Future] that resolves to the value (if any) that was passed to
/// [Navigator.pop] when the modal was closed.
///
/// See also:
///
/// * [ModalConfiguration], which is the configuration object used to define
/// the modal's characteristics.
Future<T> showModal<T>({
@required BuildContext context,
@required ModalConfiguration configuration,
bool useRootNavigator = true,
WidgetBuilder builder,
}) {
assert(configuration != null);
assert(useRootNavigator != null);
String barrierLabel = configuration.barrierLabel;
// Avoid looking up [MaterialLocalizations.of(context).modalBarrierDismissLabel]
// if there is no dismissible barrier.
if (configuration.barrierDismissible && configuration.barrierLabel == null) {
barrierLabel = MaterialLocalizations.of(context).modalBarrierDismissLabel;
}
assert(!configuration.barrierDismissible || barrierLabel != null);
return Navigator.of(context, rootNavigator: useRootNavigator).push<T>(
_ModalRoute<T>(
barrierColor: configuration.barrierColor,
barrierDismissible: configuration.barrierDismissible,
barrierLabel: barrierLabel,
transitionBuilder: configuration.transitionBuilder,
transitionDuration: configuration.transitionDuration,
reverseTransitionDuration: configuration.reverseTransitionDuration,
builder: builder,
),
);
}
// A modal route that overlays a widget on the current route.
class _ModalRoute<T> extends PopupRoute<T> {
/// Creates a route with general modal route.
///
/// [barrierDismissible] configures whether or not tapping the modal's
/// scrim dismisses the modal. [barrierLabel] sets the semantic label for
/// a dismissible barrier. [barrierDismissible] cannot be null. If
/// [barrierDismissible] is true, the [barrierLabel] cannot be null.
///
/// [transitionBuilder] takes in a function that creates a widget. This
/// widget is typically used to configure the modal's transition.
_ModalRoute({
this.barrierColor,
this.barrierDismissible = true,
this.barrierLabel,
this.transitionDuration,
this.reverseTransitionDuration,
_ModalTransitionBuilder transitionBuilder,
@required this.builder,
}) : assert(barrierDismissible != null),
assert(!barrierDismissible || barrierLabel != null),
_transitionBuilder = transitionBuilder;
@override
final Color barrierColor;
@override
final bool barrierDismissible;
@override
final String barrierLabel;
@override
final Duration transitionDuration;
// TODO(shihaohong): Remove the override analyzer ignore once
// Flutter stable contains https://github.com/flutter/flutter/pull/48274.
@override
// ignore: override_on_non_overriding_member
final Duration reverseTransitionDuration;
/// The primary contents of the modal.
final WidgetBuilder builder;
final _ModalTransitionBuilder _transitionBuilder;
@override
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
final ThemeData theme = Theme.of(context);
return Semantics(
child: SafeArea(
child: Builder(
builder: (BuildContext context) {
final Widget child = Builder(builder: builder);
return theme != null ? Theme(data: theme, child: child) : child;
},
),
),
scopesRoute: true,
explicitChildNodes: true,
);
}
@override
Widget buildTransitions(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return _transitionBuilder(
context,
animation,
secondaryAnimation,
child,
);
}
}
/// A configuration object containing the properties needed to implement a
/// modal route.
///
/// The `barrierDismissible` argument is used to determine whether this route
/// can be dismissed by tapping the modal barrier. This argument defaults
/// to true. If `barrierDismissible` is true, a non-null `barrierLabel` must be
/// provided.
///
/// The `barrierLabel` argument is the semantic label used for a dismissible
/// barrier. This argument defaults to "Dismiss".
abstract class ModalConfiguration {
/// Creates a modal configuration object that provides the necessary
/// properties to implement a modal route.
///
/// [barrierDismissible] configures whether or not tapping the modal's
/// scrim dismisses the modal. [barrierLabel] sets the semantic label for
/// a dismissible barrier. [barrierDismissible] cannot be null. If
/// [barrierDismissible] is true, the [barrierLabel] cannot be null.
///
/// [transitionDuration] and [reverseTransitionDuration] determine the
/// duration of the transitions when the modal enters and exits the
/// application. [transitionDuration] and [reverseTransitionDuration]
/// cannot be null.
ModalConfiguration({
@required this.barrierColor,
@required this.barrierDismissible,
this.barrierLabel,
@required this.transitionDuration,
@required this.reverseTransitionDuration,
}) : assert(barrierColor != null),
assert(barrierDismissible != null),
assert(!barrierDismissible || barrierLabel != null),
assert(transitionDuration != null),
assert(reverseTransitionDuration != null);
/// The color to use for the modal barrier. If this is null, the barrier will
/// be transparent.
final Color barrierColor;
/// Whether you can dismiss this route by tapping the modal barrier.
final bool barrierDismissible;
/// The semantic label used for a dismissible barrier.
final String barrierLabel;
/// The duration of the transition running forwards.
final Duration transitionDuration;
/// The duration of the transition running in reverse.
final Duration reverseTransitionDuration;
/// A builder that defines how the route arrives on and leaves the screen.
///
/// The [buildTransitions] method is typically used to define transitions
/// that animate the new topmost route's comings and goings. When the
/// [Navigator] pushes a route on the top of its stack, the new route's
/// primary [animation] runs from 0.0 to 1.0. When the [Navigator] pops the
/// topmost route, e.g. because the use pressed the back button, the
/// primary animation runs from 1.0 to 0.0.
Widget transitionBuilder(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
);
}