Unverified Commit 582f35df authored by Hans Muller's avatar Hans Muller Committed by GitHub

PageTransitionsTheme, new MountainView page transition (#21715)

MaterialPageRoute transitions are now defined by the Theme. Added (optional) support for Android P style page transitions.
parent eadd59a9
...@@ -69,6 +69,7 @@ export 'src/material/material_localizations.dart'; ...@@ -69,6 +69,7 @@ export 'src/material/material_localizations.dart';
export 'src/material/mergeable_material.dart'; export 'src/material/mergeable_material.dart';
export 'src/material/outline_button.dart'; export 'src/material/outline_button.dart';
export 'src/material/page.dart'; export 'src/material/page.dart';
export 'src/material/page_transitions_theme.dart';
export 'src/material/paginated_data_table.dart'; export 'src/material/paginated_data_table.dart';
export 'src/material/popup_menu.dart'; export 'src/material/popup_menu.dart';
export 'src/material/progress_indicator.dart'; export 'src/material/progress_indicator.dart';
......
...@@ -92,7 +92,6 @@ class CupertinoPageRoute<T> extends PageRoute<T> { ...@@ -92,7 +92,6 @@ class CupertinoPageRoute<T> extends PageRoute<T> {
RouteSettings settings, RouteSettings settings,
this.maintainState = true, this.maintainState = true,
bool fullscreenDialog = false, bool fullscreenDialog = false,
this.hostRoute,
}) : assert(builder != null), }) : assert(builder != null),
assert(maintainState != null), assert(maintainState != null),
assert(fullscreenDialog != null), assert(fullscreenDialog != null),
...@@ -151,16 +150,6 @@ class CupertinoPageRoute<T> extends PageRoute<T> { ...@@ -151,16 +150,6 @@ class CupertinoPageRoute<T> extends PageRoute<T> {
@override @override
final bool maintainState; final bool maintainState;
/// The route that owns this one.
///
/// The [MaterialPageRoute] creates a [CupertinoPageRoute] to handle iOS-style
/// navigation. When this happens, the [MaterialPageRoute] is the [hostRoute]
/// of this [CupertinoPageRoute].
///
/// The [hostRoute] is responsible for calling [dispose] on the route. When
/// there is a [hostRoute], the [CupertinoPageRoute] must not be [install]ed.
final PageRoute<T> hostRoute;
@override @override
Duration get transitionDuration => const Duration(milliseconds: 350); Duration get transitionDuration => const Duration(milliseconds: 350);
...@@ -181,52 +170,43 @@ class CupertinoPageRoute<T> extends PageRoute<T> { ...@@ -181,52 +170,43 @@ class CupertinoPageRoute<T> extends PageRoute<T> {
return nextRoute is CupertinoPageRoute && !nextRoute.fullscreenDialog; return nextRoute is CupertinoPageRoute && !nextRoute.fullscreenDialog;
} }
@override
void install(OverlayEntry insertionPoint) {
assert(() {
if (hostRoute == null)
return true;
throw FlutterError(
'Cannot install a subsidiary route (one with a hostRoute).\n'
'This route ($this) cannot be installed, because it has a host route ($hostRoute).'
);
}());
super.install(insertionPoint);
}
@override @override
void dispose() { void dispose() {
_backGestureController?.dispose(); _popGestureInProgress.remove(this);
_backGestureController = null;
super.dispose(); super.dispose();
} }
_CupertinoBackGestureController<T> _backGestureController; /// True if a Cupertino pop gesture is currently underway for [route].
/// Whether a pop gesture is currently underway.
/// ///
/// This starts returning true when pop gesture is started by the user. It /// See also:
/// returns false if that has not yet occurred or if the most recent such ///
/// gesture has completed. /// * [popGestureEnabled], which returns true if a user-triggered pop gesture
/// would be allowed.
static bool isPopGestureInProgress(PageRoute<dynamic> route) => _popGestureInProgress.contains(route);
static final Set<PageRoute<dynamic>> _popGestureInProgress = Set<PageRoute<dynamic>>();
/// True if a Cupertino pop gesture is currently underway for this route.
/// ///
/// See also: /// See also:
/// ///
/// * [popGestureEnabled], which returns whether a pop gesture is appropriate /// * [isPopGestureInProgress], which returns true if a Cupertino pop gesture
/// in the first place. /// is currently underway for specific route.
bool get popGestureInProgress => _backGestureController != null; /// * [popGestureEnabled], which returns true if a user-triggered pop gesture
/// would be allowed.
bool get popGestureInProgress => isPopGestureInProgress(this);
/// Whether a pop gesture can be started by the user. /// Whether a pop gesture can be started by the user.
/// ///
/// This returns true if the user can edge-swipe to a previous route, /// Returns true if the user can edge-swipe to a previous route.
/// otherwise false.
/// ///
/// This will return false once [popGestureInProgress] is true, but /// Returns false once [isPopGestureInProgress] is true, but
/// [popGestureInProgress] can only become true if [popGestureEnabled] was /// [isPopGestureInProgress] can only become true if [popGestureEnabled] was
/// true first. /// true first.
/// ///
/// This should only be used between frames, not during build. /// This should only be used between frames, not during build.
bool get popGestureEnabled { bool get popGestureEnabled => _isPopGestureEnabled(this);
final PageRoute<T> route = hostRoute ?? this;
static bool _isPopGestureEnabled<T>(PageRoute<T> route) {
// If there's nothing to go back to, then obviously we don't support // If there's nothing to go back to, then obviously we don't support
// the back gesture. // the back gesture.
if (route.isFirst) if (route.isFirst)
...@@ -240,54 +220,19 @@ class CupertinoPageRoute<T> extends PageRoute<T> { ...@@ -240,54 +220,19 @@ class CupertinoPageRoute<T> extends PageRoute<T> {
if (route.hasScopedWillPopCallback) if (route.hasScopedWillPopCallback)
return false; return false;
// Fullscreen dialogs aren't dismissable by back swipe. // Fullscreen dialogs aren't dismissable by back swipe.
if (fullscreenDialog) if (route.fullscreenDialog)
return false; return false;
// If we're in an animation already, we cannot be manually swiped. // If we're in an animation already, we cannot be manually swiped.
if (route.controller.status != AnimationStatus.completed) if (route.controller.status != AnimationStatus.completed)
return false; return false;
// If we're in a gesture already, we cannot start another. // If we're in a gesture already, we cannot start another.
if (popGestureInProgress) if (_popGestureInProgress.contains(route))
return false; return false;
// Looks like a back gesture would be welcome! // Looks like a back gesture would be welcome!
return true; return true;
} }
/// Begin dismissing this route from a horizontal swipe, if appropriate.
///
/// Swiping will be disabled if the page is a fullscreen dialog or if
/// dismissals can be overridden because a [WillPopCallback] was
/// defined for the route.
///
/// When this method decides a pop gesture is appropriate, it returns a
/// [CupertinoBackGestureController].
///
/// See also:
///
/// * [hasScopedWillPopCallback], which is true if a `willPop` callback
/// is defined for this route.
/// * [popGestureEnabled], which returns whether a pop gesture is
/// appropriate.
/// * [Route.startPopGesture], which describes the contract that this method
/// must implement.
_CupertinoBackGestureController<T> _startPopGesture() {
assert(!popGestureInProgress);
assert(popGestureEnabled);
final PageRoute<T> route = hostRoute ?? this;
_backGestureController = _CupertinoBackGestureController<T>(
navigator: route.navigator,
controller: route.controller,
onEnded: _endPopGesture,
);
return _backGestureController;
}
void _endPopGesture() {
// In practice this only gets called if for some reason popping the route
// did not cause this route to get disposed.
_backGestureController?.dispose();
_backGestureController = null;
}
@override @override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
final Widget result = Semantics( final Widget result = Semantics(
...@@ -307,9 +252,49 @@ class CupertinoPageRoute<T> extends PageRoute<T> { ...@@ -307,9 +252,49 @@ class CupertinoPageRoute<T> extends PageRoute<T> {
return result; return result;
} }
@override // Called by _CupertinoBackGestureDetector when a pop ("back") drag start
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) { // gesture is detected. The returned controller handles all of the subsquent
if (fullscreenDialog) { // drag events.
static _CupertinoBackGestureController<T> _startPopGesture<T>(PageRoute<T> route) {
assert(!_popGestureInProgress.contains(route));
assert(_isPopGestureEnabled(route));
_popGestureInProgress.add(route);
_CupertinoBackGestureController<T> backController;
backController = _CupertinoBackGestureController<T>(
navigator: route.navigator,
controller: route.controller,
onEnded: () {
backController?.dispose();
backController = null;
_popGestureInProgress.remove(route);
},
);
return backController;
}
/// Returns a [CupertinoFullscreenDialogTransition] if [route] is a full
/// screen dialog, otherwise a [CupertinoPageTransition] is returned.
///
/// Used by [CupertinoPageRoute.buildTransitions].
///
/// This method can be applied to any [PageRoute], not just
/// [CupertinoPageRoute]. It's typically used to provide a Cupertino style
/// horizontal transition for material widgets when the target platform
/// is [TargetPlatform.iOS].
///
/// See also:
///
/// * [CupertinoPageTransitionsBuilder], which uses this method to define a
/// [PageTransitionsBuilder] for the [PageTransitionsTheme].
static Widget buildPageTransitions<T>(
PageRoute<T> route,
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
if (route.fullscreenDialog) {
return CupertinoFullscreenDialogTransition( return CupertinoFullscreenDialogTransition(
animation: animation, animation: animation,
child: child, child: child,
...@@ -320,16 +305,21 @@ class CupertinoPageRoute<T> extends PageRoute<T> { ...@@ -320,16 +305,21 @@ class CupertinoPageRoute<T> extends PageRoute<T> {
secondaryRouteAnimation: secondaryAnimation, secondaryRouteAnimation: secondaryAnimation,
// In the middle of a back gesture drag, let the transition be linear to // In the middle of a back gesture drag, let the transition be linear to
// match finger motions. // match finger motions.
linearTransition: popGestureInProgress, linearTransition: _popGestureInProgress.contains(route),
child: _CupertinoBackGestureDetector<T>( child: _CupertinoBackGestureDetector<T>(
enabledCallback: () => popGestureEnabled, enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: _startPopGesture, onStartPopGesture: () => _startPopGesture<T>(route),
child: child, child: child,
), ),
); );
} }
} }
@override
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
return buildPageTransitions<T>(this, context, animation, secondaryAnimation, child);
}
@override @override
String get debugLabel => '${super.debugLabel}(${settings.name})'; String get debugLabel => '${super.debugLabel}(${settings.name})';
} }
...@@ -385,11 +375,10 @@ class CupertinoPageTransition extends StatelessWidget { ...@@ -385,11 +375,10 @@ class CupertinoPageTransition extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasDirectionality(context)); assert(debugCheckHasDirectionality(context));
final TextDirection textDirection = Directionality.of(context); final TextDirection textDirection = Directionality.of(context);
// TODO(ianh): tell the transform to be un-transformed for hit testing
// but not while being controlled by a gesture.
return SlideTransition( return SlideTransition(
position: _secondaryPositionAnimation, position: _secondaryPositionAnimation,
textDirection: textDirection, textDirection: textDirection,
transformHitTests: false,
child: SlideTransition( child: SlideTransition(
position: _primaryPositionAnimation, position: _primaryPositionAnimation,
textDirection: textDirection, textDirection: textDirection,
...@@ -917,4 +906,4 @@ Future<T> showCupertinoDialog<T>({ ...@@ -917,4 +906,4 @@ Future<T> showCupertinoDialog<T>({
}, },
transitionBuilder: _buildCupertinoDialogTransitions, transitionBuilder: _buildCupertinoDialogTransitions,
); );
} }
\ No newline at end of file
...@@ -5,47 +5,9 @@ ...@@ -5,47 +5,9 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'page_transitions_theme.dart';
import 'theme.dart'; import 'theme.dart';
// Fractional offset from 1/4 screen below the top to fully on screen.
final Animatable<Offset> _kBottomUpTween = Tween<Offset>(
begin: const Offset(0.0, 0.25),
end: Offset.zero,
);
// Used for Android and Fuchsia.
class _MountainViewPageTransition extends StatelessWidget {
_MountainViewPageTransition({
Key key,
@required bool fade,
@required Animation<double> routeAnimation, // The route's linear 0.0 - 1.0 animation.
@required this.child,
}) : _positionAnimation = routeAnimation.drive(_kBottomUpTween.chain(_fastOutSlowInTween)),
_opacityAnimation = fade
? routeAnimation.drive(_easeInTween) // Eyeballed from other Material apps.
: const AlwaysStoppedAnimation<double>(1.0),
super(key: key);
static final Animatable<double> _fastOutSlowInTween = CurveTween(curve: Curves.fastOutSlowIn);
static final Animatable<double> _easeInTween = CurveTween(curve: Curves.easeIn);
final Animation<Offset> _positionAnimation;
final Animation<double> _opacityAnimation;
final Widget child;
@override
Widget build(BuildContext context) {
// TODO(ianh): tell the transform to be un-transformed for hit testing
return SlideTransition(
position: _positionAnimation,
child: FadeTransition(
opacity: _opacityAnimation,
child: child,
),
);
}
}
/// A modal route that replaces the entire screen with a platform-adaptive /// A modal route that replaces the entire screen with a platform-adaptive
/// transition. /// transition.
/// ///
...@@ -71,16 +33,22 @@ class _MountainViewPageTransition extends StatelessWidget { ...@@ -71,16 +33,22 @@ class _MountainViewPageTransition extends StatelessWidget {
/// ///
/// See also: /// See also:
/// ///
/// * [CupertinoPageRoute], which this [PageRoute] delegates transition /// * [PageTransitionsTheme], which defines the default page transitions used
/// animations to for iOS. /// by [MaterialPageRoute.buildTransitions].
///
class MaterialPageRoute<T> extends PageRoute<T> { class MaterialPageRoute<T> extends PageRoute<T> {
/// Creates a page route for use in a material design app. /// Construct a MaterialPageRoute whose contents are defined by [builder].
///
/// The values of [builder], [maintainState], and [fullScreenDialog] must not
/// be null.
MaterialPageRoute({ MaterialPageRoute({
@required this.builder, @required this.builder,
RouteSettings settings, RouteSettings settings,
this.maintainState = true, this.maintainState = true,
bool fullscreenDialog = false, bool fullscreenDialog = false,
}) : assert(builder != null), }) : assert(builder != null),
assert(maintainState != null),
assert(fullscreenDialog != null),
super(settings: settings, fullscreenDialog: fullscreenDialog) { super(settings: settings, fullscreenDialog: fullscreenDialog) {
// ignore: prefer_asserts_in_initializer_lists , https://github.com/dart-lang/sdk/issues/31223 // ignore: prefer_asserts_in_initializer_lists , https://github.com/dart-lang/sdk/issues/31223
assert(opaque); assert(opaque);
...@@ -92,26 +60,6 @@ class MaterialPageRoute<T> extends PageRoute<T> { ...@@ -92,26 +60,6 @@ class MaterialPageRoute<T> extends PageRoute<T> {
@override @override
final bool maintainState; final bool maintainState;
/// A delegate PageRoute to which iOS themed page operations are delegated to.
/// It's lazily created on first use.
CupertinoPageRoute<T> get _cupertinoPageRoute {
assert(_useCupertinoTransitions);
_internalCupertinoPageRoute ??= CupertinoPageRoute<T>(
builder: builder, // Not used.
fullscreenDialog: fullscreenDialog,
hostRoute: this,
);
return _internalCupertinoPageRoute;
}
CupertinoPageRoute<T> _internalCupertinoPageRoute;
/// Whether we should currently be using Cupertino transitions. This is true
/// if the theme says we're on iOS, or if we're in an active gesture.
bool get _useCupertinoTransitions {
return _internalCupertinoPageRoute?.popGestureInProgress == true
|| Theme.of(navigator.context).platform == TargetPlatform.iOS;
}
@override @override
Duration get transitionDuration => const Duration(milliseconds: 300); Duration get transitionDuration => const Duration(milliseconds: 300);
...@@ -133,12 +81,6 @@ class MaterialPageRoute<T> extends PageRoute<T> { ...@@ -133,12 +81,6 @@ class MaterialPageRoute<T> extends PageRoute<T> {
|| (nextRoute is CupertinoPageRoute && !nextRoute.fullscreenDialog); || (nextRoute is CupertinoPageRoute && !nextRoute.fullscreenDialog);
} }
@override
void dispose() {
_internalCupertinoPageRoute?.dispose();
super.dispose();
}
@override @override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
final Widget result = Semantics( final Widget result = Semantics(
...@@ -160,15 +102,8 @@ class MaterialPageRoute<T> extends PageRoute<T> { ...@@ -160,15 +102,8 @@ class MaterialPageRoute<T> extends PageRoute<T> {
@override @override
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) { Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
if (_useCupertinoTransitions) { final PageTransitionsTheme theme = Theme.of(context).pageTransitionsTheme;
return _cupertinoPageRoute.buildTransitions(context, animation, secondaryAnimation, child); return theme.buildTransitions<T>(this, context, animation, secondaryAnimation, child);
} else {
return _MountainViewPageTransition(
routeAnimation: animation,
child: child,
fade: true,
);
}
} }
@override @override
......
// 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/foundation.dart';
import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'theme.dart';
// Slides the page upwards and fades it in, starting from 1/4 screen
// below the top.
class _FadeUpwardsPageTransition extends StatelessWidget {
_FadeUpwardsPageTransition({
Key key,
@required Animation<double> routeAnimation, // The route's linear 0.0 - 1.0 animation.
@required this.child,
}) : _positionAnimation = routeAnimation.drive(_bottomUpTween.chain(_fastOutSlowInTween)),
_opacityAnimation = routeAnimation.drive(_easeInTween),
super(key: key);
// Fractional offset from 1/4 screen below the top to fully on screen.
static final Tween<Offset> _bottomUpTween = Tween<Offset>(
begin: const Offset(0.0, 0.25),
end: Offset.zero,
);
static final Animatable<double> _fastOutSlowInTween = CurveTween(curve: Curves.fastOutSlowIn);
static final Animatable<double> _easeInTween = CurveTween(curve: Curves.easeIn);
final Animation<Offset> _positionAnimation;
final Animation<double> _opacityAnimation;
final Widget child;
@override
Widget build(BuildContext context) {
return SlideTransition(
position: _positionAnimation,
// TODO(ianh): tell the transform to be un-transformed for hit testing
child: FadeTransition(
opacity: _opacityAnimation,
child: child,
),
);
}
}
// This transition is intended to match the default for Android P.
class _OpenUpwardsPageTransition extends StatelessWidget {
const _OpenUpwardsPageTransition({
Key key,
this.animation,
this.secondaryAnimation,
this.child,
}) : super(key: key);
// The new page slides upwards just a little as its clip
// rectangle exposes the page from bottom to top.
static final Tween<Offset> _primaryTranslationTween = Tween<Offset>(
begin: const Offset(0.0, 0.05),
end: Offset.zero,
);
// The old page slides upwards a little as the new page appears.
static final Tween<Offset> _secondaryTranslationTween = Tween<Offset>(
begin: Offset.zero,
end: const Offset(0.0, -0.025),
);
// The scrim obscures the old page by becoming increasingly opaque.
static final Tween<double> _scrimOpacityTween = Tween<double>(
begin: 0.0,
end: 0.25,
);
// Used by all of the transition animations.
static const Curve _transitionCurve = Cubic(0.20, 0.00, 0.00, 1.00);
final Animation<double> animation;
final Animation<double> secondaryAnimation;
final Widget child;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final Size size = constraints.biggest;
final CurvedAnimation primaryAnimation = CurvedAnimation(
parent: animation,
curve: _transitionCurve,
reverseCurve: _transitionCurve.flipped,
);
// Gradually expose the new page from bottom to top.
final Animation<double> clipAnimation = Tween<double>(
begin: 0.0,
end: size.height,
).animate(primaryAnimation);
final Animation<double> opacityAnimation = _scrimOpacityTween.animate(primaryAnimation);
final Animation<Offset> primaryTranslationAnimation = _primaryTranslationTween.animate(primaryAnimation);
final Animation<Offset> secondaryTranslationAnimation = _secondaryTranslationTween.animate(
CurvedAnimation(
parent: secondaryAnimation,
curve: _transitionCurve,
reverseCurve: _transitionCurve.flipped,
),
);
return AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget child) {
return Container(
color: Colors.black.withOpacity(opacityAnimation.value),
alignment: Alignment.bottomLeft,
child: ClipRect(
child: SizedBox(
height: clipAnimation.value,
child: OverflowBox(
alignment: Alignment.bottomLeft,
maxHeight: size.height,
child: child,
),
),
),
);
},
child: AnimatedBuilder(
animation: secondaryAnimation,
child: FractionalTranslation(
translation: primaryTranslationAnimation.value,
child: child,
),
builder: (BuildContext context, Widget child) {
return FractionalTranslation(
translation: secondaryTranslationAnimation.value,
child: child,
);
},
),
);
},
);
}
}
/// Used by [PageTransitionsTheme] to define a [MaterialPageRoute] page
/// transition animation.
///
/// Apps can configure the map of builders for [ThemeData.platformTheme]
/// to customize the default [MaterialPageRoute] page transition animation
/// for different platforms.
///
/// See also:
///
/// * [FadeUpwardsPageTransitionsBuilder], which defines a default page transition.
/// * [OpenUpwardsPageTransitionsBuilder], which defines a page transition
/// that's similar to the one provided by Android P.
/// * [CupertinoPageTransitionsBuilder], which defines a horizontal page
/// transition that matches native iOS page transitions.
abstract class PageTransitionsBuilder {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const PageTransitionsBuilder();
/// Wraps the child with one or more transition widgets which define how [route]
/// arrives on and leaves the screen.
///
/// The [MaterialPageRoute.buildTransitions] method looks up the current
/// current [PageTransitionsTheme] with `Theme.of(context).pageTransitionsTheme`
/// and delegates to this method with a [PageTransitionsBuilder] based
/// on the theme's [ThemeData.platform].
Widget buildTransitions<T>(
PageRoute<T> route,
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
);
}
/// Used by [PageTransitionsTheme] to define a default [MaterialPageRoute] page
/// transition animation.
///
/// The default animation fades the new page in while translating it upwards,
/// starting from about 25% below the top of the screen.
///
/// See also:
///
/// * [OpenUpwardsPageTransitionsBuilder], which defines a page transition
/// that's similar to the one provided by Android P.
/// * [CupertinoPageTransitionsBuilder], which defines a horizontal page
/// transition that matches native iOS page transitions.
class FadeUpwardsPageTransitionsBuilder extends PageTransitionsBuilder {
/// Construct a [FadeUpwardsPageTransitionsBuilder].
const FadeUpwardsPageTransitionsBuilder();
@override
Widget buildTransitions<T>(
PageRoute<T> route,
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return _FadeUpwardsPageTransition(routeAnimation: animation, child: child);
}
}
/// Used by [PageTransitionsTheme] to define a vertical [MaterialPageRoute] page
/// transition animation that looks like the default page transition
/// used on Android P.
///
/// See also:
/// * [FadeUpwardsPageTransitionsBuilder], which defines a default page transition.
/// * [CupertinoPageTransitionsBuilder], which defines a horizontal page
/// transition that matches native iOS page transitions.
class OpenUpwardsPageTransitionsBuilder extends PageTransitionsBuilder {
/// Construct a [OpenUpwardsPageTransitionsBuilder].
const OpenUpwardsPageTransitionsBuilder();
@override
Widget buildTransitions<T>(
PageRoute<T> route,
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return _OpenUpwardsPageTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
child: child,
);
}
}
/// Used by [PageTransitionsTheme] to define a horizontal [MaterialPageRoute]
/// page transition animation that matches native iOS page transitions.
///
/// See also:
///
/// * [FadeUpwardsPageTransitionsBuilder], which defines a default page transition.
/// * [OpenUpwardsPageTransitionsBuilder], which defines a page transition
/// that's similar to the one provided by Android P.
class CupertinoPageTransitionsBuilder extends PageTransitionsBuilder {
/// Construct a [CupertinoPageTransitionsBuilder].
const CupertinoPageTransitionsBuilder();
@override
Widget buildTransitions<T>(
PageRoute<T> route,
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return CupertinoPageRoute.buildPageTransitions<T>(route, context, animation, secondaryAnimation, child);
}
}
/// Defines the page transition animations used by [MaterialPageRoute]
/// for different [TargetPlatform]s.
///
/// The [MaterialPageRoute.buildTransitions] method looks up the current
/// current [PageTransitionsTheme] with `Theme.of(context).pageTransitionsTheme`
/// and delegates to [buildTransitions].
///
/// If a builder with a matching platform is not found, then the
/// [FadeUpwardsPageTransitionsBuilder] is used.
///
/// See also:
///
/// * [ThemeData.pageTransitionsTheme], which defines the default page
/// transitions for the overall theme.
/// * [FadeUpwardsPageTransitionsBuilder], which defines a default page transition.
/// * [OpenUpwardsPageTransitionsBuilder], which defines a page transition
/// that's similar to the one provided by Android P.
/// * [CupertinoPageTransitionsBuilder], which defines a horizontal page
/// transition that matches native iOS page transitions.
@immutable
class PageTransitionsTheme extends Diagnosticable {
/// Construct a PageTransitionsTheme.
///
/// By default the list of builders is: [FadeUpwardsPageTransitionsBuilder],
/// [CupertinoPageTransitionsBuilder] for [TargetPlatform.android]
/// and [TargetPlatform.iOS] respectively.
const PageTransitionsTheme({ Map<TargetPlatform, PageTransitionsBuilder> builders }) : _builders = builders;
static const Map<TargetPlatform, PageTransitionsBuilder> _defaultBuilders = <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
};
/// The [PageTransitionsBuilder]s supported by this theme.
Map<TargetPlatform, PageTransitionsBuilder> get builders => _builders ?? _defaultBuilders;
final Map<TargetPlatform, PageTransitionsBuilder> _builders;
/// Delegates to the builder for the current [ThemeData.platform]
/// or [FadeUpwardsPageTransitionsBuilder].
///
/// [MaterialPageRoute.buildTransitions] delegates to this method.
Widget buildTransitions<T>(
PageRoute<T> route,
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
TargetPlatform platform = Theme.of(context).platform;
if (CupertinoPageRoute.isPopGestureInProgress(route))
platform = TargetPlatform.iOS;
final PageTransitionsBuilder matchingBuilder =
builders[platform] ?? const FadeUpwardsPageTransitionsBuilder();
return matchingBuilder.buildTransitions<T>(route, context, animation, secondaryAnimation, child);
}
// Just used to the buidlers Map to a list with one PageTransitionsBuilder per platform
// for the operator == overload.
List<PageTransitionsBuilder> _all(Map<TargetPlatform, PageTransitionsBuilder> builders) {
return TargetPlatform.values.map((TargetPlatform platform) => builders[platform]).toList();
}
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
final PageTransitionsTheme typedOther = other;
if (identical(builders, other.builders))
return true;
return listEquals<PageTransitionsBuilder>(_all(builders), _all(typedOther.builders));
}
@override
int get hashCode => hashList(_all(builders));
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(
DiagnosticsProperty<Map<TargetPlatform, PageTransitionsBuilder>>(
'builders',
builders,
defaultValue: PageTransitionsTheme._defaultBuilders
),
);
}
}
...@@ -14,6 +14,7 @@ import 'colors.dart'; ...@@ -14,6 +14,7 @@ import 'colors.dart';
import 'ink_splash.dart'; import 'ink_splash.dart';
import 'ink_well.dart' show InteractiveInkFeatureFactory; import 'ink_well.dart' show InteractiveInkFeatureFactory;
import 'input_decorator.dart'; import 'input_decorator.dart';
import 'page_transitions_theme.dart';
import 'slider_theme.dart'; import 'slider_theme.dart';
import 'typography.dart'; import 'typography.dart';
...@@ -143,6 +144,7 @@ class ThemeData extends Diagnosticable { ...@@ -143,6 +144,7 @@ class ThemeData extends Diagnosticable {
ChipThemeData chipTheme, ChipThemeData chipTheme,
TargetPlatform platform, TargetPlatform platform,
MaterialTapTargetSize materialTapTargetSize, MaterialTapTargetSize materialTapTargetSize,
PageTransitionsTheme pageTransitionsTheme,
}) { }) {
materialTapTargetSize ??= MaterialTapTargetSize.padded; materialTapTargetSize ??= MaterialTapTargetSize.padded;
brightness ??= Brightness.light; brightness ??= Brightness.light;
...@@ -182,6 +184,7 @@ class ThemeData extends Diagnosticable { ...@@ -182,6 +184,7 @@ class ThemeData extends Diagnosticable {
hintColor ??= isDark ? const Color(0x80FFFFFF) : const Color(0x8A000000); hintColor ??= isDark ? const Color(0x80FFFFFF) : const Color(0x8A000000);
errorColor ??= Colors.red[700]; errorColor ??= Colors.red[700];
inputDecorationTheme ??= const InputDecorationTheme(); inputDecorationTheme ??= const InputDecorationTheme();
pageTransitionsTheme ??= const PageTransitionsTheme();
primaryIconTheme ??= primaryIsDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black); primaryIconTheme ??= primaryIsDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black);
accentIconTheme ??= accentIsDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black); accentIconTheme ??= accentIsDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black);
iconTheme ??= isDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black87); iconTheme ??= isDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black87);
...@@ -209,6 +212,7 @@ class ThemeData extends Diagnosticable { ...@@ -209,6 +212,7 @@ class ThemeData extends Diagnosticable {
brightness: brightness, brightness: brightness,
labelStyle: textTheme.body2, labelStyle: textTheme.body2,
); );
return ThemeData.raw( return ThemeData.raw(
brightness: brightness, brightness: brightness,
primaryColor: primaryColor, primaryColor: primaryColor,
...@@ -251,6 +255,7 @@ class ThemeData extends Diagnosticable { ...@@ -251,6 +255,7 @@ class ThemeData extends Diagnosticable {
chipTheme: chipTheme, chipTheme: chipTheme,
platform: platform, platform: platform,
materialTapTargetSize: materialTapTargetSize, materialTapTargetSize: materialTapTargetSize,
pageTransitionsTheme: pageTransitionsTheme,
); );
} }
...@@ -261,6 +266,9 @@ class ThemeData extends Diagnosticable { ...@@ -261,6 +266,9 @@ class ThemeData extends Diagnosticable {
/// create intermediate themes based on two themes created with the /// create intermediate themes based on two themes created with the
/// [new ThemeData] constructor. /// [new ThemeData] constructor.
const ThemeData.raw({ const ThemeData.raw({
// Warning: make sure these properties are in the exact same order as in
// operator == and in the hashValues method and in the order of fields
// in this class, and in the lerp() method.
@required this.brightness, @required this.brightness,
@required this.primaryColor, @required this.primaryColor,
@required this.primaryColorBrightness, @required this.primaryColorBrightness,
...@@ -302,6 +310,7 @@ class ThemeData extends Diagnosticable { ...@@ -302,6 +310,7 @@ class ThemeData extends Diagnosticable {
@required this.chipTheme, @required this.chipTheme,
@required this.platform, @required this.platform,
@required this.materialTapTargetSize, @required this.materialTapTargetSize,
@required this.pageTransitionsTheme,
}) : assert(brightness != null), }) : assert(brightness != null),
assert(primaryColor != null), assert(primaryColor != null),
assert(primaryColorBrightness != null), assert(primaryColorBrightness != null),
...@@ -341,7 +350,8 @@ class ThemeData extends Diagnosticable { ...@@ -341,7 +350,8 @@ class ThemeData extends Diagnosticable {
assert(sliderTheme != null), assert(sliderTheme != null),
assert(chipTheme != null), assert(chipTheme != null),
assert(platform != null), assert(platform != null),
assert(materialTapTargetSize != null); assert(materialTapTargetSize != null),
assert(pageTransitionsTheme != null);
/// A default light blue theme. /// A default light blue theme.
/// ///
...@@ -543,6 +553,13 @@ class ThemeData extends Diagnosticable { ...@@ -543,6 +553,13 @@ class ThemeData extends Diagnosticable {
/// Configures the hit test size of certain Material widgets. /// Configures the hit test size of certain Material widgets.
final MaterialTapTargetSize materialTapTargetSize; final MaterialTapTargetSize materialTapTargetSize;
/// Default [MaterialPageRoute] transitions per [TargetPlatform].
///
/// [MaterialPageRoute.buildTransitions] delegates to a [PageTransitionsBuilder]
/// whose [PageTransitionsBuilder.platform] matches [platform]. If a matching
/// builder is not found, a builder whose platform is null is used.
final PageTransitionsTheme pageTransitionsTheme;
/// Creates a copy of this theme but with the given fields replaced with the new values. /// Creates a copy of this theme but with the given fields replaced with the new values.
ThemeData copyWith({ ThemeData copyWith({
Brightness brightness, Brightness brightness,
...@@ -586,6 +603,7 @@ class ThemeData extends Diagnosticable { ...@@ -586,6 +603,7 @@ class ThemeData extends Diagnosticable {
ChipThemeData chipTheme, ChipThemeData chipTheme,
TargetPlatform platform, TargetPlatform platform,
MaterialTapTargetSize materialTapTargetSize, MaterialTapTargetSize materialTapTargetSize,
PageTransitionsTheme pageTransitionsTheme,
}) { }) {
return ThemeData.raw( return ThemeData.raw(
brightness: brightness ?? this.brightness, brightness: brightness ?? this.brightness,
...@@ -629,6 +647,7 @@ class ThemeData extends Diagnosticable { ...@@ -629,6 +647,7 @@ class ThemeData extends Diagnosticable {
chipTheme: chipTheme ?? this.chipTheme, chipTheme: chipTheme ?? this.chipTheme,
platform: platform ?? this.platform, platform: platform ?? this.platform,
materialTapTargetSize: materialTapTargetSize ?? this.materialTapTargetSize, materialTapTargetSize: materialTapTargetSize ?? this.materialTapTargetSize,
pageTransitionsTheme: pageTransitionsTheme ?? this.pageTransitionsTheme,
); );
} }
...@@ -723,6 +742,8 @@ class ThemeData extends Diagnosticable { ...@@ -723,6 +742,8 @@ class ThemeData extends Diagnosticable {
primaryColorLight: Color.lerp(a.primaryColorLight, b.primaryColorLight, t), primaryColorLight: Color.lerp(a.primaryColorLight, b.primaryColorLight, t),
primaryColorDark: Color.lerp(a.primaryColorDark, b.primaryColorDark, t), primaryColorDark: Color.lerp(a.primaryColorDark, b.primaryColorDark, t),
canvasColor: Color.lerp(a.canvasColor, b.canvasColor, t), canvasColor: Color.lerp(a.canvasColor, b.canvasColor, t),
accentColor: Color.lerp(a.accentColor, b.accentColor, t),
accentColorBrightness: t < 0.5 ? a.accentColorBrightness : b.accentColorBrightness,
scaffoldBackgroundColor: Color.lerp(a.scaffoldBackgroundColor, b.scaffoldBackgroundColor, t), scaffoldBackgroundColor: Color.lerp(a.scaffoldBackgroundColor, b.scaffoldBackgroundColor, t),
bottomAppBarColor: Color.lerp(a.bottomAppBarColor, b.bottomAppBarColor, t), bottomAppBarColor: Color.lerp(a.bottomAppBarColor, b.bottomAppBarColor, t),
cardColor: Color.lerp(a.cardColor, b.cardColor, t), cardColor: Color.lerp(a.cardColor, b.cardColor, t),
...@@ -734,6 +755,7 @@ class ThemeData extends Diagnosticable { ...@@ -734,6 +755,7 @@ class ThemeData extends Diagnosticable {
unselectedWidgetColor: Color.lerp(a.unselectedWidgetColor, b.unselectedWidgetColor, t), unselectedWidgetColor: Color.lerp(a.unselectedWidgetColor, b.unselectedWidgetColor, t),
disabledColor: Color.lerp(a.disabledColor, b.disabledColor, t), disabledColor: Color.lerp(a.disabledColor, b.disabledColor, t),
buttonColor: Color.lerp(a.buttonColor, b.buttonColor, t), buttonColor: Color.lerp(a.buttonColor, b.buttonColor, t),
toggleableActiveColor: Color.lerp(a.toggleableActiveColor, b.toggleableActiveColor, t),
buttonTheme: t < 0.5 ? a.buttonTheme : b.buttonTheme, buttonTheme: t < 0.5 ? a.buttonTheme : b.buttonTheme,
secondaryHeaderColor: Color.lerp(a.secondaryHeaderColor, b.secondaryHeaderColor, t), secondaryHeaderColor: Color.lerp(a.secondaryHeaderColor, b.secondaryHeaderColor, t),
textSelectionColor: Color.lerp(a.textSelectionColor, b.textSelectionColor, t), textSelectionColor: Color.lerp(a.textSelectionColor, b.textSelectionColor, t),
...@@ -741,12 +763,9 @@ class ThemeData extends Diagnosticable { ...@@ -741,12 +763,9 @@ class ThemeData extends Diagnosticable {
textSelectionHandleColor: Color.lerp(a.textSelectionHandleColor, b.textSelectionHandleColor, t), textSelectionHandleColor: Color.lerp(a.textSelectionHandleColor, b.textSelectionHandleColor, t),
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
dialogBackgroundColor: Color.lerp(a.dialogBackgroundColor, b.dialogBackgroundColor, t), dialogBackgroundColor: Color.lerp(a.dialogBackgroundColor, b.dialogBackgroundColor, t),
accentColor: Color.lerp(a.accentColor, b.accentColor, t),
accentColorBrightness: t < 0.5 ? a.accentColorBrightness : b.accentColorBrightness,
indicatorColor: Color.lerp(a.indicatorColor, b.indicatorColor, t), indicatorColor: Color.lerp(a.indicatorColor, b.indicatorColor, t),
hintColor: Color.lerp(a.hintColor, b.hintColor, t), hintColor: Color.lerp(a.hintColor, b.hintColor, t),
errorColor: Color.lerp(a.errorColor, b.errorColor, t), errorColor: Color.lerp(a.errorColor, b.errorColor, t),
toggleableActiveColor: Color.lerp(a.toggleableActiveColor, b.toggleableActiveColor, t),
textTheme: TextTheme.lerp(a.textTheme, b.textTheme, t), textTheme: TextTheme.lerp(a.textTheme, b.textTheme, t),
primaryTextTheme: TextTheme.lerp(a.primaryTextTheme, b.primaryTextTheme, t), primaryTextTheme: TextTheme.lerp(a.primaryTextTheme, b.primaryTextTheme, t),
accentTextTheme: TextTheme.lerp(a.accentTextTheme, b.accentTextTheme, t), accentTextTheme: TextTheme.lerp(a.accentTextTheme, b.accentTextTheme, t),
...@@ -758,6 +777,7 @@ class ThemeData extends Diagnosticable { ...@@ -758,6 +777,7 @@ class ThemeData extends Diagnosticable {
chipTheme: ChipThemeData.lerp(a.chipTheme, b.chipTheme, t), chipTheme: ChipThemeData.lerp(a.chipTheme, b.chipTheme, t),
platform: t < 0.5 ? a.platform : b.platform, platform: t < 0.5 ? a.platform : b.platform,
materialTapTargetSize: t < 0.5 ? a.materialTapTargetSize : b.materialTapTargetSize, materialTapTargetSize: t < 0.5 ? a.materialTapTargetSize : b.materialTapTargetSize,
pageTransitionsTheme: t < 0.5 ? a.pageTransitionsTheme : b.pageTransitionsTheme,
); );
} }
...@@ -766,9 +786,16 @@ class ThemeData extends Diagnosticable { ...@@ -766,9 +786,16 @@ class ThemeData extends Diagnosticable {
if (other.runtimeType != runtimeType) if (other.runtimeType != runtimeType)
return false; return false;
final ThemeData otherData = other; final ThemeData otherData = other;
// Warning: make sure these properties are in the exact same order as in
// hashValues() and in the raw constructor and in the order of fields in
// the class and in the lerp() method.
return (otherData.brightness == brightness) && return (otherData.brightness == brightness) &&
(otherData.primaryColor == primaryColor) && (otherData.primaryColor == primaryColor) &&
(otherData.primaryColorBrightness == primaryColorBrightness) && (otherData.primaryColorBrightness == primaryColorBrightness) &&
(otherData.primaryColorLight == primaryColorLight) &&
(otherData.primaryColorDark == primaryColorDark) &&
(otherData.accentColor == accentColor) &&
(otherData.accentColorBrightness == accentColorBrightness) &&
(otherData.canvasColor == canvasColor) && (otherData.canvasColor == canvasColor) &&
(otherData.scaffoldBackgroundColor == scaffoldBackgroundColor) && (otherData.scaffoldBackgroundColor == scaffoldBackgroundColor) &&
(otherData.bottomAppBarColor == bottomAppBarColor) && (otherData.bottomAppBarColor == bottomAppBarColor) &&
...@@ -789,8 +816,6 @@ class ThemeData extends Diagnosticable { ...@@ -789,8 +816,6 @@ class ThemeData extends Diagnosticable {
(otherData.textSelectionHandleColor == textSelectionHandleColor) && (otherData.textSelectionHandleColor == textSelectionHandleColor) &&
(otherData.backgroundColor == backgroundColor) && (otherData.backgroundColor == backgroundColor) &&
(otherData.dialogBackgroundColor == dialogBackgroundColor) && (otherData.dialogBackgroundColor == dialogBackgroundColor) &&
(otherData.accentColor == accentColor) &&
(otherData.accentColorBrightness == accentColorBrightness) &&
(otherData.indicatorColor == indicatorColor) && (otherData.indicatorColor == indicatorColor) &&
(otherData.hintColor == hintColor) && (otherData.hintColor == hintColor) &&
(otherData.errorColor == errorColor) && (otherData.errorColor == errorColor) &&
...@@ -804,15 +829,24 @@ class ThemeData extends Diagnosticable { ...@@ -804,15 +829,24 @@ class ThemeData extends Diagnosticable {
(otherData.sliderTheme == sliderTheme) && (otherData.sliderTheme == sliderTheme) &&
(otherData.chipTheme == chipTheme) && (otherData.chipTheme == chipTheme) &&
(otherData.platform == platform) && (otherData.platform == platform) &&
(otherData.materialTapTargetSize == materialTapTargetSize); (otherData.materialTapTargetSize == materialTapTargetSize) &&
(otherData.pageTransitionsTheme == pageTransitionsTheme);
} }
@override @override
int get hashCode { int get hashCode {
// The hashValues() function supports up to 20 arguments.
return hashValues( return hashValues(
// Warning: make sure these properties are in the exact same order as in
// operator == and in the raw constructor and in the order of fields in
// the class and in the lerp() method.
brightness, brightness,
primaryColor, primaryColor,
primaryColorBrightness, primaryColorBrightness,
primaryColorLight,
primaryColorDark,
accentColor,
accentColorBrightness,
canvasColor, canvasColor,
scaffoldBackgroundColor, scaffoldBackgroundColor,
bottomAppBarColor, bottomAppBarColor,
...@@ -823,33 +857,33 @@ class ThemeData extends Diagnosticable { ...@@ -823,33 +857,33 @@ class ThemeData extends Diagnosticable {
splashFactory, splashFactory,
selectedRowColor, selectedRowColor,
unselectedWidgetColor, unselectedWidgetColor,
disabledColor,
buttonColor, buttonColor,
buttonTheme, buttonTheme,
secondaryHeaderColor, hashValues(
textSelectionColor, secondaryHeaderColor,
textSelectionHandleColor, textSelectionColor,
hashValues( // Too many values. cursorColor,
toggleableActiveColor, textSelectionHandleColor,
backgroundColor, backgroundColor,
accentColor,
accentColorBrightness,
indicatorColor,
dialogBackgroundColor, dialogBackgroundColor,
indicatorColor,
hintColor, hintColor,
errorColor, errorColor,
toggleableActiveColor,
textTheme, textTheme,
primaryTextTheme, primaryTextTheme,
accentTextTheme, accentTextTheme,
iconTheme,
inputDecorationTheme, inputDecorationTheme,
iconTheme,
primaryIconTheme, primaryIconTheme,
accentIconTheme, accentIconTheme,
sliderTheme, sliderTheme,
chipTheme, chipTheme,
platform, hashValues(
materialTapTargetSize, platform,
cursorColor materialTapTargetSize,
pageTransitionsTheme,
),
), ),
); );
} }
...@@ -896,6 +930,7 @@ class ThemeData extends Diagnosticable { ...@@ -896,6 +930,7 @@ class ThemeData extends Diagnosticable {
properties.add(DiagnosticsProperty<SliderThemeData>('sliderTheme', sliderTheme)); properties.add(DiagnosticsProperty<SliderThemeData>('sliderTheme', sliderTheme));
properties.add(DiagnosticsProperty<ChipThemeData>('chipTheme', chipTheme)); properties.add(DiagnosticsProperty<ChipThemeData>('chipTheme', chipTheme));
properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize)); properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize));
properties.add(DiagnosticsProperty<PageTransitionsTheme>('pageTransitionsTheme', pageTransitionsTheme));
} }
} }
......
...@@ -917,7 +917,7 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix ...@@ -917,7 +917,7 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
/// The parameters `viewport` and `offset` are required and cannot be null. /// The parameters `viewport` and `offset` are required and cannot be null.
/// If `descendant` is null, this is a no-op and `rect` is returned. /// If `descendant` is null, this is a no-op and `rect` is returned.
/// ///
/// If both `decedent` and `rect` are null, null is returned because there is /// If both `descendant` and `rect` are null, null is returned because there is
/// nothing to be shown in the viewport. /// nothing to be shown in the viewport.
/// ///
/// The `duration` parameter can be set to a non-zero value to animate the /// The `duration` parameter can be set to a non-zero value to animate the
......
// 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/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Default PageTranstionsTheme platform', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(home: const Text('home')));
final PageTransitionsTheme theme = Theme.of(tester.element(find.text('home'))).pageTransitionsTheme;
expect(theme.builders, isNotNull);
expect(theme.builders[TargetPlatform.android], isNotNull);
expect(theme.builders[TargetPlatform.iOS], isNotNull);
});
testWidgets('Default PageTranstionsTheme builds a CupertionPageTransition for iOS', (WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => Material(
child: FlatButton(
child: const Text('push'),
onPressed: () { Navigator.of(context).pushNamed('/b'); },
),
),
'/b': (BuildContext context) => const Text('page b'),
};
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(platform: TargetPlatform.iOS),
routes: routes,
),
);
expect(Theme.of(tester.element(find.text('push'))).platform, TargetPlatform.iOS);
expect(find.byType(CupertinoPageTransition), findsOneWidget);
await tester.tap(find.text('push'));
await tester.pumpAndSettle();
expect(find.text('page b'), findsOneWidget);
expect(find.byType(CupertinoPageTransition), findsOneWidget);
});
testWidgets('Default PageTranstionsTheme builds a _FadeUpwardsPageTransition for android', (WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => Material(
child: FlatButton(
child: const Text('push'),
onPressed: () { Navigator.of(context).pushNamed('/b'); },
),
),
'/b': (BuildContext context) => const Text('page b'),
};
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(platform: TargetPlatform.android),
routes: routes,
),
);
Finder findFadeUpwardsPageTransition() {
return find.descendant(
of: find.byType(MaterialApp),
matching: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_FadeUpwardsPageTransition'),
);
}
expect(Theme.of(tester.element(find.text('push'))).platform, TargetPlatform.android);
expect(findFadeUpwardsPageTransition(), findsOneWidget);
await tester.tap(find.text('push'));
await tester.pumpAndSettle();
expect(find.text('page b'), findsOneWidget);
expect(findFadeUpwardsPageTransition(), findsOneWidget);
});
testWidgets('pageTranstionsTheme override builds a _OpenUpwardsPageTransition for android', (WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => Material(
child: FlatButton(
child: const Text('push'),
onPressed: () { Navigator.of(context).pushNamed('/b'); },
),
),
'/b': (BuildContext context) => const Text('page b'),
};
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
platform: TargetPlatform.android,
pageTransitionsTheme: const PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: OpenUpwardsPageTransitionsBuilder(), // creates a _OpenUpwardsPageTransition
},
),
),
routes: routes,
),
);
Finder findOpenUpwardsPageTransition() {
return find.descendant(
of: find.byType(MaterialApp),
matching: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_OpenUpwardsPageTransition'),
);
}
expect(Theme.of(tester.element(find.text('push'))).platform, TargetPlatform.android);
expect(findOpenUpwardsPageTransition(), findsOneWidget);
await tester.tap(find.text('push'));
await tester.pumpAndSettle();
expect(find.text('page b'), findsOneWidget);
expect(findOpenUpwardsPageTransition(), findsOneWidget);
});
}
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