Unverified Commit 062e469f authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Page-subclasses to take children instead of builder (#66694)

parent b8397f69
......@@ -93,7 +93,8 @@ final DecorationTween _kGradientShadowTween = DecorationTween(
/// * [CupertinoPageRoute], which is a [PageRoute] that leverages this mixin.
mixin CupertinoRouteTransitionMixin<T> on PageRoute<T> {
/// Builds the primary contents of the route.
WidgetBuilder get builder;
@protected
Widget buildContent(BuildContext context);
/// {@template flutter.cupertino.cupertinoRouteTransitionMixin.title}
/// A title string for this route.
......@@ -224,7 +225,7 @@ mixin CupertinoRouteTransitionMixin<T> on PageRoute<T> {
@override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
final Widget child = builder(context);
final Widget child = buildContent(context);
final Widget result = Semantics(
scopesRoute: true,
explicitChildNodes: true,
......@@ -351,9 +352,12 @@ class CupertinoPageRoute<T> extends PageRoute<T> with CupertinoRouteTransitionMi
assert(opaque),
super(settings: settings, fullscreenDialog: fullscreenDialog);
@override
/// Builds the primary contents of the route.
final WidgetBuilder builder;
@override
Widget buildContent(BuildContext context) => builder(context);
@override
final String? title;
......@@ -378,7 +382,7 @@ class _PageBasedCupertinoPageRoute<T> extends PageRoute<T> with CupertinoRouteTr
CupertinoPage<T> get _page => settings as CupertinoPage<T>;
@override
WidgetBuilder get builder => _page.builder;
Widget buildContent(BuildContext context) => _page.child;
@override
String? get title => _page.title;
......@@ -412,20 +416,20 @@ class _PageBasedCupertinoPageRoute<T> extends PageRoute<T> with CupertinoRouteTr
class CupertinoPage<T> extends Page<T> {
/// Creates a cupertino page.
const CupertinoPage({
required this.builder,
required this.child,
this.maintainState = true,
this.title,
this.fullscreenDialog = false,
LocalKey? key,
String? name,
Object? arguments,
}) : assert(builder != null),
}) : assert(child != null),
assert(maintainState != null),
assert(fullscreenDialog != null),
super(key: key, name: name, arguments: arguments);
/// Builds the primary contents of the route.
final WidgetBuilder builder;
/// The content to be shown in the [Route] created by this page.
final Widget child;
/// {@macro flutter.cupertino.cupertinoRouteTransitionMixin.title}
final String? title;
......
......@@ -48,9 +48,12 @@ class MaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixi
assert(opaque),
super(settings: settings, fullscreenDialog: fullscreenDialog);
@override
/// Builds the primary contents of the route.
final WidgetBuilder builder;
@override
Widget buildContent(BuildContext context) => builder(context);
@override
final bool maintainState;
......@@ -77,7 +80,8 @@ class MaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixi
/// by the [MaterialRouteTransitionMixin.buildTransitions].
mixin MaterialRouteTransitionMixin<T> on PageRoute<T> {
/// Builds the primary contents of the route.
WidgetBuilder get builder;
@protected
Widget buildContent(BuildContext context);
@override
Duration get transitionDuration => const Duration(milliseconds: 300);
......@@ -101,7 +105,7 @@ mixin MaterialRouteTransitionMixin<T> on PageRoute<T> {
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
final Widget result = builder(context);
final Widget result = buildContent(context);
assert(() {
if (result == null) {
throw FlutterError(
......@@ -148,19 +152,19 @@ mixin MaterialRouteTransitionMixin<T> on PageRoute<T> {
class MaterialPage<T> extends Page<T> {
/// Creates a material page.
const MaterialPage({
@required this.builder,
@required this.child,
this.maintainState = true,
this.fullscreenDialog = false,
LocalKey key,
String name,
Object arguments,
}) : assert(builder != null),
}) : assert(child != null),
assert(maintainState != null),
assert(fullscreenDialog != null),
super(key: key, name: name, arguments: arguments);
/// Builds the primary contents of the route.
final WidgetBuilder builder;
/// The content to be shown in the [Route] created by this page.
final Widget child;
/// {@macro flutter.widgets.modalRoute.maintainState}
final bool maintainState;
......@@ -188,7 +192,9 @@ class _PageBasedMaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTran
MaterialPage<T> get _page => settings as MaterialPage<T>;
@override
WidgetBuilder get builder => _page.builder;
Widget buildContent(BuildContext context) {
return _page.child;
}
@override
bool get maintainState => _page.maintainState;
......
......@@ -37,11 +37,6 @@ import 'ticker_provider.dart';
/// * [Navigator], which is where all the [Route]s end up.
typedef RouteFactory = Route<dynamic>? Function(RouteSettings settings);
/// Creates a route for the given context and route settings.
///
/// Used by [CustomBuilderPage.routeBuilder].
typedef RouteBuilder<T> = Route<T> Function(BuildContext context, RouteSettings settings);
/// Creates a series of one or more routes.
///
/// Used by [Navigator.onGenerateInitialRoutes].
......@@ -507,8 +502,6 @@ class RouteSettings {
///
/// * [Navigator.pages], which accepts a list of [Page]s and updates its routes
/// history.
/// * [CustomBuilderPage], a [Page] subclass that provides the API to build a
/// customized route.
abstract class Page<T> extends RouteSettings {
/// Creates a page and initializes [key] for subclasses.
///
......@@ -543,38 +536,6 @@ abstract class Page<T> extends RouteSettings {
String toString() => '${objectRuntimeType(this, 'Page')}("$name", $key, $arguments)';
}
/// A [Page] that builds a customized [Route] based on the [routeBuilder].
///
/// The type argument `T` is the corresponding [Route]'s return type, as
/// used by [Route.currentResult], [Route.popped], and [Route.didPop].
class CustomBuilderPage<T> extends Page<T> {
/// Creates a page with a custom route builder.
///
/// Use [routeBuilder] to specify the route that will be created from this
/// page.
const CustomBuilderPage({
required LocalKey key,
required this.routeBuilder,
String? name,
Object? arguments,
}) : assert(key != null),
assert(routeBuilder != null),
super(key: key, name: name, arguments: arguments);
/// A builder that will be called during [createRoute] to create a [Route].
///
/// The routes returned from this builder must have their settings equal to
/// the input `settings`.
final RouteBuilder<T> routeBuilder;
@override
Route<T> createRoute(BuildContext context) {
final Route<T> route = routeBuilder(context, this);
assert(route.settings == this);
return route;
}
}
/// An interface for observing the behavior of a [Navigator].
class NavigatorObserver {
/// The navigator that the observer is observing, if any.
......@@ -1045,10 +1006,10 @@ class DefaultTransitionDelegate<T> extends TransitionDelegate<T> {
/// The [Navigator] will convert its [Navigator.pages] into a stack of [Route]s
/// if it is provided. A change in [Navigator.pages] will trigger an update to
/// the stack of [Route]s. The [Navigator] will update its routes to match the
/// new configuration of its [Navigator.pages]. To use this API, one can use
/// [CustomBuilderPage] or create a [Page] subclass and defines a list of
/// [Page]s for [Navigator.pages]. A [Navigator.onPopPage] callback is also
/// required to properly clean up the input pages in case of a pop.
/// new configuration of its [Navigator.pages]. To use this API, one can create
/// a [Page] subclass and defines a list of [Page]s for [Navigator.pages]. A
/// [Navigator.onPopPage] callback is also required to properly clean up the
/// input pages in case of a pop.
///
/// By Default, the [Navigator] will use [DefaultTransitionDelegate] to decide
/// how routes transition in or out of the screen. To customize it, define a
......
......@@ -46,10 +46,6 @@ Widget _defaultTransitionsBuilder(BuildContext context, Animation<double> animat
///
/// Callers must define the [pageBuilder] function which creates the route's
/// primary contents. To add transitions define the [transitionsBuilder] function.
///
/// See also:
///
/// * [TransitionBuilderPage], which is a [Page] of this class.
class PageRouteBuilder<T> extends PageRoute<T> {
/// Creates a route that delegates to builder callbacks.
///
......@@ -120,126 +116,3 @@ class PageRouteBuilder<T> extends PageRoute<T> {
return transitionsBuilder(context, animation, secondaryAnimation, child);
}
}
/// A page that creates a [PageRoute] with customizable transition.
///
/// Similar to the [PageRouteBuilder], callers must define the [pageBuilder]
/// function which creates the route's primary contents. To add transitions
/// define the [transitionsBuilder] function.
///
/// See also:
///
/// * [PageRouteBuilder], which is a [PageRoute] version of this class.
class TransitionBuilderPage<T> extends Page<T> {
/// Creates a [TransitionBuilderPage].
const TransitionBuilderPage({
required this.pageBuilder,
this.transitionsBuilder = _defaultTransitionsBuilder,
this.transitionDuration = const Duration(milliseconds: 300),
this.reverseTransitionDuration = const Duration(milliseconds: 300),
this.opaque = true,
this.barrierDismissible = false,
this.barrierColor,
this.barrierLabel,
this.maintainState = true,
this.fullscreenDialog = false,
LocalKey? key,
String? name,
Object? arguments,
}) : assert(pageBuilder != null),
assert(transitionsBuilder != null),
assert(opaque != null),
assert(barrierDismissible != null),
assert(maintainState != null),
assert(fullscreenDialog != null),
super(key: key, name: name, arguments: arguments);
/// {@macro flutter.widgets.pageRouteBuilder.pageBuilder}
final RoutePageBuilder pageBuilder;
/// {@macro flutter.widgets.pageRouteBuilder.transitionsBuilder}
final RouteTransitionsBuilder transitionsBuilder;
/// {@macro flutter.widgets.transitionRoute.transitionDuration}
final Duration transitionDuration;
/// {@macro flutter.widgets.transitionRoute.reverseTransitionDuration}
final Duration reverseTransitionDuration;
/// {@macro flutter.widgets.transitionRoute.opaque}
final bool opaque;
/// {@macro flutter.widgets.modalRoute.barrierDismissible}
final bool barrierDismissible;
/// {@macro flutter.widgets.modalRoute.barrierColor}
///
/// See also:
///
/// * [barrierDismissible], which controls the behavior of the barrier when
/// tapped.
/// * [ModalBarrier], the widget that implements this feature.
final Color? barrierColor;
/// {@macro flutter.widgets.modalRoute.barrierLabel}
///
/// See also:
///
/// * [barrierDismissible], which controls the behavior of the barrier when
/// tapped.
/// * [ModalBarrier], the widget that implements this feature.
final String? barrierLabel;
/// {@macro flutter.widgets.modalRoute.maintainState}
final bool maintainState;
/// {@macro flutter.widgets.pageRoute.fullscreenDialog}
final bool fullscreenDialog;
@override
Route<T> createRoute(BuildContext context) => _PageBasedPageRouteBuilder<T>(this);
}
// A page-based version of the [PageRouteBuilder].
//
// This class gets its builder and settings directly from the [TransitionBuilderPage],
// so that its content updates accordingly to the [TransitionBuilderPage].
class _PageBasedPageRouteBuilder<T> extends PageRoute<T>{
_PageBasedPageRouteBuilder(
TransitionBuilderPage<T> page,
) : assert(page != null),
super(settings: page, fullscreenDialog: page.fullscreenDialog);
TransitionBuilderPage<T> get _page => settings as TransitionBuilderPage<T>;
@override
Duration get transitionDuration => _page.transitionDuration;
@override
Duration get reverseTransitionDuration => _page.reverseTransitionDuration;
@override
bool get opaque => _page.opaque;
@override
bool get barrierDismissible => _page.barrierDismissible;
@override
Color? get barrierColor => _page.barrierColor;
@override
String? get barrierLabel => _page.barrierLabel;
@override
bool get maintainState => _page.maintainState;
@override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return _page.pageBuilder(context, animation, secondaryAnimation);
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
return _page.transitionsBuilder(context, animation, secondaryAnimation, child);
}
}
......@@ -236,12 +236,12 @@ class SimpleNavigatorRouterDelegate extends RouterDelegate<RouteInformation> wit
pages: <Page<void>>[
// We need at least two pages for the pop to propagate through.
// Otherwise, the navigator will bubble the pop to the system navigator.
CupertinoPage<void>(
builder: (BuildContext context) => const Text('base'),
const CupertinoPage<void>(
child: Text('base'),
),
CupertinoPage<void>(
key: ValueKey<String>(routeInformation?.location),
builder: (BuildContext context) => builder(context, routeInformation),
child: builder(context, routeInformation),
)
],
);
......
......@@ -464,6 +464,19 @@ void main() {
expect(find.text('Page 1'), findsNothing);
expect(find.text('Page 2'), isOnstage);
});
testWidgets('CupertinoPage does not lose its state when transitioning out', (WidgetTester tester) async {
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
await tester.pumpWidget(KeepsStateTestWidget(navigatorKey: navigator));
expect(find.text('subpage'), findsOneWidget);
expect(find.text('home'), findsNothing);
navigator.currentState.pop();
await tester.pump();
expect(find.text('subpage'), findsOneWidget);
expect(find.text('home'), findsOneWidget);
});
}
class RtlOverrideWidgetsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
......@@ -481,3 +494,38 @@ class RtlOverrideWidgetsLocalization implements WidgetsLocalizations {
@override
TextDirection get textDirection => TextDirection.rtl;
}
class KeepsStateTestWidget extends StatefulWidget {
const KeepsStateTestWidget({this.navigatorKey});
final Key navigatorKey;
@override
State<KeepsStateTestWidget> createState() => _KeepsStateTestWidgetState();
}
class _KeepsStateTestWidgetState extends State<KeepsStateTestWidget> {
String _subpage = 'subpage';
@override
Widget build(BuildContext context) {
return CupertinoApp(
home: Navigator(
key: widget.navigatorKey,
pages: <Page<void>>[
const CupertinoPage<void>(child: Text('home')),
if (_subpage != null) CupertinoPage<void>(child: Text(_subpage)),
],
onPopPage: (Route<dynamic> route, dynamic result) {
if (!route.didPop(result)) {
return false;
}
setState(() {
_subpage = null;
});
return true;
},
),
);
}
}
......@@ -1382,12 +1382,10 @@ void main() {
CupertinoPage<void>(
key: pageKey,
title: 'title one',
builder: (BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(key: UniqueKey()),
child: const Text('first'),
);
}
child: CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(key: UniqueKey()),
child: const Text('first'),
),
),
];
await tester.pumpWidget(
......@@ -1406,12 +1404,10 @@ void main() {
CupertinoPage<void>(
key: pageKey,
title: 'title two',
builder: (BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(key: UniqueKey()),
child: const Text('second'),
);
}
child: CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(key: UniqueKey()),
child: const Text('second'),
),
),
];
......@@ -1437,8 +1433,8 @@ void main() {
final LocalKey pageKeyTwo = UniqueKey();
final TransitionDetector detector = TransitionDetector();
List<Page<void>> myPages = <Page<void>>[
CupertinoPage<void>(key: pageKeyOne, maintainState: false, builder: (BuildContext context) => const Text('first')),
CupertinoPage<void>(key: pageKeyTwo, builder: (BuildContext context) => const Text('second')),
CupertinoPage<void>(key: pageKeyOne, maintainState: false, child: const Text('first')),
CupertinoPage<void>(key: pageKeyTwo, child: const Text('second')),
];
await tester.pumpWidget(
buildNavigator(
......@@ -1454,8 +1450,8 @@ void main() {
expect(find.text('second'), findsOneWidget);
myPages = <Page<void>>[
CupertinoPage<void>(key: pageKeyOne, maintainState: true, builder: (BuildContext context) => const Text('first')),
CupertinoPage<void>(key: pageKeyTwo, builder: (BuildContext context) => const Text('second')),
CupertinoPage<void>(key: pageKeyOne, maintainState: true, child: const Text('first')),
CupertinoPage<void>(key: pageKeyTwo, child: const Text('second')),
];
await tester.pumpWidget(
......
......@@ -1056,12 +1056,12 @@ class SimpleNavigatorRouterDelegate extends RouterDelegate<RouteInformation> wit
pages: <Page<void>>[
// We need at least two pages for the pop to propagate through.
// Otherwise, the navigator will bubble the pop to the system navigator.
MaterialPage<void>(
builder: (BuildContext context) => const Text('base'),
const MaterialPage<void>(
child: Text('base'),
),
MaterialPage<void>(
key: ValueKey<String>(routeInformation?.location),
builder: (BuildContext context) => builder(context, routeInformation),
child: builder(context, routeInformation),
)
],
);
......
......@@ -841,7 +841,7 @@ void main() {
final LocalKey pageKey = UniqueKey();
final TransitionDetector detector = TransitionDetector();
List<Page<void>> myPages = <Page<void>>[
MaterialPage<void>(key: pageKey, builder: (BuildContext context) => const Text('first')),
MaterialPage<void>(key: pageKey, child: const Text('first')),
];
await tester.pumpWidget(
buildNavigator(
......@@ -855,7 +855,7 @@ void main() {
expect(find.text('first'), findsOneWidget);
myPages = <Page<void>>[
MaterialPage<void>(key: pageKey, builder: (BuildContext context) => const Text('second')),
MaterialPage<void>(key: pageKey, child: const Text('second')),
];
await tester.pumpWidget(
......@@ -877,8 +877,8 @@ void main() {
final LocalKey pageKeyTwo = UniqueKey();
final TransitionDetector detector = TransitionDetector();
List<Page<void>> myPages = <Page<void>>[
MaterialPage<void>(key: pageKeyOne, maintainState: false, builder: (BuildContext context) => const Text('first')),
MaterialPage<void>(key: pageKeyTwo, builder: (BuildContext context) => const Text('second')),
MaterialPage<void>(key: pageKeyOne, maintainState: false, child: const Text('first')),
MaterialPage<void>(key: pageKeyTwo, child: const Text('second')),
];
await tester.pumpWidget(
buildNavigator(
......@@ -894,8 +894,8 @@ void main() {
expect(find.text('second'), findsOneWidget);
myPages = <Page<void>>[
MaterialPage<void>(key: pageKeyOne, maintainState: true, builder: (BuildContext context) => const Text('first')),
MaterialPage<void>(key: pageKeyTwo, builder: (BuildContext context) => const Text('second')),
MaterialPage<void>(key: pageKeyOne, maintainState: true, child: const Text('first')),
MaterialPage<void>(key: pageKeyTwo, child: const Text('second')),
];
await tester.pumpWidget(
......@@ -912,6 +912,19 @@ void main() {
expect(find.text('first', skipOffstage: false), findsOneWidget);
expect(find.text('second'), findsOneWidget);
});
testWidgets('MaterialPage does not lose its state when transitioning out', (WidgetTester tester) async {
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
await tester.pumpWidget(KeepsStateTestWidget(navigatorKey: navigator));
expect(find.text('subpage'), findsOneWidget);
expect(find.text('home'), findsNothing);
navigator.currentState.pop();
await tester.pump();
expect(find.text('subpage'), findsOneWidget);
expect(find.text('home'), findsOneWidget);
});
}
class TransitionDetector extends DefaultTransitionDelegate<void> {
......@@ -957,3 +970,38 @@ Widget buildNavigator({
),
);
}
class KeepsStateTestWidget extends StatefulWidget {
const KeepsStateTestWidget({this.navigatorKey});
final Key navigatorKey;
@override
State<KeepsStateTestWidget> createState() => _KeepsStateTestWidgetState();
}
class _KeepsStateTestWidgetState extends State<KeepsStateTestWidget> {
String _subpage = 'subpage';
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Navigator(
key: widget.navigatorKey,
pages: <Page<void>>[
const MaterialPage<void>(child: Text('home')),
if (_subpage != null) MaterialPage<void>(child: Text(_subpage)),
],
onPopPage: (Route<dynamic> route, dynamic result) {
if (!route.didPop(result)) {
return false;
}
setState(() {
_subpage = null;
});
return true;
},
),
);
}
}
......@@ -366,12 +366,12 @@ class SimpleNavigatorRouterDelegate extends RouterDelegate<RouteInformation> wit
pages: <Page<void>>[
// We need at least two pages for the pop to propagate through.
// Otherwise, the navigator will bubble the pop to the system navigator.
MaterialPage<void>(
builder: (BuildContext context) => const Text('base'),
const MaterialPage<void>(
child: Text('base'),
),
MaterialPage<void>(
key: ValueKey<String>(routeInformation?.location),
builder: (BuildContext context) => builder(context, routeInformation),
child: builder(context, routeInformation),
)
],
);
......
......@@ -520,9 +520,9 @@ void main() {
// Regression Test for https://github.com/flutter/flutter/issues/61346.
Widget buildNavigator() {
return Navigator(
pages: <Page<void>>[
pages: const <Page<void>>[
MaterialPage<void>(
builder: (BuildContext context) => const Placeholder(),
child: Placeholder(),
)
],
onPopPage: (Route<dynamic> route, dynamic result) => false,
......@@ -550,14 +550,14 @@ void main() {
testWidgets('Pages update does update overlay correctly', (WidgetTester tester) async {
// Regression Test for https://github.com/flutter/flutter/issues/64941.
List<Page<void>> pages = <Page<void>>[
List<Page<void>> pages = const <Page<void>>[
MaterialPage<void>(
key: const ValueKey<int>(0),
builder: (BuildContext context) => const Text('page 0'),
key: ValueKey<int>(0),
child: Text('page 0'),
),
MaterialPage<void>(
key: const ValueKey<int>(1),
builder: (BuildContext context) => const Text('page 1'),
key: ValueKey<int>(1),
child: Text('page 1'),
),
];
Widget buildNavigator() {
......@@ -577,10 +577,10 @@ void main() {
expect(find.text('page 0'), findsNothing);
// Removes the first page.
pages = <Page<void>>[
pages = const <Page<void>>[
MaterialPage<void>(
key: const ValueKey<int>(1),
builder: (BuildContext context) => const Text('page 1'),
key: ValueKey<int>(1),
child: Text('page 1'),
),
];
......@@ -2509,18 +2509,13 @@ void main() {
Animation<double> primaryAnimationOfRouteThree;
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
List<Page<dynamic>> myPages = <Page<dynamic>>[
CustomBuilderPage<void>(
BuilderPage(
key: const ValueKey<String>('1'),
name:'initial',
routeBuilder: (BuildContext context, RouteSettings settings) {
return PageRouteBuilder<void>(
settings: settings,
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteOne = secondaryAnimation;
primaryAnimationOfRouteOne = animation;
return const Text('initial');
},
);
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteOne = secondaryAnimation;
primaryAnimationOfRouteOne = animation;
return const Text('initial');
},
),
];
......@@ -2536,48 +2531,33 @@ void main() {
expect(find.text('initial'), findsOneWidget);
myPages = <Page<dynamic>>[
CustomBuilderPage<void>(
BuilderPage(
key: const ValueKey<String>('1'),
name:'initial',
routeBuilder: (BuildContext context, RouteSettings settings) {
return PageRouteBuilder<void>(
settings: settings,
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteOne = secondaryAnimation;
primaryAnimationOfRouteOne = animation;
return const Text('initial');
},
);
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteOne = secondaryAnimation;
primaryAnimationOfRouteOne = animation;
return const Text('initial');
},
),
CustomBuilderPage<void>(
BuilderPage(
key: const ValueKey<String>('2'),
name:'second',
routeBuilder: (BuildContext context, RouteSettings settings) {
return PageRouteBuilder<void>(
settings: settings,
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteTwo = secondaryAnimation;
primaryAnimationOfRouteTwo = animation;
return const Text('second');
},
);
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteTwo = secondaryAnimation;
primaryAnimationOfRouteTwo = animation;
return const Text('second');
},
),
CustomBuilderPage<void>(
BuilderPage(
key: const ValueKey<String>('3'),
name:'third',
routeBuilder: (BuildContext context, RouteSettings settings) {
return PageRouteBuilder<void>(
settings: settings,
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteThree = secondaryAnimation;
primaryAnimationOfRouteThree = animation;
return const Text('third');
},
);
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteThree = secondaryAnimation;
primaryAnimationOfRouteThree = animation;
return const Text('third');
},
)
),
];
await tester.pumpWidget(
......@@ -2615,32 +2595,22 @@ void main() {
// correctly.
myPages = <Page<dynamic>>[
CustomBuilderPage<void>(
BuilderPage(
key: const ValueKey<String>('1'),
name:'initial',
routeBuilder: (BuildContext context, RouteSettings settings) {
return PageRouteBuilder<void>(
settings: settings,
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteOne = secondaryAnimation;
primaryAnimationOfRouteOne = animation;
return const Text('initial');
},
);
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteOne = secondaryAnimation;
primaryAnimationOfRouteOne = animation;
return const Text('initial');
},
),
CustomBuilderPage<void>(
BuilderPage(
key: const ValueKey<String>('2'),
name:'second',
routeBuilder: (BuildContext context, RouteSettings settings) {
return PageRouteBuilder<void>(
settings: settings,
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteTwo = secondaryAnimation;
primaryAnimationOfRouteTwo = animation;
return const Text('second');
},
);
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteTwo = secondaryAnimation;
primaryAnimationOfRouteTwo = animation;
return const Text('second');
},
),
];
......@@ -2672,47 +2642,32 @@ void main() {
Animation<double> primaryAnimationOfRouteTwo;
Animation<double> secondaryAnimationOfRouteThree;
Animation<double> primaryAnimationOfRouteThree;
List<Page<dynamic>> myPages = <CustomBuilderPage<void>>[
CustomBuilderPage<void>(
List<Page<dynamic>> myPages = <Page<void>>[
BuilderPage(
key: const ValueKey<String>('1'),
name:'initial',
routeBuilder: (BuildContext context, RouteSettings settings) {
return PageRouteBuilder<void>(
settings: settings,
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteOne = secondaryAnimation;
primaryAnimationOfRouteOne = animation;
return const Text('initial');
},
);
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteOne = secondaryAnimation;
primaryAnimationOfRouteOne = animation;
return const Text('initial');
},
),
CustomBuilderPage<void>(
BuilderPage(
key: const ValueKey<String>('2'),
name:'second',
routeBuilder: (BuildContext context, RouteSettings settings) {
return PageRouteBuilder<void>(
settings: settings,
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteTwo = secondaryAnimation;
primaryAnimationOfRouteTwo = animation;
return const Text('second');
},
);
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteTwo = secondaryAnimation;
primaryAnimationOfRouteTwo = animation;
return const Text('second');
},
),
CustomBuilderPage<void>(
BuilderPage(
key: const ValueKey<String>('3'),
name:'third',
routeBuilder: (BuildContext context, RouteSettings settings) {
return PageRouteBuilder<void>(
settings: settings,
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteThree = secondaryAnimation;
primaryAnimationOfRouteThree = animation;
return const Text('third');
},
);
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteThree = secondaryAnimation;
primaryAnimationOfRouteThree = animation;
return const Text('third');
},
),
];
......@@ -3512,3 +3467,17 @@ class NavigatorObservation {
final String current;
final String operation;
}
class BuilderPage extends Page<void> {
const BuilderPage({LocalKey key, String name, this.pageBuilder}) : super(key: key, name: name);
final RoutePageBuilder pageBuilder;
@override
Route<void> createRoute(BuildContext context) {
return PageRouteBuilder<void>(
settings: this,
pageBuilder: pageBuilder,
);
}
}
......@@ -119,11 +119,8 @@ void main() {
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: Navigator(
pages: <Page<void>>[
TransitionBuilderPage<void>(
name: '/',
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => const Placeholder(),
),
pages: const <Page<void>>[
TestPage(name: '/'),
],
onPopPage: (Route<void> route, void result) => false,
)
......@@ -134,15 +131,9 @@ void main() {
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: Navigator(
pages: <Page<void>>[
TransitionBuilderPage<void>(
name: '/',
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => const Placeholder(),
),
TransitionBuilderPage<void>(
name: '/abc',
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => const Placeholder(),
),
pages: const <Page<void>>[
TestPage(name: '/'),
TestPage(name: '/abc',),
],
onPopPage: (Route<void> route, void result) => false,
)
......@@ -371,3 +362,15 @@ class SimpleRouterDelegate extends RouterDelegate<RouteInformation> with ChangeN
@override
Widget build(BuildContext context) => builder(context, routeInformation);
}
class TestPage extends Page<void> {
const TestPage({LocalKey key, String name}) : super(key: key, name: name);
@override
Route<void> createRoute(BuildContext context) {
return PageRouteBuilder<void>(
settings: this,
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => const Placeholder(),
);
}
}
......@@ -663,12 +663,12 @@ class SimpleNavigatorRouterDelegate extends RouterDelegate<RouteInformation> wit
pages: <Page<void>>[
// We need at least two pages for the pop to propagate through.
// Otherwise, the navigator will bubble the pop to the system navigator.
MaterialPage<void>(
builder: (BuildContext context) => const Text('base'),
const MaterialPage<void>(
child: Text('base'),
),
MaterialPage<void>(
key: ValueKey<String>(routeInformation?.location),
builder: (BuildContext context) => builder(context, routeInformation),
child: builder(context, routeInformation),
)
],
);
......
......@@ -542,142 +542,6 @@ void main() {
expect(focusNode.hasPrimaryFocus, isTrue);
});
testWidgets('TransitionBuilderPage works', (WidgetTester tester) async {
final LocalKey pageKey = UniqueKey();
final TransitionDetector detector = TransitionDetector();
List<Page<void>> myPages = <Page<void>>[
TransitionBuilderPage<void>(
key: pageKey,
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return const Text('first');
},
)
];
await tester.pumpWidget(
buildNavigator(
pages: myPages,
onPopPage: (Route<dynamic> route, dynamic result) => null,
transitionDelegate: detector,
)
);
expect(detector.hasTransition, isFalse);
expect(find.text('first'), findsOneWidget);
myPages = <Page<void>>[
TransitionBuilderPage<void>(
key: pageKey,
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return const Text('second');
},
)
];
await tester.pumpWidget(
buildNavigator(
pages: myPages,
onPopPage: (Route<dynamic> route, dynamic result) => null,
transitionDelegate: detector,
)
);
// There should be no transition because the page has the same key.
expect(detector.hasTransition, isFalse);
// The content does update.
expect(find.text('first'), findsNothing);
expect(find.text('second'), findsOneWidget);
myPages = <Page<void>>[
TransitionBuilderPage<void>(
key: pageKey,
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return const Text('dummy');
},
transitionsBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
// Purposely discard the input child.
return const Text('third');
}
)
];
await tester.pumpWidget(
buildNavigator(
pages: myPages,
onPopPage: (Route<dynamic> route, dynamic result) => null,
transitionDelegate: detector,
)
);
// There should be no transition because the page has the same key.
expect(detector.hasTransition, isFalse);
// Makes sure transitionsBuilder works.
expect(find.text('second'), findsNothing);
expect(find.text('dummy'), findsNothing);
expect(find.text('third'), findsOneWidget);
});
testWidgets('TransitionBuilderPage can toggle MaintainState', (WidgetTester tester) async {
final LocalKey pageKeyOne = UniqueKey();
final LocalKey pageKeyTwo = UniqueKey();
final TransitionDetector detector = TransitionDetector();
List<Page<void>> myPages = <Page<void>>[
TransitionBuilderPage<void>(
key: pageKeyOne,
maintainState: false,
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return const Text('first');
},
),
TransitionBuilderPage<void>(
key: pageKeyTwo,
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return const Text('second');
},
)
];
await tester.pumpWidget(
buildNavigator(
pages: myPages,
onPopPage: (Route<dynamic> route, dynamic result) => null,
transitionDelegate: detector,
)
);
expect(detector.hasTransition, isFalse);
// Page one does not maintain state.
expect(find.text('first', skipOffstage: false), findsNothing);
expect(find.text('second'), findsOneWidget);
myPages = <Page<void>>[
TransitionBuilderPage<void>(
key: pageKeyOne,
maintainState: true,
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return const Text('first');
},
),
TransitionBuilderPage<void>(
key: pageKeyTwo,
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return const Text('second');
},
)
];
await tester.pumpWidget(
buildNavigator(
pages: myPages,
onPopPage: (Route<dynamic> route, dynamic result) => null,
transitionDelegate: detector,
)
);
// There should be no transition because the page has the same key.
expect(detector.hasTransition, isFalse);
// Page one sets the maintain state to be true, its widget tree should be
// built.
expect(find.text('first', skipOffstage: false), findsOneWidget);
expect(find.text('second'), findsOneWidget);
});
group('PageRouteBuilder', () {
testWidgets('reverseTransitionDuration defaults to 300ms', (WidgetTester tester) async {
// Default PageRouteBuilder reverse transition duration should be 300ms.
......
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