Commit 5b3b3dfb authored by Adam Barth's avatar Adam Barth

Merge pull request #1862 from abarth/navigator2

Add an initial implementation of Navigator2
parents 90a3a20d b991b7d6
......@@ -4,51 +4,71 @@
import 'package:flutter/material.dart';
final Map<String, RouteBuilder> routes = <String, RouteBuilder>{
'/': (RouteArguments args) => new Container(
import 'package:flutter/src/widgets/navigator2.dart' as n2;
class Home extends StatelessComponent {
Widget build(BuildContext context) {
return new Container(
padding: const EdgeDims.all(30.0),
decoration: new BoxDecoration(backgroundColor: const Color(0xFFCCCCCC)),
child: new Column(<Widget>[
new Text("You are at home"),
new RaisedButton(
child: new Text('GO SHOPPING'),
onPressed: () => Navigator.of(args.context).pushNamed('/shopping')
onPressed: () => n2.Navigator.of(context).pushNamed('/shopping')
),
new RaisedButton(
child: new Text('START ADVENTURE'),
onPressed: () => Navigator.of(args.context).pushNamed('/adventure')
onPressed: () => n2.Navigator.of(context).pushNamed('/adventure')
)],
justifyContent: FlexJustifyContent.center
)
),
'/shopping': (RouteArguments args) => new Container(
);
}
}
class Shopping extends StatelessComponent {
Widget build(BuildContext context) {
return new Container(
padding: const EdgeDims.all(20.0),
decoration: new BoxDecoration(backgroundColor: const Color(0xFFBF5FFF)),
child: new Column(<Widget>[
new Text("Village Shop"),
new RaisedButton(
child: new Text('RETURN HOME'),
onPressed: () => Navigator.of(args.context).pop()
onPressed: () => n2.Navigator.of(context).pop()
),
new RaisedButton(
child: new Text('GO TO DUNGEON'),
onPressed: () => Navigator.of(args.context).pushNamed('/adventure')
onPressed: () => n2.Navigator.of(context).pushNamed('/adventure')
)],
justifyContent: FlexJustifyContent.center
)
),
'/adventure': (RouteArguments args) => new Container(
);
}
}
class Adventure extends StatelessComponent {
Widget build(BuildContext context) {
return new Container(
padding: const EdgeDims.all(20.0),
decoration: new BoxDecoration(backgroundColor: const Color(0xFFDC143C)),
child: new Column(<Widget>[
new Text("Monster's Lair"),
new RaisedButton(
child: new Text('RUN!!!'),
onPressed: () => Navigator.of(args.context).pop()
onPressed: () => n2.Navigator.of(context).pop()
)],
justifyContent: FlexJustifyContent.center
)
)
);
}
}
final Map<String, RouteBuilder> routes = <String, RouteBuilder>{
'/': (_) => new Home(),
'/shopping': (_) => new Shopping(),
'/adventure': (_) => new Adventure(),
};
final ThemeData theme = new ThemeData(
......
......@@ -8,6 +8,8 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/src/widgets/navigator2.dart' as n2;
import 'theme.dart';
import 'title.dart';
......@@ -31,6 +33,8 @@ AssetBundle _initDefaultBundle() {
final AssetBundle _defaultBundle = _initDefaultBundle();
const bool _kUseNavigator2 = false;
class MaterialApp extends StatefulComponent {
MaterialApp({
Key key,
......@@ -83,6 +87,19 @@ class _MaterialAppState extends State<MaterialApp> {
void _metricHandler(Size size) => setState(() { _size = size; });
Widget build(BuildContext context) {
Widget navigator;
if (_kUseNavigator2) {
navigator = new n2.Navigator(
key: _navigator,
routes: config.routes
);
} else {
navigator = new Navigator(
key: _navigator,
routes: config.routes,
onGenerateRoute: config.onGenerateRoute
);
}
return new MediaQuery(
data: new MediaQueryData(size: _size),
child: new Theme(
......@@ -93,11 +110,7 @@ class _MaterialAppState extends State<MaterialApp> {
bundle: _defaultBundle,
child: new Title(
title: config.title,
child: new Navigator(
key: _navigator,
routes: config.routes,
onGenerateRoute: config.onGenerateRoute
)
child: navigator
)
)
)
......
// 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 '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();
Widget _child;
OverlayEntry _entry;
void willPush() {
_child = createWidget();
}
void didPop(dynamic result) {
_entry.remove();
}
}
typedef Widget RouteBuilder(args);
typedef RouteBuilder RouteGenerator(String name);
const String _kDefaultPageName = '/';
class Navigator extends StatefulComponent {
Navigator({
Key key,
this.routes,
this.onGeneratePage,
this.onUnknownPage
}) : 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));
}
final Map<String, RouteBuilder> routes;
/// you need to implement this if you pushNamed() to names that might not be in routes.
final RouteGenerator onGeneratePage;
/// 404 generator. You only need to implement this if you have a way to navigate to arbitrary names.
final RouteBuilder onUnknownPage;
static NavigatorState of(BuildContext context) {
NavigatorState result;
context.visitAncestorElements((Element element) {
if (element is StatefulComponentElement && element.state is NavigatorState) {
result = element.state;
return false;
}
return true;
});
return result;
}
NavigatorState createState() => new NavigatorState();
}
class NavigatorState extends State<Navigator> {
GlobalKey<OverlayState> _overlay = new GlobalKey<OverlayState>();
List<Route> _history = 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);
}
bool get hasPreviousRoute => _history.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
));
}
void _addRouteToHistory(Route route) {
route.willPush();
route._entry = new OverlayEntry(child: route._child);
_history.add(route);
}
void push(Route route) {
OverlayEntry reference = _history.last._entry;
_addRouteToHistory(route);
_overlay.currentState.insert(route._entry, above: reference);
}
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 willPush() {
_performance = createPerformance();
_performance.forward();
super.willPush();
}
Future didPop(dynamic result) async {
await _performance.reverse();
super.didPop(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()
)
);
}
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 'basic.dart';
import 'framework.dart';
class OverlayEntry {
OverlayEntry({
this.child,
bool opaque: false
}) : _opaque = opaque;
final Widget child;
bool get opaque => _opaque;
bool _opaque;
void set opaque(bool value) {
_opaque = value;
_state?.setState(() {});
}
OverlayState _state;
/// Remove the entry from the overlay.
void remove() {
_state?._remove(this);
_state = null;
}
}
class Overlay extends StatefulComponent {
Overlay({
Key key,
this.initialEntries
}) : super(key: key);
final List<OverlayEntry> initialEntries;
OverlayState createState() => new OverlayState();
}
class OverlayState extends State<Overlay> {
final List<OverlayEntry> _entries = new List<OverlayEntry>();
void initState() {
super.initState();
for (OverlayEntry entry in config.initialEntries)
insert(entry);
}
void insert(OverlayEntry entry, { OverlayEntry above }) {
assert(entry._state == null);
if (above != null) {
print('above._state ${above._state} --- ${above._state == this}');
print('_entries.contains ${_entries.contains(above)}');
}
assert(above == null || (above._state == this && _entries.contains(above)));
entry._state = this;
setState(() {
int index = above == null ? _entries.length : _entries.indexOf(above) + 1;
_entries.insert(index, entry);
});
}
void _remove(OverlayEntry entry) {
setState(() {
_entries.remove(entry);
});
}
Widget build(BuildContext context) {
List<Widget> backwardsChildren = <Widget>[];
for (int i = _entries.length - 1; i >= 0; --i) {
OverlayEntry entry = _entries[i];
backwardsChildren.add(new KeyedSubtree(
key: new ObjectKey(entry),
child: entry.child
));
if (entry.opaque)
break;
}
return new Stack(backwardsChildren.reversed.toList(growable: false));
}
}
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