Commit 56a2d226 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Pesto home stack (#5168)

parent fb3e64cc
......@@ -142,20 +142,25 @@ class _PestoDemoState extends State<PestoDemo> {
new DrawerItem(
child: new Text('Home'),
selected: !config.showFavorites,
onPressed: () => Navigator.pushNamed(context, PestoDemo.routeName)
onPressed: () {
Navigator.popUntil(context, ModalRoute.withName('/pesto'));
}
),
new DrawerItem(
child: new Text('Favorites'),
selected: config.showFavorites,
onPressed: () { _showFavorites(context); }
onPressed: () {
if (config.showFavorites)
Navigator.pop(context);
else
_showFavorites(context);
}
),
new Divider(),
new DrawerItem(
child: new Text('Return to Gallery'),
onPressed: () {
Navigator.of(context)
..pop() // Close the drawer.
..pop(); // Go back to the gallery.
Navigator.popUntil(context, ModalRoute.withName('/'));
}
),
]
......@@ -193,6 +198,7 @@ class _PestoDemoState extends State<PestoDemo> {
void _showFavorites(BuildContext context) {
Navigator.push(context, new MaterialPageRoute<Null>(
settings: const RouteSettings(name: "/pesto/favorites"),
builder: (BuildContext context) {
return new PestoDemo(showFavorites: true);
}
......@@ -201,6 +207,7 @@ class _PestoDemoState extends State<PestoDemo> {
void _showRecipe(BuildContext context, Recipe recipe) {
Navigator.push(context, new MaterialPageRoute<Null>(
settings: const RouteSettings(name: "/pesto/recipe"),
builder: (BuildContext context) {
return new Theme(
data: _kTheme,
......
// Copyright 2016 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/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_gallery/main.dart' as flutter_gallery_main;
Finder byTooltip(WidgetTester tester, String message) {
return find.byWidgetPredicate((Widget widget) {
return widget is Tooltip && widget.message == message;
});
}
Finder findNavigationMenuButton(WidgetTester tester) {
return byTooltip(tester, 'Open navigation menu');
}
void main() {
TestWidgetsFlutterBinding binding =
TestWidgetsFlutterBinding.ensureInitialized();
if (binding is LiveTestWidgetsFlutterBinding) binding.allowAllFrames = true;
// Regression test for https://github.com/flutter/flutter/pull/5168
testWidgets('Pesto route management', (WidgetTester tester) async {
flutter_gallery_main
.main(); // builds the app and schedules a frame but doesn't trigger one
await tester.pump(); // see https://github.com/flutter/flutter/issues/1865
await tester.pump(); // triggers a frame
expect(find.text('Pesto'), findsOneWidget);
await tester.tap(find.text('Pesto'));
await tester.pump(); // Launch pesto
await tester.pump(const Duration(seconds: 1)); // transition is complete
Future<Null> tapDrawerItem(String title) async {
await tester.tap(findNavigationMenuButton(tester));
await tester.pump();
await tester.pump(const Duration(seconds: 1)); // drawer opening animation
await tester.tap(find.text(title));
await tester.pump();
await tester.pump(const Duration(seconds: 1)); // drawer closing animation
await tester.pump(); // maybe open a new page
return tester.pump(const Duration(seconds: 1)); // new page transition
}
await tapDrawerItem('Home');
await tapDrawerItem('Favorites');
await tapDrawerItem('Home');
await tapDrawerItem('Favorites');
await tapDrawerItem('Home');
await tapDrawerItem('Return to Gallery');
expect(find.text('Flutter gallery'), findsOneWidget);
});
}
......@@ -161,6 +161,9 @@ class NavigatorObserver {
void didPop(Route<dynamic> route, Route<dynamic> previousRoute) { }
}
/// Signature for the [Navigator.popUntil] predicate argument.
typedef bool RoutePredicate(Route<dynamic> route);
/// A widget that manages a set of child widgets with a stack discipline.
///
/// Many apps have a navigator near the top of their widget hierarchy in order
......@@ -243,10 +246,11 @@ class Navigator extends StatefulWidget {
return Navigator.of(context).pop(result);
}
/// Calls pop() repeatedly until the given route is the current route.
/// If it is already the current route, nothing happens.
static void popUntil(BuildContext context, Route<dynamic> targetRoute) {
Navigator.of(context).popUntil(targetRoute);
/// Calls [pop()] repeatedly until the predicate returns false.
/// The predicate may be applied to the same route more than once if
/// [Route.willHandlePopInternally] is true.
static void popUntil(BuildContext context, RoutePredicate predicate) {
Navigator.of(context).popUntil(predicate);
}
/// Whether the navigator that most tightly encloses the given context can be popped.
......@@ -463,9 +467,8 @@ class NavigatorState extends State<Navigator> {
return true;
}
void popUntil(Route<dynamic> targetRoute) {
assert(_history.contains(targetRoute));
while (!targetRoute.isCurrent)
void popUntil(RoutePredicate predicate) {
while (!predicate(_history.last))
pop();
}
......
......@@ -487,6 +487,17 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
}
}
/// Returns a predicate that's true if the route has the specified name and if
/// popping the route will not yield the same route, i.e. if the route's
/// [willHandlePopInternally] property is false.
///
/// This function is typically used with [Navigator.popUntil()].
static RoutePredicate withName(String name) {
return (Route<dynamic> route) {
return !route.willHandlePopInternally
&& route is ModalRoute && route.settings.name == name;
};
}
// The API for subclasses to override - used by _ModalScope
......
......@@ -11,7 +11,7 @@ final List<String> results = <String>[];
Set<TestRoute> routes = new HashSet<TestRoute>();
class TestRoute extends Route<String> {
class TestRoute extends LocalHistoryRoute<String> {
TestRoute(this.name);
final String name;
......@@ -329,7 +329,7 @@ void main() {
await runNavigatorTest(
tester,
host,
() { host.popUntil(routeB); },
() { host.popUntil((Route<dynamic> route) => route == routeB); },
<String>[
'C: didPop null',
'C: dispose',
......@@ -341,4 +341,42 @@ void main() {
expect(routes.isEmpty, isTrue);
results.clear();
});
testWidgets('Route localHistory - popUntil', (WidgetTester tester) async {
TestRoute routeA = new TestRoute('A');
routeA.addLocalHistoryEntry(new LocalHistoryEntry(
onRemove: () { routeA.log('onRemove 0'); }
));
routeA.addLocalHistoryEntry(new LocalHistoryEntry(
onRemove: () { routeA.log('onRemove 1'); }
));
GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
await tester.pumpWidget(new Navigator(
key: navigatorKey,
onGenerateRoute: (_) => routeA
));
NavigatorState host = navigatorKey.currentState;
await runNavigatorTest(
tester,
host,
() { host.popUntil((Route<dynamic> route) => !route.willHandlePopInternally); },
<String>[
'A: install',
'A: didPush',
'A: didChangeNext null',
'A: didPop null',
'A: onRemove 1',
'A: didPop null',
'A: onRemove 0',
]
);
await runNavigatorTest(
tester,
host,
() { host.popUntil((Route<dynamic> route) => !route.willHandlePopInternally); },
<String>[
]
);
});
}
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