Commit 2e301d4d authored by Adam Barth's avatar Adam Barth

Add support for modal, ephemeral, and contentless routes to Navigator2

Routes can now create a list of widgets, which can be empty (in the case of
contentless routes) or have multiple entries (in the case of modal routes).

Ephemeral routes are handled by keeping a separate list of modal and ephemeral
routes.
parent 856ee978
......@@ -9,6 +9,7 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/src/widgets/navigator2.dart' as n2;
import 'package:flutter/src/widgets/page.dart' as n2;
import 'theme.dart';
import 'title.dart';
......@@ -86,12 +87,22 @@ class _MaterialAppState extends State<MaterialApp> {
void _metricHandler(Size size) => setState(() { _size = size; });
n2.Route _generateRoute(n2.RouteArguments args) {
return new n2.PageRoute(
builder: (BuildContext context) {
RouteArguments routeArgs = new RouteArguments(context: context);
return config.routes[args.name](routeArgs);
},
args: args
);
}
Widget build(BuildContext context) {
Widget navigator;
if (_kUseNavigator2) {
navigator = new n2.Navigator(
key: _navigator,
routes: config.routes
onGenerateRoute: _generateRoute
);
} else {
navigator = new Navigator(
......
......@@ -2,55 +2,54 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/animation.dart';
import 'basic.dart';
import 'framework.dart';
import 'overlay.dart';
import 'transitions.dart';
abstract class Route {
/// Override this function to return the widget that this route should display.
Widget createWidget();
List<Widget> createWidgets() => const <Widget>[];
OverlayEntry get topEntry => _entries.isNotEmpty ? _entries.last : null;
OverlayEntry get bottomEntry => _entries.isNotEmpty ? _entries.first : null;
Widget _child;
OverlayEntry _entry;
final List<OverlayEntry> _entries = new List<OverlayEntry>();
void willPush() {
_child = createWidget();
void add(OverlayState overlay, OverlayEntry insertionPoint) {
List<Widget> widgets = createWidgets();
for (Widget widget in widgets) {
_entries.add(new OverlayEntry(child: widget));
overlay?.insert(_entries.last, above: insertionPoint);
insertionPoint = _entries.last;
}
}
void didPop(dynamic result) {
_entry.remove();
void remove(dynamic result) {
for (OverlayEntry entry in _entries)
entry.remove();
}
}
typedef Widget RouteBuilder(args);
typedef RouteBuilder RouteGenerator(String name);
class RouteArguments {
const RouteArguments({ this.name: '<anonymous>', this.mostValuableKeys });
final String name;
final Set<Key> mostValuableKeys;
}
const String _kDefaultPageName = '/';
typedef Route RouteFactory(RouteArguments args);
class Navigator extends StatefulComponent {
Navigator({
Key key,
this.routes,
this.onGeneratePage,
this.onUnknownPage
this.onGenerateRoute,
this.onUnknownRoute
}) : super(key: key) {
// To use a navigator, you must at a minimum define the route with the name '/'.
assert(routes != null);
assert(routes.containsKey(_kDefaultPageName));
assert(onGenerateRoute != null);
}
final Map<String, RouteBuilder> routes;
/// you need to implement this if you pushNamed() to names that might not be in routes.
final RouteGenerator onGeneratePage;
final RouteFactory onGenerateRoute;
final RouteFactory onUnknownRoute;
/// 404 generator. You only need to implement this if you have a way to navigate to arbitrary names.
final RouteBuilder onUnknownPage;
static const String defaultRouteName = '/';
static NavigatorState of(BuildContext context) {
NavigatorState result;
......@@ -68,139 +67,65 @@ class Navigator extends StatefulComponent {
}
class NavigatorState extends State<Navigator> {
GlobalKey<OverlayState> _overlay = new GlobalKey<OverlayState>();
List<Route> _history = new List<Route>();
final GlobalKey<OverlayState> _overlay = new GlobalKey<OverlayState>();
final List<Route> _ephemeral = new List<Route>();
final List<Route> _modal = new List<Route>();
void initState() {
super.initState();
_addRouteToHistory(new PageRoute(
builder: config.routes[_kDefaultPageName],
name: _kDefaultPageName
));
}
RouteBuilder _generatePage(String name) {
assert(config.onGeneratePage != null);
return config.onGeneratePage(name);
push(config.onGenerateRoute(new RouteArguments(name: Navigator.defaultRouteName)));
}
bool get hasPreviousRoute => _history.length > 1;
bool get hasPreviousRoute => _modal.length > 1;
void pushNamed(String name, { Set<Key> mostValuableKeys }) {
final RouteBuilder builder = config.routes[name] ?? _generatePage(name) ?? config.onUnknownPage;
assert(builder != null); // 404 getting your 404!
push(new PageRoute(
builder: builder,
name: name,
mostValuableKeys: mostValuableKeys
));
OverlayEntry get _topRouteOverlay {
for (Route route in _ephemeral.reversed) {
if (route.topEntry != null)
return route.topEntry;
}
for (Route route in _modal.reversed) {
if (route.topEntry != null)
return route.topEntry;
}
return null;
}
void _addRouteToHistory(Route route) {
route.willPush();
route._entry = new OverlayEntry(child: route._child);
_history.add(route);
void pushNamed(String name, { Set<Key> mostValuableKeys }) {
RouteArguments args = new RouteArguments(name: name, mostValuableKeys: mostValuableKeys);
push(config.onGenerateRoute(args) ?? config.onUnknownRoute(args));
}
void push(Route route) {
OverlayEntry reference = _history.last._entry;
_addRouteToHistory(route);
_overlay.currentState.insert(route._entry, above: reference);
_popAllEphemeralRoutes();
route.add(_overlay.currentState, _topRouteOverlay);
_modal.add(route);
}
void pop([dynamic result]) {
_history.removeLast().didPop(result);
}
Widget build(BuildContext context) {
return new Overlay(
key: _overlay,
initialEntries: <OverlayEntry>[ _history.first._entry ]
);
}
}
abstract class TransitionRoute extends Route {
PerformanceView get performance => _performance?.view;
Performance _performance;
Duration get transitionDuration;
Performance createPerformance() {
Duration duration = transitionDuration;
assert(duration != null && duration >= Duration.ZERO);
return new Performance(duration: duration, debugLabel: debugLabel);
void pushEphemeral(Route route) {
route.add(_overlay.currentState, _topRouteOverlay);
_ephemeral.add(route);
}
void willPush() {
_performance = createPerformance();
_performance.forward();
super.willPush();
void _popAllEphemeralRoutes() {
List<Route> localEphemeral = new List<Route>.from(_ephemeral);
_ephemeral.clear();
for (Route route in localEphemeral)
route.remove(null);
assert(_ephemeral.isEmpty);
}
Future didPop(dynamic result) async {
await _performance.reverse();
super.didPop(result);
void pop([dynamic result]) {
if (_ephemeral.isNotEmpty) {
_ephemeral.removeLast().remove(result);
return;
}
_modal.removeLast().remove(result);
}
String get debugLabel => '$runtimeType';
String toString() => '$runtimeType(performance: $_performance)';
}
class _Page extends StatefulComponent {
_Page({ Key key, this.route }) : super(key: key);
final PageRoute route;
_PageState createState() => new _PageState();
}
class _PageState extends State<_Page> {
final AnimatedValue<Point> _position =
new AnimatedValue<Point>(const Point(0.0, 75.0), end: Point.origin, curve: Curves.easeOut);
final AnimatedValue<double> _opacity =
new AnimatedValue<double>(0.0, end: 1.0, curve: Curves.easeOut);
Widget build(BuildContext context) {
return new SlideTransition(
performance: config.route.performance,
position: _position,
child: new FadeTransition(
performance: config.route.performance,
opacity: _opacity,
child: _invokeBuilder()
)
return new Overlay(
key: _overlay,
initialEntries: _modal.first._entries
);
}
Widget _invokeBuilder() {
Widget result = config.route.builder(null);
assert(() {
if (result == null)
debugPrint('The builder for route \'${config.route.name}\' returned null. Route builders must never return null.');
assert(result != null && 'A route builder returned null. See the previous log message for details.' is String);
return true;
});
return result;
}
}
class PageRoute extends TransitionRoute {
PageRoute({
this.builder,
this.name: '<anonymous>',
this.mostValuableKeys
}) {
assert(builder != null);
}
final RouteBuilder builder;
final String name;
final Set<Key> mostValuableKeys;
Duration get transitionDuration => const Duration(milliseconds: 150);
Widget createWidget() => new _Page(route: this);
String get debugLabel => '${super.debugLabel}($name)';
}
// Copyright 2015 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/animation.dart';
import 'basic.dart';
import 'framework.dart';
import 'navigator2.dart';
import 'overlay.dart';
import 'transitions.dart';
abstract class TransitionRoute extends Route {
bool get opaque => true;
PerformanceView get performance => _performance?.view;
Performance _performance;
Duration get transitionDuration;
Performance createPerformance() {
Duration duration = transitionDuration;
assert(duration != null && duration >= Duration.ZERO);
return new Performance(duration: duration, debugLabel: debugLabel);
}
dynamic _result;
void _handleStatusChanged(PerformanceStatus status) {
if (status == PerformanceStatus.completed && opaque) {
bottomEntry.opaque = true;
} else if (status == PerformanceStatus.dismissed) {
super.remove(_result);
}
}
void add(OverlayState overlayer, OverlayEntry insertionPoint) {
_performance = createPerformance()
..addStatusListener(_handleStatusChanged)
..forward();
super.add(overlayer, insertionPoint);
}
void remove(dynamic result) {
_result = result;
_performance.reverse();
}
String get debugLabel => '$runtimeType';
String toString() => '$runtimeType(performance: $_performance)';
}
class _Page extends StatefulComponent {
_Page({
PageRoute route
}) : route = route, super(key: new GlobalObjectKey(route));
final PageRoute route;
_PageState createState() => new _PageState();
}
class _PageState extends State<_Page> {
final AnimatedValue<Point> _position =
new AnimatedValue<Point>(const Point(0.0, 75.0), end: Point.origin, curve: Curves.easeOut);
final AnimatedValue<double> _opacity =
new AnimatedValue<double>(0.0, end: 1.0, curve: Curves.easeOut);
Widget build(BuildContext context) {
return new SlideTransition(
performance: config.route.performance,
position: _position,
child: new FadeTransition(
performance: config.route.performance,
opacity: _opacity,
child: _invokeBuilder()
)
);
}
Widget _invokeBuilder() {
Widget result = config.route.builder(context);
assert(() {
if (result == null)
debugPrint('The builder for route \'${config.route.name}\' returned null. Route builders must never return null.');
assert(result != null && 'A route builder returned null. See the previous log message for details.' is String);
return true;
});
return result;
}
}
class PageRoute extends TransitionRoute {
PageRoute({
this.builder,
this.args: const RouteArguments()
}) {
assert(builder != null);
assert(opaque);
}
final WidgetBuilder builder;
final RouteArguments args;
String get name => args.name;
Duration get transitionDuration => const Duration(milliseconds: 150);
List<Widget> createWidgets() => [ new _Page(route: this) ];
String get debugLabel => '${super.debugLabel}($name)';
}
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