diff --git a/examples/stocks/lib/stock_home.dart b/examples/stocks/lib/stock_home.dart index 9e1fa4a009..c606972389 100644 --- a/examples/stocks/lib/stock_home.dart +++ b/examples/stocks/lib/stock_home.dart @@ -24,22 +24,20 @@ class StockHomeState extends State { String _searchQuery; void _handleSearchBegin() { - Navigator.of(context).pushState(this, (_) { - setState(() { - _isSearching = false; - _searchQuery = null; - }); - }); + Navigator.of(context).push(new StateRoute( + onPop: () { + setState(() { + _isSearching = false; + _searchQuery = null; + }); + } + )); setState(() { _isSearching = true; }); } void _handleSearchEnd() { - assert(() { - final StateRoute currentRoute = Navigator.of(context).currentRoute; - return currentRoute.owner == this; - }); Navigator.of(context).pop(); } diff --git a/examples/widgets/navigation.dart b/examples/widgets/navigation.dart index f4ebc0465b..3e468a6d45 100644 --- a/examples/widgets/navigation.dart +++ b/examples/widgets/navigation.dart @@ -4,8 +4,6 @@ import 'package:flutter/material.dart'; -import 'package:flutter/src/widgets/navigator2.dart' as n2; - class Home extends StatelessComponent { Widget build(BuildContext context) { return new Container( @@ -15,11 +13,11 @@ class Home extends StatelessComponent { new Text("You are at home"), new RaisedButton( child: new Text('GO SHOPPING'), - onPressed: () => n2.Navigator.of(context).pushNamed('/shopping') + onPressed: () => Navigator.of(context).pushNamed('/shopping') ), new RaisedButton( child: new Text('START ADVENTURE'), - onPressed: () => n2.Navigator.of(context).pushNamed('/adventure') + onPressed: () => Navigator.of(context).pushNamed('/adventure') )], justifyContent: FlexJustifyContent.center ) @@ -36,11 +34,11 @@ class Shopping extends StatelessComponent { new Text("Village Shop"), new RaisedButton( child: new Text('RETURN HOME'), - onPressed: () => n2.Navigator.of(context).pop() + onPressed: () => Navigator.of(context).pop() ), new RaisedButton( child: new Text('GO TO DUNGEON'), - onPressed: () => n2.Navigator.of(context).pushNamed('/adventure') + onPressed: () => Navigator.of(context).pushNamed('/adventure') )], justifyContent: FlexJustifyContent.center ) @@ -57,7 +55,7 @@ class Adventure extends StatelessComponent { new Text("Monster's Lair"), new RaisedButton( child: new Text('RUN!!!'), - onPressed: () => n2.Navigator.of(context).pop() + onPressed: () => Navigator.of(context).pop() )], justifyContent: FlexJustifyContent.center ) diff --git a/packages/flutter/lib/src/material/bottom_sheet.dart b/packages/flutter/lib/src/material/bottom_sheet.dart index 864c465944..9ec5cbf598 100644 --- a/packages/flutter/lib/src/material/bottom_sheet.dart +++ b/packages/flutter/lib/src/material/bottom_sheet.dart @@ -17,14 +17,9 @@ const double _kMinFlingVelocity = 700.0; const double _kFlingVelocityScale = 1.0 / 300.0; class _BottomSheet extends StatefulComponent { - _BottomSheet({ - Key key, - this.child, - this.performance - }) : super(key: key); + _BottomSheet({ Key key, this.route }) : super(key: key); - final Widget child; - final Performance performance; + final _ModalBottomSheetRoute route; _BottomSheetState createState() => new _BottomSheetState(); } @@ -54,47 +49,62 @@ class _BottomSheetState extends State<_BottomSheet> { bool _dragEnabled = false; void _handleDragStart(Point position) { - _dragEnabled = !config.performance.isAnimating; + _dragEnabled = !config.route._performance.isAnimating; } void _handleDragUpdate(double delta) { if (!_dragEnabled) return; - config.performance.progress -= delta / _layout.childTop.end; + config.route._performance.progress -= delta / _layout.childTop.end; } void _handleDragEnd(Offset velocity) { if (!_dragEnabled) return; if (velocity.dy > _kMinFlingVelocity) - config.performance.fling(velocity: -velocity.dy * _kFlingVelocityScale); + config.route._performance.fling(velocity: -velocity.dy * _kFlingVelocityScale); else - config.performance.forward(); + config.route._performance.forward(); } Widget build(BuildContext context) { - return new BuilderTransition( - performance: config.performance, - variables: >[_layout.childTop], - builder: (BuildContext context) { - return new ClipRect( - child: new CustomOneChildLayout( - delegate: _layout, - token: _layout.childTop.value, - child: new GestureDetector( - onVerticalDragStart: _handleDragStart, - onVerticalDragUpdate: _handleDragUpdate, - onVerticalDragEnd: _handleDragEnd, - child: new Material(child: config.child) - ) + return new Focus( + key: new GlobalObjectKey(config.route), + autofocus: true, + child: new GestureDetector( + onTap: () { Navigator.of(context).pop(); }, + child: new Stack([ + // mask + new ColorTransition( + performance: config.route._performance, + color: new AnimatedColorValue(Colors.transparent, end: Colors.black54), + child: new Container() + ), + new BuilderTransition( + performance: config.route._performance, + variables: >[_layout.childTop], + builder: (BuildContext context) { + return new ClipRect( + child: new CustomOneChildLayout( + delegate: _layout, + token: _layout.childTop.value, + child: new GestureDetector( + onVerticalDragStart: _handleDragStart, + onVerticalDragUpdate: _handleDragUpdate, + onVerticalDragEnd: _handleDragEnd, + child: new Material(child: config.route.child) + ) + ) + ); + } ) - ); - } + ]) + ) ); } } -class _ModalBottomSheetRoute extends Route { +class _ModalBottomSheetRoute extends TransitionRoute { _ModalBottomSheetRoute({ this.completer, this.child }) { _performance = new Performance(duration: transitionDuration, debugLabel: 'ModalBottomSheet'); } @@ -102,53 +112,27 @@ class _ModalBottomSheetRoute extends Route { final Completer completer; final Widget child; - PerformanceView get performance => _performance?.view; - Performance _performance; - - bool get ephemeral => true; - bool get modal => true; bool get opaque => false; Duration get transitionDuration => _kBottomSheetDuration; - Widget build(RouteArguments args) { - return new Focus( - key: new GlobalObjectKey(this), - autofocus: true, - child: new GestureDetector( - onTap: () { navigator.pop(); }, - child: new Stack([ - // mask - new ColorTransition( - performance: performance, - color: new AnimatedColorValue(Colors.transparent, end: Colors.black54), - child: new Container() - ), - // sheet - new _BottomSheet( - performance: _performance, - child: child - ) - ]) - ) - ); + Performance _performance; + + Performance createPerformance() { + _performance = super.createPerformance(); + return _performance; } - void didPush(NavigatorState navigator) { - super.didPush(navigator); - _performance?.forward(); - } + List createWidgets() => [ new _BottomSheet(route: this) ]; void didPop([dynamic result]) { completer.complete(result); super.didPop(result); - if (_performance.status != PerformanceStatus.dismissed) - _performance?.reverse(); } } Future showModalBottomSheet({ BuildContext context, Widget child }) { final Completer completer = new Completer(); - Navigator.of(context).push(new _ModalBottomSheetRoute( + Navigator.of(context).pushEphemeral(new _ModalBottomSheetRoute( completer: completer, child: child )); diff --git a/packages/flutter/lib/src/material/dialog.dart b/packages/flutter/lib/src/material/dialog.dart index f947d60abd..a892e48979 100644 --- a/packages/flutter/lib/src/material/dialog.dart +++ b/packages/flutter/lib/src/material/dialog.dart @@ -100,6 +100,7 @@ class Dialog extends StatelessComponent { )); } + // TODO(abarth): We should return the backdrop as a separate entry from createWidgets. return new Stack([ new GestureDetector( onTap: onDismiss, @@ -130,21 +131,27 @@ class Dialog extends StatelessComponent { } } -class _DialogRoute extends PerformanceRoute { - _DialogRoute({ this.completer, this.builder }); +class _DialogRoute extends TransitionRoute { + _DialogRoute({ this.completer, this.child }); final Completer completer; - final RouteBuilder builder; + final Widget child; bool get opaque => false; Duration get transitionDuration => const Duration(milliseconds: 150); - Widget build(RouteArguments args) { - return new FadeTransition( - performance: args.previousPerformance, - opacity: new AnimatedValue(0.0, end: 1.0, curve: Curves.easeOut), - child: builder(args) - ); + List createWidgets() { + return [ + new Focus( + key: new GlobalObjectKey(this), + autofocus: true, + child: new FadeTransition( + performance: performance, + opacity: new AnimatedValue(0.0, end: 1.0, curve: Curves.easeOut), + child: child + ) + ) + ]; } void didPop([dynamic result]) { @@ -155,15 +162,6 @@ class _DialogRoute extends PerformanceRoute { Future showDialog({ BuildContext context, Widget child }) { Completer completer = new Completer(); - Navigator.of(context).push(new _DialogRoute( - completer: completer, - builder: (RouteArguments args) { - return new Focus( - key: new GlobalObjectKey(completer), - autofocus: true, - child: child - ); - } - )); + Navigator.of(context).push(new _DialogRoute(completer: completer, child: child)); return completer.future; } diff --git a/packages/flutter/lib/src/material/drawer.dart b/packages/flutter/lib/src/material/drawer.dart index f6ddba64c4..ef5827491e 100644 --- a/packages/flutter/lib/src/material/drawer.dart +++ b/packages/flutter/lib/src/material/drawer.dart @@ -31,115 +31,90 @@ const Point _kOpenPosition = Point.origin; const Point _kClosedPosition = const Point(-_kWidth, 0.0); class _Drawer extends StatelessComponent { + _Drawer({ Key key, this.route }) : super(key: key); - _Drawer({ - Key key, - this.child, - this.level: 3, - this.performance, - this.interactive, - this.route - }) : super(key: key); - - final Widget child; - final int level; - final PerformanceView performance; - final bool interactive; final _DrawerRoute route; Widget build(BuildContext context) { - return new GestureDetector( - onHorizontalDragStart: (_) { - if (interactive) - route._takeControl(); - }, - onHorizontalDragUpdate: (double delta) { - if (interactive) - route._moveDrawer(delta); - }, - onHorizontalDragEnd: (Offset velocity) { - if (interactive) - route._settle(velocity); - }, - child: new Stack([ - // mask - new GestureDetector( - onTap: () { - if (interactive) - route._close(); - }, - child: new ColorTransition( - performance: performance, - color: new AnimatedColorValue(Colors.transparent, end: Colors.black54), - child: new Container() - ) - ), - // drawer - new Positioned( - top: 0.0, - left: 0.0, - bottom: 0.0, - child: new SlideTransition( - performance: performance, - position: new AnimatedValue(_kClosedPosition, end: _kOpenPosition), - child: new AnimatedContainer( - curve: Curves.ease, - duration: _kThemeChangeDuration, - decoration: new BoxDecoration( - backgroundColor: Theme.of(context).canvasColor, - boxShadow: shadows[level]), - width: _kWidth, - child: child + return new Focus( + key: new GlobalObjectKey(route), + autofocus: true, + child: new GestureDetector( + onHorizontalDragStart: (_) { + if (route.interactive) + route._takeControl(); + }, + onHorizontalDragUpdate: (double delta) { + if (route.interactive) + route._moveDrawer(delta); + }, + onHorizontalDragEnd: (Offset velocity) { + if (route.interactive) + route._settle(velocity); + }, + child: new Stack([ + // mask + new GestureDetector( + onTap: () { + if (route.interactive) + route._close(); + }, + child: new ColorTransition( + performance: route.performance, + color: new AnimatedColorValue(Colors.transparent, end: Colors.black54), + child: new Container() + ) + ), + new Positioned( + top: 0.0, + left: 0.0, + bottom: 0.0, + child: new SlideTransition( + performance: route.performance, + position: new AnimatedValue(_kClosedPosition, end: _kOpenPosition), + child: new AnimatedContainer( + curve: Curves.ease, + duration: _kThemeChangeDuration, + decoration: new BoxDecoration( + backgroundColor: Theme.of(context).canvasColor, + boxShadow: shadows[route.level]), + width: _kWidth, + child: route.child + ) ) ) - ) - ]) + ]) + ) ); } } -class _DrawerRoute extends Route { +class _DrawerRoute extends TransitionRoute { _DrawerRoute({ this.child, this.level }); final Widget child; final int level; - PerformanceView get performance => _performance?.view; - Performance _performance = new Performance(duration: _kBaseSettleDuration, debugLabel: 'Drawer'); - + Duration get transitionDuration => _kBaseSettleDuration; bool get opaque => false; + bool get interactive => _interactive; bool _interactive = true; - Widget build(RouteArguments args) { - return new Focus( - key: new GlobalObjectKey(this), - autofocus: true, - child: new _Drawer( - child: child, - level: level, - performance: performance, - interactive: _interactive, - route: this - ) - ); + Performance _performance; + + Performance createPerformance() { + _performance = super.createPerformance(); + return _performance; } - void didPush(NavigatorState navigator) { - super.didPush(navigator); - _performance.forward(); - } + List createWidgets() => [ new _Drawer(route: this) ]; void didPop([dynamic result]) { assert(result == null); // because we don't do anything with it, so otherwise it'd be lost super.didPop(result); - if (_performance.status != PerformanceStatus.dismissed) - _performance.reverse(); - setState(() { - _interactive = false; - // TODO(ianh): https://github.com/flutter/engine/issues/1539 - }); + _interactive = false; } void _takeControl() { diff --git a/packages/flutter/lib/src/material/dropdown.dart b/packages/flutter/lib/src/material/dropdown.dart index b70511090a..c512d04a98 100644 --- a/packages/flutter/lib/src/material/dropdown.dart +++ b/packages/flutter/lib/src/material/dropdown.dart @@ -22,23 +22,9 @@ const double _kBaselineOffsetFromBottom = 20.0; const Border _kDropdownUnderline = const Border(bottom: const BorderSide(color: const Color(0xFFBDBDBD), width: 2.0)); class _DropdownMenu extends StatelessComponent { - _DropdownMenu({ - Key key, - this.items, - this.rect, - this.performance, - this.selectedIndex, - this.level: 4 - }) : super(key: key) { - assert(items != null); - assert(performance != null); - } + _DropdownMenu({ Key key, this.route }) : super(key: key); - final List items; - final Rect rect; - final PerformanceView performance; - final int selectedIndex; - final int level; + final _MenuRoute route; Widget build(BuildContext context) { // The menu is shown in three stages (unit timing in brackets): @@ -50,11 +36,11 @@ class _DropdownMenu extends StatelessComponent { // When the menu is dismissed we just fade the entire thing out // in the first 0.25. - final double unit = 0.5 / (items.length + 1.5); + final double unit = 0.5 / (route.items.length + 1.5); final List children = []; - for (int itemIndex = 0; itemIndex < items.length; ++itemIndex) { + for (int itemIndex = 0; itemIndex < route.items.length; ++itemIndex) { AnimatedValue opacity; - if (itemIndex == selectedIndex) { + if (itemIndex == route.selectedIndex) { opacity = new AnimatedValue(0.0, end: 1.0, curve: const Interval(0.0, 0.001), reverseCurve: const Interval(0.75, 1.0)); } else { final double start = (0.5 + (itemIndex + 1) * unit).clamp(0.0, 1.0); @@ -62,12 +48,12 @@ class _DropdownMenu extends StatelessComponent { opacity = new AnimatedValue(0.0, end: 1.0, curve: new Interval(start, end), reverseCurve: const Interval(0.75, 1.0)); } children.add(new FadeTransition( - performance: performance, + performance: route.performance, opacity: opacity, child: new InkWell( - child: items[itemIndex], + child: route.items[itemIndex], onTap: () { - Navigator.of(context).pop(items[itemIndex].value); + Navigator.of(context).pop(route.items[itemIndex].value); } ) )); @@ -79,13 +65,13 @@ class _DropdownMenu extends StatelessComponent { reverseCurve: new Interval(0.75, 1.0) ); - final AnimatedValue menuTop = new AnimatedValue(rect.top, - end: rect.top - selectedIndex * rect.height, + final AnimatedValue menuTop = new AnimatedValue(route.rect.top, + end: route.rect.top - route.selectedIndex * route.rect.height, curve: new Interval(0.25, 0.5), reverseCurve: const Interval(0.0, 0.001) ); - final AnimatedValue menuBottom = new AnimatedValue(rect.bottom, - end: menuTop.end + items.length * rect.height, + final AnimatedValue menuBottom = new AnimatedValue(route.rect.bottom, + end: menuTop.end + route.items.length * route.rect.height, curve: new Interval(0.25, 0.5), reverseCurve: const Interval(0.0, 0.001) ); @@ -93,32 +79,45 @@ class _DropdownMenu extends StatelessComponent { final BoxPainter menuPainter = new BoxPainter(new BoxDecoration( backgroundColor: Theme.of(context).canvasColor, borderRadius: 2.0, - boxShadow: shadows[level] + boxShadow: shadows[route.level] )); - return new FadeTransition( - performance: performance, - opacity: menuOpacity, - child: new BuilderTransition( - performance: performance, - variables: >[menuTop, menuBottom], - builder: (BuildContext context) { - RenderBox renderBox = context.findRenderObject(); - return new CustomPaint( - child: new ScrollableViewport(child: new Container(child: new Column(children))), - onPaint: (ui.Canvas canvas, Size size) { - double top = renderBox.globalToLocal(new Point(0.0, menuTop.value)).y; - double bottom = renderBox.globalToLocal(new Point(0.0, menuBottom.value)).y; - menuPainter.paint(canvas, new Rect.fromLTRB(0.0, top, size.width, bottom)); + final RenderBox renderBox = Navigator.of(context).context.findRenderObject(); + final Size navigatorSize = renderBox.size; + final RelativeRect menuRect = new RelativeRect.fromSize(route.rect, navigatorSize); + + return new Positioned( + top: menuRect.top - (route.selectedIndex * route.rect.height), + right: menuRect.right - _kMenuHorizontalPadding, + left: menuRect.left - _kMenuHorizontalPadding, + child: new Focus( + key: new GlobalObjectKey(route), + autofocus: true, + child: new FadeTransition( + performance: route.performance, + opacity: menuOpacity, + child: new BuilderTransition( + performance: route.performance, + variables: >[menuTop, menuBottom], + builder: (BuildContext context) { + RenderBox renderBox = context.findRenderObject(); + return new CustomPaint( + child: new ScrollableViewport(child: new Container(child: new Column(children))), + onPaint: (ui.Canvas canvas, Size size) { + double top = renderBox.globalToLocal(new Point(0.0, menuTop.value)).y; + double bottom = renderBox.globalToLocal(new Point(0.0, menuBottom.value)).y; + menuPainter.paint(canvas, new Rect.fromLTRB(0.0, top, size.width, bottom)); + } + ); } - ); - } + ) + ) ) ); } } -class _MenuRoute extends PerformanceRoute { +class _MenuRoute extends TransitionRoute { _MenuRoute({ this.completer, this.items, @@ -133,33 +132,13 @@ class _MenuRoute extends PerformanceRoute { final int level; final int selectedIndex; - bool get ephemeral => true; - bool get modal => true; bool get opaque => false; Duration get transitionDuration => _kMenuDuration; - Widget build(RouteArguments args) { - final RenderBox renderBox = navigator.context.findRenderObject(); - final Size navigatorSize = renderBox.size; - final RelativeRect menuRect = new RelativeRect.fromSize(rect, navigatorSize); - - return new Positioned( - top: menuRect.top - (selectedIndex * rect.height), - right: menuRect.right - _kMenuHorizontalPadding, - left: menuRect.left - _kMenuHorizontalPadding, - child: new Focus( - key: new GlobalObjectKey(this), - autofocus: true, - child: new _DropdownMenu( - items: items, - selectedIndex: selectedIndex, - rect: rect, - level: level, - performance: performance - ) - ) - ); - } + List createWidgets() => [ + new ModalBarrier(), + new _DropdownMenu(route: this) + ]; void didPop([dynamic result]) { completer.complete(result); @@ -210,7 +189,7 @@ class DropdownButton extends StatelessComponent { final RenderBox renderBox = indexedStackKey.currentContext.findRenderObject(); final Rect rect = renderBox.localToGlobal(Point.origin) & renderBox.size; final Completer completer = new Completer(); - Navigator.of(context).push(new _MenuRoute( + Navigator.of(context).pushEphemeral(new _MenuRoute( completer: completer, items: items, selectedIndex: selectedIndex, diff --git a/packages/flutter/lib/src/material/material_app.dart b/packages/flutter/lib/src/material/material_app.dart index 117c6d5c03..01be12943e 100644 --- a/packages/flutter/lib/src/material/material_app.dart +++ b/packages/flutter/lib/src/material/material_app.dart @@ -8,9 +8,6 @@ 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 'package:flutter/src/widgets/hero_controller.dart' as n2; - import 'theme.dart'; import 'title.dart'; @@ -34,8 +31,6 @@ AssetBundle _initDefaultBundle() { final AssetBundle _defaultBundle = _initDefaultBundle(); -const bool _kUseNavigator2 = false; - class MaterialApp extends StatefulComponent { MaterialApp({ Key key, @@ -87,10 +82,10 @@ class _MaterialAppState extends State { void _metricHandler(Size size) => setState(() { _size = size; }); - final n2.HeroController _heroController = new n2.HeroController(); + final HeroController _heroController = new HeroController(); - n2.Route _generateRoute(n2.NamedRouteSettings settings) { - return new n2.HeroPageRoute( + Route _generateRoute(NamedRouteSettings settings) { + return new HeroPageRoute( builder: (BuildContext context) { RouteBuilder builder = config.routes[settings.name] ?? config.onGenerateRoute(settings.name); return builder(new RouteArguments(context: context)); @@ -101,19 +96,6 @@ class _MaterialAppState extends State { } Widget build(BuildContext context) { - Widget navigator; - if (_kUseNavigator2) { - navigator = new n2.Navigator( - key: _navigator, - onGenerateRoute: _generateRoute - ); - } else { - navigator = new Navigator( - key: _navigator, - routes: config.routes, - onGenerateRoute: config.onGenerateRoute - ); - } return new MediaQuery( data: new MediaQueryData(size: _size), child: new Theme( @@ -124,7 +106,10 @@ class _MaterialAppState extends State { bundle: _defaultBundle, child: new Title( title: config.title, - child: navigator + child: new Navigator( + key: _navigator, + onGenerateRoute: _generateRoute + ) ) ) ) diff --git a/packages/flutter/lib/src/material/popup_menu.dart b/packages/flutter/lib/src/material/popup_menu.dart index 8954fe4f94..3fe413a983 100644 --- a/packages/flutter/lib/src/material/popup_menu.dart +++ b/packages/flutter/lib/src/material/popup_menu.dart @@ -22,80 +22,84 @@ const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep; const double _kMenuHorizontalPadding = 16.0; const double _kMenuVerticalPadding = 8.0; -class PopupMenu extends StatelessComponent { - PopupMenu({ +class _PopupMenu extends StatelessComponent { + _PopupMenu({ Key key, - this.items, - this.level: 4, - this.performance - }) : super(key: key) { - assert(items != null); - assert(performance != null); - } + this.route + }) : super(key: key); - final List items; - final int level; - final PerformanceView performance; + final _MenuRoute route; Widget build(BuildContext context) { final BoxPainter painter = new BoxPainter(new BoxDecoration( backgroundColor: Theme.of(context).canvasColor, borderRadius: 2.0, - boxShadow: shadows[level] + boxShadow: shadows[route.level] )); - double unit = 1.0 / (items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade. + double unit = 1.0 / (route.items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade. List children = []; - for (int i = 0; i < items.length; ++i) { + for (int i = 0; i < route.items.length; ++i) { double start = (i + 1) * unit; double end = (start + 1.5 * unit).clamp(0.0, 1.0); children.add(new FadeTransition( - performance: performance, + performance: route.performance, opacity: new AnimatedValue(0.0, end: 1.0, curve: new Interval(start, end)), child: new InkWell( - onTap: () { Navigator.of(context).pop(items[i].value); }, - child: items[i] + onTap: () { Navigator.of(context).pop(route.items[i].value); }, + child: route.items[i] )) ); } final AnimatedValue width = new AnimatedValue(0.0, end: 1.0, curve: new Interval(0.0, unit)); - final AnimatedValue height = new AnimatedValue(0.0, end: 1.0, curve: new Interval(0.0, unit * items.length)); + final AnimatedValue height = new AnimatedValue(0.0, end: 1.0, curve: new Interval(0.0, unit * route.items.length)); - return new FadeTransition( - performance: performance, - opacity: new AnimatedValue(0.0, end: 1.0, curve: new Interval(0.0, 1.0 / 3.0)), - child: new BuilderTransition( - performance: performance, - variables: >[width, height], - builder: (BuildContext context) { - return new CustomPaint( - onPaint: (ui.Canvas canvas, Size size) { - double widthValue = width.value * size.width; - double heightValue = height.value * size.height; - painter.paint(canvas, new Rect.fromLTWH(size.width - widthValue, 0.0, widthValue, heightValue)); - }, - child: new ConstrainedBox( - constraints: new BoxConstraints( - minWidth: _kMenuMinWidth, - maxWidth: _kMenuMaxWidth - ), - child: new IntrinsicWidth( - stepWidth: _kMenuWidthStep, - child: new ScrollableViewport( - child: new Container( - padding: const EdgeDims.symmetric( - horizontal: _kMenuHorizontalPadding, - vertical: _kMenuVerticalPadding - ), - child: new BlockBody(children) + return new Positioned( + top: route.position?.top, + right: route.position?.right, + bottom: route.position?.bottom, + left: route.position?.left, + child: new Focus( + key: new GlobalObjectKey(route), + autofocus: true, + child: new FadeTransition( + performance: route.performance, + opacity: new AnimatedValue(0.0, end: 1.0, curve: new Interval(0.0, 1.0 / 3.0)), + child: new BuilderTransition( + performance: route.performance, + variables: >[width, height], + builder: (BuildContext context) { + return new CustomPaint( + onPaint: (ui.Canvas canvas, Size size) { + double widthValue = width.value * size.width; + double heightValue = height.value * size.height; + painter.paint(canvas, new Rect.fromLTWH(size.width - widthValue, 0.0, widthValue, heightValue)); + }, + child: new ConstrainedBox( + constraints: new BoxConstraints( + minWidth: _kMenuMinWidth, + maxWidth: _kMenuMaxWidth + ), + child: new IntrinsicWidth( + stepWidth: _kMenuWidthStep, + child: new ScrollableViewport( + child: new Container( + // TODO(abarth): Teach Block about padding. + padding: const EdgeDims.symmetric( + horizontal: _kMenuHorizontalPadding, + vertical: _kMenuVerticalPadding + ), + child: new BlockBody(children) + ) + ) ) ) - ) - ) - ); - } + ); + } + ) + ) ) ); } @@ -109,7 +113,7 @@ class MenuPosition { final double left; } -class _MenuRoute extends PerformanceRoute { +class _MenuRoute extends TransitionRoute { _MenuRoute({ this.completer, this.position, this.items, this.level }); final Completer completer; @@ -125,28 +129,13 @@ class _MenuRoute extends PerformanceRoute { return result; } - bool get ephemeral => true; - bool get modal => true; bool get opaque => false; Duration get transitionDuration => _kMenuDuration; - Widget build(RouteArguments args) { - return new Positioned( - top: position?.top, - right: position?.right, - bottom: position?.bottom, - left: position?.left, - child: new Focus( - key: new GlobalObjectKey(this), - autofocus: true, - child: new PopupMenu( - items: items, - level: level, - performance: performance - ) - ) - ); - } + List createWidgets() => [ + new ModalBarrier(), + new _PopupMenu(route: this), + ]; void didPop([dynamic result]) { completer.complete(result); @@ -156,7 +145,7 @@ class _MenuRoute extends PerformanceRoute { Future showMenu({ BuildContext context, MenuPosition position, List items, int level: 4 }) { Completer completer = new Completer(); - Navigator.of(context).push(new _MenuRoute( + Navigator.of(context).pushEphemeral(new _MenuRoute( completer: completer, position: position, items: items, diff --git a/packages/flutter/lib/src/material/snack_bar.dart b/packages/flutter/lib/src/material/snack_bar.dart index f8d83dc246..6b59282210 100644 --- a/packages/flutter/lib/src/material/snack_bar.dart +++ b/packages/flutter/lib/src/material/snack_bar.dart @@ -34,19 +34,19 @@ class SnackBarAction extends StatelessComponent { } } -class SnackBar extends StatelessComponent { - SnackBar({ +class _SnackBar extends StatelessComponent { + _SnackBar({ Key key, this.content, this.actions, - this.performance + this.route }) : super(key: key) { assert(content != null); } final Widget content; final List actions; - final PerformanceView performance; + final _SnackBarRoute route; Widget build(BuildContext context) { List children = [ @@ -63,7 +63,7 @@ class SnackBar extends StatelessComponent { if (actions != null) children.addAll(actions); return new SquashTransition( - performance: performance, + performance: route.performance, height: new AnimatedValue( 0.0, end: kSnackBarHeight, @@ -91,27 +91,18 @@ class SnackBar extends StatelessComponent { } } -class _SnackBarRoute extends PerformanceRoute { - _SnackBarRoute({ this.content, this.actions }); - - final Widget content; - final List actions; - - bool get hasContent => false; - bool get ephemeral => true; - bool get modal => false; +class _SnackBarRoute extends TransitionRoute { + bool get opaque => false; Duration get transitionDuration => const Duration(milliseconds: 200); - - Widget build(RouteArguments args) => null; } void showSnackBar({ BuildContext context, GlobalKey placeholderKey, Widget content, List actions }) { - Route route = new _SnackBarRoute(); - SnackBar snackBar = new SnackBar( + _SnackBarRoute route = new _SnackBarRoute(); + _SnackBar snackBar = new _SnackBar( + route: route, content: content, - actions: actions, - performance: route.performance + actions: actions ); placeholderKey.currentState.child = snackBar; - Navigator.of(context).push(route); + Navigator.of(context).pushEphemeral(route); } diff --git a/packages/flutter/lib/src/widgets/drag_target.dart b/packages/flutter/lib/src/widgets/drag_target.dart index 24b231b031..4822615171 100644 --- a/packages/flutter/lib/src/widgets/drag_target.dart +++ b/packages/flutter/lib/src/widgets/drag_target.dart @@ -10,6 +10,7 @@ import 'basic.dart'; import 'binding.dart'; import 'framework.dart'; import 'navigator.dart'; +import 'overlay.dart'; typedef bool DragTargetWillAccept(T data); typedef void DragTargetAccept(T data); @@ -61,11 +62,11 @@ class Draggable extends StatefulComponent { } class _DraggableState extends State { - DragRoute _route; + _DragAvatar _avatar; void _startDrag(PointerInputEvent event) { - if (_route != null) - return; // TODO(ianh): once we switch to using gestures, just hand the gesture to the route so it can do everything itself. then we can have multiple drags at the same time. + if (_avatar != null) + return; // TODO(ianh): once we switch to using gestures, just hand the gesture to the avatar so it can do everything itself. then we can have multiple drags at the same time. final Point point = new Point(event.x, event.y); Point dragStartPoint; switch (config.dragAnchor) { @@ -78,39 +79,38 @@ class _DraggableState extends State { break; } assert(dragStartPoint != null); - _route = new DragRoute( + _avatar = new _DragAvatar( data: config.data, dragStartPoint: dragStartPoint, feedback: config.feedback, feedbackOffset: config.feedbackOffset, onDragFinished: () { - _route = null; + _avatar = null; } ); - _route.update(point); - Navigator.of(context).push(_route); + _avatar.update(point); + _avatar.rebuild(context); } void _updateDrag(PointerInputEvent event) { - if (_route != null) { - Navigator.of(context).setState(() { - _route.update(new Point(event.x, event.y)); - }); + if (_avatar != null) { + _avatar.update(new Point(event.x, event.y)); + _avatar.rebuild(context); } } void _cancelDrag(PointerInputEvent event) { - if (_route != null) { - Navigator.of(context).popRoute(_route, DragEndKind.canceled); - assert(_route == null); + if (_avatar != null) { + _avatar.finish(_DragEndKind.canceled); + assert(_avatar == null); } } void _drop(PointerInputEvent event) { - if (_route != null) { - _route.update(new Point(event.x, event.y)); - Navigator.of(context).popRoute(_route, DragEndKind.dropped); - assert(_route == null); + if (_avatar != null) { + _avatar.update(new Point(event.x, event.y)); + _avatar.finish(_DragEndKind.dropped); + assert(_avatar == null); } } @@ -187,10 +187,10 @@ class DragTargetState extends State> { } -enum DragEndKind { dropped, canceled } +enum _DragEndKind { dropped, canceled } -class DragRoute extends Route { - DragRoute({ +class _DragAvatar { + _DragAvatar({ this.data, this.dragStartPoint: Point.origin, this.feedback, @@ -209,6 +209,7 @@ class DragRoute extends Route { DragTargetState _activeTarget; bool _activeTargetWillAcceptDrop = false; Offset _lastOffset; + OverlayEntry _entry; void update(Point globalPosition) { _lastOffset = globalPosition - dragStartPoint; @@ -222,6 +223,12 @@ class DragRoute extends Route { _activeTargetWillAcceptDrop = _activeTarget != null && _activeTarget.didEnter(data); } + void rebuild(BuildContext context) { + _entry?.remove(); + _entry = new OverlayEntry(child: _build(context)); + Navigator.of(context).overlay.insert(_entry); + } + DragTargetState _getDragTarget(List path) { // TODO(abarth): Why do we reverse the path here? for (HitTestEntry entry in path.reversed) { @@ -234,25 +241,21 @@ class DragRoute extends Route { return null; } - void didPop([DragEndKind endKind]) { + void finish(_DragEndKind endKind) { if (_activeTarget != null) { - if (endKind == DragEndKind.dropped && _activeTargetWillAcceptDrop) + if (endKind == _DragEndKind.dropped && _activeTargetWillAcceptDrop) _activeTarget.didDrop(data); else _activeTarget.didLeave(data); } _activeTarget = null; _activeTargetWillAcceptDrop = false; + _entry.remove(); if (onDragFinished != null) onDragFinished(); - super.didPop(endKind); } - bool get ephemeral => true; - bool get modal => false; - bool get opaque => false; - - Widget build(RouteArguments args) { + Widget _build(BuildContext context) { return new Positioned( left: _lastOffset.dx, top: _lastOffset.dy, diff --git a/packages/flutter/lib/src/widgets/hero_controller.dart b/packages/flutter/lib/src/widgets/hero_controller.dart index 3e0852bbce..3995caa36f 100644 --- a/packages/flutter/lib/src/widgets/hero_controller.dart +++ b/packages/flutter/lib/src/widgets/hero_controller.dart @@ -8,7 +8,7 @@ import 'package:flutter/rendering.dart'; import 'basic.dart'; import 'framework.dart'; import 'heroes.dart'; -import 'navigator2.dart'; +import 'navigator.dart'; import 'overlay.dart'; import 'page.dart'; @@ -69,10 +69,9 @@ class HeroController { } void _addHeroesToOverlay(Iterable heroes, OverlayState overlay) { - OverlayEntry insertionPoint = _to.topEntry; for (Widget hero in heroes) { OverlayEntry entry = new OverlayEntry(child: hero); - overlay.insert(entry, above: insertionPoint); + overlay.insert(entry); _overlayEntries.add(entry); } } diff --git a/packages/flutter/lib/src/widgets/modal_barrier.dart b/packages/flutter/lib/src/widgets/modal_barrier.dart new file mode 100644 index 0000000000..179fd1007b --- /dev/null +++ b/packages/flutter/lib/src/widgets/modal_barrier.dart @@ -0,0 +1,20 @@ +// 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'; +import 'navigator.dart'; + +class ModalBarrier extends StatelessComponent { + ModalBarrier({ Key key }) : super(key: key); + + Widget build(BuildContext context) { + return new Listener( + onPointerDown: (_) { + Navigator.of(context).pop(); + }, + child: new Container() + ); + } +} diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart index bc8df68845..22a2cc38a6 100644 --- a/packages/flutter/lib/src/widgets/navigator.dart +++ b/packages/flutter/lib/src/widgets/navigator.dart @@ -2,50 +2,48 @@ // 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 'package:flutter/rendering.dart'; - -import 'basic.dart'; -import 'focus.dart'; import 'framework.dart'; -import 'heroes.dart'; -import 'transitions.dart'; -import 'gridpaper.dart'; - -/// Set this to true to overlay a pixel grid on the screen, with every 100 -/// pixels marked with a thick maroon line and every 10 pixels marked with a -/// thin maroon line. This can help with verifying widget positions. -bool debugShowGrid = false; -Color debugGridColor = const Color(0x7F7F2020); - -const String kDefaultRouteName = '/'; +import 'overlay.dart'; +// ---------------- Begin scaffolding for Navigator1 to Navigator2 transition class RouteArguments { - const RouteArguments({ this.context, this.previousPerformance, this.nextPerformance }); + const RouteArguments({ this.context }); final BuildContext context; - final PerformanceView previousPerformance; - final PerformanceView nextPerformance; } - typedef Widget RouteBuilder(RouteArguments args); typedef RouteBuilder RouteGenerator(String name); -typedef void StateRouteCallback(StateRoute route); +// ---------------- End scaffolding for Navigator1 to Navigator2 transition + +abstract class Route { + List get overlayEntries; + + void didPush(OverlayState overlay, OverlayEntry insertionPoint); + void didMakeCurrent(); + void didPop(dynamic result); +} + +class NamedRouteSettings { + const NamedRouteSettings({ this.name: '', this.mostValuableKeys }); + + final String name; + final Set mostValuableKeys; +} + +typedef Route RouteFactory(NamedRouteSettings settings); class Navigator extends StatefulComponent { Navigator({ Key key, - this.routes, - this.onGenerateRoute, // you need to implement this if you pushNamed() to names that might not be in routes. - this.onUnknownRoute // 404 generator. You only need to implement this if you have a way to navigate to arbitrary names. + 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(kDefaultRouteName)); + assert(onGenerateRoute != null); } - final Map routes; - final RouteGenerator onGenerateRoute; - final RouteBuilder onUnknownRoute; + final RouteFactory onGenerateRoute; + final RouteFactory onUnknownRoute; + + static const String defaultRouteName = '/'; static NavigatorState of(BuildContext context) { NavigatorState result; @@ -62,656 +60,75 @@ class Navigator extends StatefulComponent { NavigatorState createState() => new NavigatorState(); } -// The navigator tracks which "page" we are on. -// It also animates between these pages. -// Pages can have "heroes", which are UI elements that animate from point to point. -// These animations are called journeys. -// -// Journeys can start in two conditions: -// - Everything is calm, and we have no heroes in flight. In this case, we will -// have to collect the heroes from the route we're starting at and the route -// we're going to, and try to transition from one set to the other. -// - We already have heroes in flight. In that case, we just want to look at -// the heroes of our destination, and then try to transition to them from the -// in-flight heroes. - -class _HeroTransitionInstruction { - Route from; - Route to; - void update(Route newFrom, Route newTo) { - assert(newFrom != null); - assert(newTo != null); - if (!newFrom.canHaveHeroes || !newTo.canHaveHeroes) - return; - assert(newFrom.performance != null); - assert(newTo.performance != null); - if (from == null) - from = newFrom; - to = newTo; - if (from == to) - reset(); - } - void reset() { - assert(hasInstructions); - from = null; - to = null; - } - bool get hasInstructions => from != null || to != null; -} - class NavigatorState extends State { - - List _history = new List(); - int _currentPosition = 0; // which route is "current" - - Route get currentRoute => _history[_currentPosition]; - bool get hasPreviousRoute => _history.length > 1; + final GlobalKey _overlayKey = new GlobalKey(); + final List _ephemeral = new List(); + final List _modal = new List(); void initState() { super.initState(); - _activeHeroes = new HeroParty(onQuestFinished: _handleHeroQuestFinished); - PageRoute route = new PageRoute(config.routes[kDefaultRouteName], name: kDefaultRouteName); - assert(route.hasContent); - assert(!route.ephemeral); - _insertRoute(route); + push(config.onGenerateRoute(new NamedRouteSettings(name: Navigator.defaultRouteName))); } - void pushState(State owner, StateRouteCallback onPop) { - push(new StateRoute( - route: currentRoute, - owner: owner, - onPop: onPop - )); + bool get hasPreviousRoute => _modal.length > 1; + OverlayState get overlay => _overlayKey.currentState; + + OverlayEntry get _currentOverlay { + for (Route route in _ephemeral.reversed) { + if (route.overlayEntries.isNotEmpty) + return route.overlayEntries.last; + } + for (Route route in _modal.reversed) { + if (route.overlayEntries.isNotEmpty) + return route.overlayEntries.last; + } + return null; + } + + Route get currentRoute => _ephemeral.isNotEmpty ? _ephemeral.last : _modal.last; + + Route _removeCurrentRoute() { + return _ephemeral.isNotEmpty ? _ephemeral.removeLast() : _modal.removeLast(); } void pushNamed(String name, { Set mostValuableKeys }) { - RouteBuilder generateRoute() { - assert(config.onGenerateRoute != null); - return config.onGenerateRoute(name); - } - final RouteBuilder builder = config.routes[name] ?? generateRoute() ?? config.onUnknownRoute; - assert(builder != null); // 404 getting your 404! - push(new PageRoute(builder, name: name, mostValuableKeys: mostValuableKeys)); - } - - final _HeroTransitionInstruction _desiredHeroes = new _HeroTransitionInstruction(); - HeroParty _activeHeroes; - - void _handleHeroQuestFinished() { - for (Route route in _history) - route._hasActiveHeroes = false; + NamedRouteSettings settings = new NamedRouteSettings( + name: name, + mostValuableKeys: mostValuableKeys + ); + push(config.onGenerateRoute(settings) ?? config.onUnknownRoute(settings)); } void push(Route route) { - assert(!_debugCurrentlyHaveRoute(route)); - setState(() { - // pop ephemeral routes (like popup menus) - while (currentRoute.ephemeral) { - currentRoute.didPop(null); - _currentPosition -= 1; - } - // find the most recent active route that might have heroes - if (route.hasContent) { - int index = _currentPosition; - while (index > 0 && !_history[index].hasContent) - index -= 1; - assert(_history[index].hasContent); - _desiredHeroes.update(_history[index], route); - } - // add the new route - _currentPosition += 1; - _insertRoute(route); - }); + _popAllEphemeralRoutes(); + route.didPush(overlay, _currentOverlay); + _modal.add(route); + route.didMakeCurrent(); } - void popRoute(Route route, [dynamic result]) { - assert(_debugCurrentlyHaveRoute(route)); - assert(_currentPosition > 0); - setState(() { - // pop any routes above this one (they must be ephemeral, otherwise there's an error) - while (currentRoute != route) { - assert(currentRoute.ephemeral); - currentRoute.didPop(null); - _currentPosition -= 1; - } - }); - pop(result); - assert(!_debugCurrentlyHaveRoute(route)); + void pushEphemeral(Route route) { + route.didPush(overlay, _currentOverlay); + _ephemeral.add(route); + route.didMakeCurrent(); + } + + void _popAllEphemeralRoutes() { + List localEphemeral = new List.from(_ephemeral); + _ephemeral.clear(); + for (Route route in localEphemeral) + route.didPop(null); + assert(_ephemeral.isEmpty); } void pop([dynamic result]) { - setState(() { - assert(_currentPosition > 0); - // find the most recent previous route that might have heroes - if (currentRoute.hasContent) { - int index = _currentPosition - 1; - while (index > 0 && !_history[index].hasContent) - index -= 1; - assert(_history[index].hasContent); - _desiredHeroes.update(currentRoute, _history[index]); - } - // pop the route - currentRoute.didPop(result); - _currentPosition -= 1; - }); + _removeCurrentRoute().didPop(result); + currentRoute.didMakeCurrent(); } - bool _debugCurrentlyHaveRoute(Route route) { - int index = _history.indexOf(route); - return index >= 0 && index <= _currentPosition; - } - - void _didCompleteRoute(Route route) { - assert(_history.contains(route)); - if (route.isActuallyOpaque) { - setState(() { - // we need to rebuild because our build function depends on - // whether the route is opaque or not. - }); - } - } - - void _didDismissRoute(Route route) { - assert(_history.contains(route)); - if (_history.lastIndexOf(route) <= _currentPosition) - popRoute(route); - } - - void _insertRoute(Route route) { - _history.insert(_currentPosition, route); - route.didPush(this); - } - - void _removeRoute(Route route) { - assert(_history.contains(route)); - setState(() { - if (_desiredHeroes.hasInstructions) { - if (_desiredHeroes.from == route || _desiredHeroes.to == route) - _desiredHeroes.reset(); - } - _history.remove(route); - }); - } - - PerformanceView _currentHeroPerformance; - Widget build(BuildContext context) { - List visibleRoutes = []; - - assert(() { - if (debugShowGrid) - visibleRoutes.add(new GridPaper(color: debugGridColor)); - return true; - }); - - - bool alreadyInsertedHeroes = false; - bool alreadyInsertedModalBarrier = false; - Route nextContentRoute; - PerformanceView nextHeroPerformance; - for (int i = _history.length-1; i >= 0; i -= 1) { - final Route route = _history[i]; - if (!route.hasContent) { - assert(!route.modal); - assert(!_desiredHeroes.hasInstructions || (_desiredHeroes.from != route && _desiredHeroes.to != route)); - assert(!route._hasActiveHeroes); - continue; - } - if (route._hasActiveHeroes && !alreadyInsertedHeroes) { - visibleRoutes.addAll(_activeHeroes.getWidgets(context, _currentHeroPerformance)); - alreadyInsertedHeroes = true; - } - if (_desiredHeroes.hasInstructions) { - if ((_desiredHeroes.to == route || _desiredHeroes.from == route) && nextHeroPerformance == null) - nextHeroPerformance = route.performance; - visibleRoutes.add(new _RouteWidget(route: route, nextRoute: nextContentRoute, buildTargetHeroes: _desiredHeroes.to == route)); - } else { - visibleRoutes.add(new _RouteWidget(route: route, nextRoute: nextContentRoute)); - } - if (route.isActuallyOpaque) { - assert(!_desiredHeroes.hasInstructions || - (_history.indexOf(_desiredHeroes.from) >= i && _history.indexOf(_desiredHeroes.to) >= i)); - break; - } - assert(route.modal || route.ephemeral); - if (route.modal && i > 0 && !alreadyInsertedModalBarrier) { - visibleRoutes.add(new Listener( - onPointerDown: (_) { pop(); }, - child: new Container() - )); - alreadyInsertedModalBarrier = true; - } - nextContentRoute = route; - } - - if (_desiredHeroes.hasInstructions) { - assert(nextHeroPerformance != null); - scheduler.requestPostFrameCallback((Duration timestamp) { - Map heroesFrom; - Map heroesTo; - Set mostValuableKeys = new Set(); - if (_desiredHeroes.from.mostValuableKeys != null) - mostValuableKeys.addAll(_desiredHeroes.from.mostValuableKeys); - if (_desiredHeroes.to.mostValuableKeys != null) - mostValuableKeys.addAll(_desiredHeroes.to.mostValuableKeys); - if (_activeHeroes.isEmpty) { - assert(!_desiredHeroes.from._hasActiveHeroes); - heroesFrom = _desiredHeroes.from.getHeroesToAnimate(mostValuableKeys); - _desiredHeroes.from._hasActiveHeroes = heroesFrom.length > 0; - } else { - assert(_desiredHeroes.from._hasActiveHeroes); - heroesFrom = _activeHeroes.getHeroesToAnimate(); - } - heroesTo = _desiredHeroes.to.getHeroesToAnimate(mostValuableKeys); - _desiredHeroes.to._hasActiveHeroes = heroesTo.length > 0; - _desiredHeroes.reset(); - setState(() { - final RenderBox renderObject = context.findRenderObject(); - final Point animationTopLeft = renderObject.localToGlobal(Point.origin); - final Point animationBottomRight = renderObject.localToGlobal(renderObject.size.bottomRight(Point.origin)); - final Rect animationArea = new Rect.fromLTRB(animationTopLeft.x, animationTopLeft.y, animationBottomRight.x, animationBottomRight.y); - Curve curve = Curves.ease; - if (nextHeroPerformance.status == PerformanceStatus.reverse) { - nextHeroPerformance = new ReversePerformance(nextHeroPerformance); - curve = new Interval(nextHeroPerformance.progress, 1.0, curve: curve); - } - _activeHeroes.animate(heroesFrom, heroesTo, animationArea, curve); - _currentHeroPerformance = nextHeroPerformance; - }); - }); - } - - return new Focus(child: new Stack(visibleRoutes.reversed.toList())); - } - -} - -class _RouteWidget extends StatefulComponent { - _RouteWidget({ - Route route, - this.nextRoute, - this.buildTargetHeroes: false - }) : route = route, - super(key: new ObjectKey(route)) { - assert(route != null); - } - final Route route; - final Route nextRoute; - final bool buildTargetHeroes; - _RouteWidgetState createState() => new _RouteWidgetState(); - void debugFillDescription(List description) { - super.debugFillDescription(description); - if (route.performance != null) - description.add('${route.performance}'); - else - description.add('${route.debugLabel}'); - if (buildTargetHeroes) - description.add('building target heroes this frame'); - } -} - -class _RouteWidgetState extends State<_RouteWidget> { - void initState() { - super.initState(); - config.route._widgetState = this; - } - void dispose() { - config.route._widgetState = null; - super.dispose(); - } - Widget build(BuildContext context) { - return config.route._internalBuild(context, config.nextRoute, buildTargetHeroes: config.buildTargetHeroes); - } -} - -class _StorageEntryIdentifier { - Type clientType; - List keys; - void addKey(Key key) { - assert(key != null); - assert(key is! GlobalKey); - keys ??= []; - keys.add(key); - } - GlobalKey scopeKey; - bool operator ==(dynamic other) { - if (other is! _StorageEntryIdentifier) - return false; - final _StorageEntryIdentifier typedOther = other; - if (clientType != typedOther.clientType || - scopeKey != typedOther.scopeKey || - keys?.length != typedOther.keys?.length) - return false; - if (keys != null) { - for (int index = 0; index < keys.length; index += 1) { - if (keys[index] != typedOther.keys[index]) - return false; - } - } - return true; - } - int get hashCode { - int value = 373; - value = 37 * value + clientType.hashCode; - value = 37 * value + scopeKey.hashCode; - if (keys != null) { - for (Key key in keys) - value = 37 * value + key.hashCode; - } - return value; - } -} - -abstract class Route { - Route() { - _subtreeKey = new GlobalKey(label: debugLabel); - } - - /// If hasContent is true, then the route represents some on-screen state. - /// - /// If hasContent is false, then no performance will be created, and the values of - /// ephemeral, modal, and opaque are ignored. This is useful if the route - /// represents some state handled by another widget. See - /// NavigatorState.pushState(). - /// - /// Set hasContent to false if you have nothing useful to return from build(). - /// - /// modal must be false if hasContent is false, since otherwise any - /// interaction with the system at all would imply that the current route is - /// popped, which would be pointless. - bool get hasContent => true; - - /// If ephemeral is true, then to explicitly pop the route you have to use - /// navigator.popRoute() with a reference to this route. navigator.pop() - /// automatically pops all ephemeral routes before popping the current - /// top-most non-ephemeral route. - /// - /// If ephemeral is false, then the route can be popped with navigator.pop(). - /// - /// Set ephemeral to true if you want to be automatically popped when another - /// route is pushed or popped. - /// - /// modal must be true if ephemeral is false. - bool get ephemeral => false; - - /// If modal is true, a hidden layer is inserted in the widget tree that - /// catches all touches to widgets created by routes below this one, even if - /// this one is transparent. - /// - /// If modal is false, then earlier routes can be interacted with, including - /// causing new routes to be pushed and/or this route (and maybe others) to be - /// popped. - /// - /// ephemeral must be true if modal is false. - /// hasContent must be true if modal is true. - bool get modal => true; - - /// If opaque is true, then routes below this one will not be built or painted - /// when the transition to this route is complete. - /// - /// If opaque is false, then the previous route will always be painted even if - /// this route's transition is complete. - /// - /// Set this to true if there's no reason to build and paint the route behind - /// you when your transition is finished, and set it to false if you do not - /// cover the entire application surface or are in any way semi-transparent. - bool get opaque => false; - - PerformanceView get performance => null; - bool get isActuallyOpaque => (performance == null || performance.isCompleted) && opaque; - - NavigatorState get navigator => _navigator; - NavigatorState _navigator; - _RouteWidgetState _widgetState; - - void setState(void fn()) { - if (_widgetState != null) - _widgetState.setState(fn); - else - fn(); - } - - void didPush(NavigatorState navigator) { - assert(_navigator == null); - _navigator = navigator; - assert(_navigator != null); - performance?.addStatusListener(_handlePerformanceStatusChanged); - } - - void didPop([dynamic result]) { - assert(navigator != null); - if (performance == null) - navigator._removeRoute(this); - } - - void _handlePerformanceStatusChanged(PerformanceStatus status) { - if (status == PerformanceStatus.completed) { - navigator._didCompleteRoute(this); - } else if (status == PerformanceStatus.dismissed) { - navigator._didDismissRoute(this); - navigator._removeRoute(this); - _navigator = null; - } - } - - /// Called (indirectly, via a RouteWidget) by the navigator.build() - /// function if hasContent is true, to get the subtree for this - /// route. - /// - /// If buildTargetHeroes is true, then getHeroesToAnimate() will be called - /// after this build, before the next build, and this build should render the - /// route off-screen, at the end of its animation. Next frame, the argument - /// will be false, and the tree should be built at the first frame of the - /// transition animation, whatever that is. - Widget _internalBuild(BuildContext context, Route nextRoute, { bool buildTargetHeroes: false }) { - assert(navigator != null); - assert(_widgetState != null); - assert(hasContent); - return keySubtree(build(new RouteArguments( - context: context, - previousPerformance: performance, - nextPerformance: nextRoute?.performance - ))); - } - - bool get canHaveHeroes => hasContent && modal && opaque; - Set get mostValuableKeys => null; - - /// Return a party of heroes (one per tag) to animate. This is called by the - /// navigator when hasContent is true just after this route, the previous - /// route, or the next route, has been pushed or popped, to figure out which - /// heroes it should be trying to animate. - Map getHeroesToAnimate([Set mostValuableKeys]) => const {}; - bool _hasActiveHeroes = false; - - GlobalKey _subtreeKey; - - /// Returns the BuildContext for the root of the subtree built for this route, - /// assuming that internalBuild used keySubtree to build that subtree. - /// This is only valid after a build phase. - BuildContext get subtreeContext => _subtreeKey.currentContext; - - /// Wraps the given subtree in a route-specific GlobalKey. - Widget keySubtree(Widget child) { - return new KeyedSubtree( - key: _subtreeKey, - child: child + return new Overlay( + key: _overlayKey, + initialEntries: _modal.first.overlayEntries ); } - - /// Called by internalBuild. This is the method to override if you want to - /// change what subtree is built for this route. - Widget build(RouteArguments args); - - static Route of(BuildContext context) { - Route result; - context.visitAncestorElements((Element element) { - if (element is StatefulComponentElement && element.state is _RouteWidgetState) { - result = element.widget.route; - return false; - } - return true; - }); - return result; - } - - _StorageEntryIdentifier _computeStorageIdentifier(BuildContext context) { - _StorageEntryIdentifier result = new _StorageEntryIdentifier(); - result.clientType = context.widget.runtimeType; - Key lastKey = context.widget.key; - if (lastKey is! GlobalKey) { - context.visitAncestorElements((Element element) { - if (element.widget.key is GlobalKey) { - lastKey = element.widget.key; - return false; - } else if (element.widget is Navigator) { - // Not quite everyone who is in a Navigator actually is in a Route. - // For example, the modal barrier. - StatefulComponentElement statefulElement = element; - lastKey = new GlobalObjectKey(statefulElement.state); - return false; - } else if (element.widget.key != null) { - result.addKey(element.widget.key); - } - return true; - }); - return result; - } - assert(lastKey is GlobalKey); - result.scopeKey = lastKey; - return result; - } - - Map<_StorageEntryIdentifier, dynamic> _storage; - void writeState(BuildContext context, dynamic data) { - _storage ??= <_StorageEntryIdentifier, dynamic>{}; - _storage[_computeStorageIdentifier(context)] = data; - } - dynamic readState(BuildContext context) => _storage != null ? _storage[_computeStorageIdentifier(context)] : null; - - String get debugLabel => '$runtimeType'; - - String toString() => '$runtimeType(performance: $performance; key: $_subtreeKey)'; -} - - -abstract class PerformanceRoute extends Route { - PerformanceRoute() { - _performance = createPerformance(); - } - - PerformanceView get performance => _performance?.view; - Performance _performance; - - Performance createPerformance() { - Duration duration = transitionDuration; - assert(duration != null && duration >= Duration.ZERO); - return new Performance(duration: duration, debugLabel: debugLabel); - } - - Duration get transitionDuration; - - Widget _internalBuild(BuildContext context, Route nextRoute, { bool buildTargetHeroes: false }) { - assert(hasContent); - assert(transitionDuration > Duration.ZERO); - if (buildTargetHeroes && performance.progress != 1.0) { - Performance fakePerformance = createPerformance(); - assert(fakePerformance != null); - fakePerformance.progress = 1.0; - return new OffStage( - child: keySubtree( - build(new RouteArguments(context: context, previousPerformance: fakePerformance)) - ) - ); - } - return super._internalBuild(context, nextRoute, buildTargetHeroes: buildTargetHeroes); - } - - void didPush(NavigatorState navigator) { - super.didPush(navigator); - _performance?.forward(); - } - - void didPop([dynamic result]) { - _performance?.reverse(); - super.didPop(result); - } -} - -const Duration _kTransitionDuration = const Duration(milliseconds: 150); -const Point _kTransitionStartPoint = const Point(0.0, 75.0); - -/// A route that represents a page in an application. -/// -/// PageRoutes try to animate between themselves in a fashion that is aware of -/// any Heroes. -class PageRoute extends PerformanceRoute { - PageRoute(this._builder, { - this.name: '', - Set mostValuableKeys - }) : _mostValuableKeys = mostValuableKeys { - assert(_builder != null); - } - - final RouteBuilder _builder; - final String name; - final Set _mostValuableKeys; - - Set get mostValuableKeys => _mostValuableKeys; - - bool get opaque => true; - Duration get transitionDuration => _kTransitionDuration; - - Map getHeroesToAnimate([Set mostValuableKeys]) { - return Hero.of(subtreeContext, mostValuableKeys); - } - - Widget build(RouteArguments args) { - // TODO(jackson): Hit testing should ignore transform - // TODO(jackson): Block input unless content is interactive - // TODO(ianh): Support having different transitions, e.g. when heroes are around. - return new SlideTransition( - performance: args.previousPerformance, - position: new AnimatedValue(_kTransitionStartPoint, end: Point.origin, curve: Curves.easeOut), - child: new FadeTransition( - performance: args.previousPerformance, - opacity: new AnimatedValue(0.0, end: 1.0, curve: Curves.easeOut), - child: invokeBuilder(args) - ) - ); - } - - Widget invokeBuilder(RouteArguments args) { - Widget result = _builder(args); - assert(() { - if (result == null) - debugPrint('The builder for route \'$name\' returned null. RouteBuilders must never return null.'); - assert(result != null && 'A RouteBuilder returned null. See the previous log message for details.' is String); - return true; - }); - return result; - } - - String get debugLabel => '${super.debugLabel}($name)'; -} - -class StateRoute extends Route { - StateRoute({ this.route, this.owner, this.onPop }); - - Route route; - State owner; - StateRouteCallback onPop; - - bool get hasContent => false; - bool get modal => false; - bool get opaque => false; - - void didPop([dynamic result]) { - assert(result == null); - if (onPop != null) - onPop(this); - super.didPop(result); - } - - Widget build(RouteArguments args) => null; } diff --git a/packages/flutter/lib/src/widgets/navigator2.dart b/packages/flutter/lib/src/widgets/navigator2.dart deleted file mode 100644 index 354bb95cb5..0000000000 --- a/packages/flutter/lib/src/widgets/navigator2.dart +++ /dev/null @@ -1,142 +0,0 @@ -// 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 'framework.dart'; -import 'overlay.dart'; - -abstract class Route { - List createWidgets() => const []; - - OverlayEntry get topEntry => _entries.isNotEmpty ? _entries.last : null; - OverlayEntry get bottomEntry => _entries.isNotEmpty ? _entries.first : null; - - final List _entries = new List(); - - void didPush(OverlayState overlay, OverlayEntry insertionPoint) { - List widgets = createWidgets(); - for (Widget widget in widgets) { - _entries.add(new OverlayEntry(child: widget)); - overlay?.insert(_entries.last, above: insertionPoint); - insertionPoint = _entries.last; - } - } - - void didMakeCurrent() { } - - void didPop(dynamic result) { - for (OverlayEntry entry in _entries) - entry.remove(); - } -} - -class NamedRouteSettings { - const NamedRouteSettings({ this.name: '', this.mostValuableKeys }); - - final String name; - final Set mostValuableKeys; -} - -typedef Route RouteFactory(NamedRouteSettings settings); - -class Navigator extends StatefulComponent { - Navigator({ - Key key, - this.onGenerateRoute, - this.onUnknownRoute - }) : super(key: key) { - assert(onGenerateRoute != null); - } - - final RouteFactory onGenerateRoute; - final RouteFactory onUnknownRoute; - - static const String defaultRouteName = '/'; - - 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 { - final GlobalKey _overlayKey = new GlobalKey(); - final List _ephemeral = new List(); - final List _modal = new List(); - - void initState() { - super.initState(); - push(config.onGenerateRoute(new NamedRouteSettings(name: Navigator.defaultRouteName))); - } - - bool get hasPreviousRoute => _modal.length > 1; - OverlayState get overlay => _overlayKey.currentState; - - OverlayEntry get _currentOverlay { - 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; - } - - Route get _currentRoute => _ephemeral.isNotEmpty ? _ephemeral.last : _modal.last; - - Route _removeCurrentRoute() { - return _ephemeral.isNotEmpty ? _ephemeral.removeLast() : _modal.removeLast(); - } - - void pushNamed(String name, { Set mostValuableKeys }) { - NamedRouteSettings settings = new NamedRouteSettings( - name: name, - mostValuableKeys: mostValuableKeys - ); - push(config.onGenerateRoute(settings) ?? config.onUnknownRoute(settings)); - } - - void push(Route route) { - _popAllEphemeralRoutes(); - route.didPush(overlay, _currentOverlay); - _modal.add(route); - route.didMakeCurrent(); - } - - void pushEphemeral(Route route) { - route.didPush(overlay, _currentOverlay); - _ephemeral.add(route); - route.didMakeCurrent(); - } - - void _popAllEphemeralRoutes() { - List localEphemeral = new List.from(_ephemeral); - _ephemeral.clear(); - for (Route route in localEphemeral) - route.didPop(null); - assert(_ephemeral.isEmpty); - } - - void pop([dynamic result]) { - _removeCurrentRoute().didPop(result); - _currentRoute.didMakeCurrent(); - } - - Widget build(BuildContext context) { - return new Overlay( - key: _overlayKey, - initialEntries: _modal.first._entries - ); - } -} diff --git a/packages/flutter/lib/src/widgets/overlay.dart b/packages/flutter/lib/src/widgets/overlay.dart index fc289fbb39..4741ea6f2d 100644 --- a/packages/flutter/lib/src/widgets/overlay.dart +++ b/packages/flutter/lib/src/widgets/overlay.dart @@ -16,7 +16,7 @@ class OverlayEntry { bool get opaque => _opaque; bool _opaque; void set opaque(bool value) { - if (_opaque = value) + if (_opaque == value) return; _opaque = value; _state?.setState(() {}); diff --git a/packages/flutter/lib/src/widgets/page.dart b/packages/flutter/lib/src/widgets/page.dart index 874e3974bc..0a7e571a45 100644 --- a/packages/flutter/lib/src/widgets/page.dart +++ b/packages/flutter/lib/src/widgets/page.dart @@ -6,59 +6,11 @@ import 'package:flutter/animation.dart'; import 'basic.dart'; import 'framework.dart'; -import 'navigator2.dart'; -import 'overlay.dart'; +import 'navigator.dart'; import 'page_storage.dart'; +import 'routes.dart'; import 'transitions.dart'; -// TODO(abarth): Should we add a type for the result? -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) { - switch (status) { - case PerformanceStatus.completed: - bottomEntry.opaque = opaque; - break; - case PerformanceStatus.forward: - case PerformanceStatus.reverse: - bottomEntry.opaque = false; - break; - case PerformanceStatus.dismissed: - super.didPop(_result); - break; - } - } - - void didPush(OverlayState overlay, OverlayEntry insertionPoint) { - _performance = createPerformance() - ..addStatusListener(_handleStatusChanged) - ..forward(); - super.didPush(overlay, insertionPoint); - } - - void didPop(dynamic result) { - _result = result; - _performance.reverse(); - } - - String get debugLabel => '$runtimeType'; - String toString() => '$runtimeType(performance: $_performance)'; -} - class _Page extends StatefulComponent { _Page({ Key key, @@ -130,6 +82,8 @@ class PageRoute extends TransitionRoute { final GlobalKey<_PageState> pageKey = new GlobalKey<_PageState>(); + bool get opaque => true; + String get name => settings.name; Duration get transitionDuration => const Duration(milliseconds: 150); diff --git a/packages/flutter/lib/src/widgets/routes.dart b/packages/flutter/lib/src/widgets/routes.dart new file mode 100644 index 0000000000..41a4f56541 --- /dev/null +++ b/packages/flutter/lib/src/widgets/routes.dart @@ -0,0 +1,98 @@ +// 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 'navigator.dart'; +import 'overlay.dart'; + +class StateRoute extends Route { + StateRoute({ this.onPop }); + + final VoidCallback onPop; + + List get overlayEntries => const []; + + void didPush(OverlayState overlay, OverlayEntry insertionPoint) { } + void didMakeCurrent() { } + void didPop(dynamic result) { + if (onPop != null) + onPop(); + } +} + +class OverlayRoute extends Route { + List createWidgets() => const []; + + List get overlayEntries => _overlayEntries; + final List _overlayEntries = new List(); + + void didPush(OverlayState overlay, OverlayEntry insertionPoint) { + List widgets = createWidgets(); + for (Widget widget in widgets) { + _overlayEntries.add(new OverlayEntry(child: widget)); + overlay?.insert(_overlayEntries.last, above: insertionPoint); + insertionPoint = _overlayEntries.last; + } + } + + void didMakeCurrent() { } + + void didPop(dynamic result) { + for (OverlayEntry entry in _overlayEntries) + entry.remove(); + _overlayEntries.clear(); + } +} + +// TODO(abarth): Should we add a type for the result? +abstract class TransitionRoute extends OverlayRoute { + Duration get transitionDuration; + bool get opaque; + + PerformanceView get performance => _performance?.view; + Performance _performance; + + Performance createPerformance() { + Duration duration = transitionDuration; + assert(duration != null && duration >= Duration.ZERO); + return new Performance(duration: duration, debugLabel: debugLabel); + } + + dynamic _result; + + void _handleStatusChanged(PerformanceStatus status) { + switch (status) { + case PerformanceStatus.completed: + if (overlayEntries.isNotEmpty) + overlayEntries.first.opaque = opaque; + break; + case PerformanceStatus.forward: + case PerformanceStatus.reverse: + if (overlayEntries.isNotEmpty) + overlayEntries.first.opaque = false; + break; + case PerformanceStatus.dismissed: + super.didPop(_result); + break; + } + } + + void didPush(OverlayState overlay, OverlayEntry insertionPoint) { + _performance = createPerformance() + ..addStatusListener(_handleStatusChanged) + ..forward(); + super.didPush(overlay, insertionPoint); + } + + void didPop(dynamic result) { + _result = result; + _performance.reverse(); + } + + String get debugLabel => '$runtimeType'; + String toString() => '$runtimeType(performance: $_performance)'; +} diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index 508a5633b4..f22443eaa6 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -16,7 +16,7 @@ import 'framework.dart'; import 'gesture_detector.dart'; import 'homogeneous_viewport.dart'; import 'mixed_viewport.dart'; -import 'navigator.dart'; +import 'page_storage.dart'; // The gesture velocity properties are pixels/second, config min,max limits are pixels/ms const double _kMillisecondsPerSecond = 1000.0; @@ -56,7 +56,7 @@ abstract class ScrollableState extends State { void initState() { super.initState(); _animation = new SimulationStepper(_setScrollOffset); - _scrollOffset = Route.of(context)?.readState(context) ?? config.initialScrollOffset ?? 0.0; + _scrollOffset = PageStorage.of(context)?.readState(context) ?? config.initialScrollOffset ?? 0.0; } SimulationStepper _animation; @@ -178,7 +178,7 @@ abstract class ScrollableState extends State { setState(() { _scrollOffset = newScrollOffset; }); - Route.of(context)?.writeState(context, _scrollOffset); + PageStorage.of(context)?.writeState(context, _scrollOffset); dispatchOnScroll(); } diff --git a/packages/flutter/lib/widgets.dart b/packages/flutter/lib/widgets.dart index 208acb4749..aeea231fca 100644 --- a/packages/flutter/lib/widgets.dart +++ b/packages/flutter/lib/widgets.dart @@ -16,13 +16,18 @@ export 'src/widgets/focus.dart'; export 'src/widgets/framework.dart'; export 'src/widgets/gesture_detector.dart'; export 'src/widgets/gridpaper.dart'; +export 'src/widgets/hero_controller.dart'; export 'src/widgets/heroes.dart'; export 'src/widgets/homogeneous_viewport.dart'; export 'src/widgets/media_query.dart'; export 'src/widgets/mimic.dart'; export 'src/widgets/mixed_viewport.dart'; +export 'src/widgets/modal_barrier.dart'; export 'src/widgets/navigator.dart'; +export 'src/widgets/page_storage.dart'; +export 'src/widgets/page.dart'; export 'src/widgets/placeholder.dart'; +export 'src/widgets/routes.dart'; export 'src/widgets/scrollable.dart'; export 'src/widgets/statistics_overlay.dart'; export 'src/widgets/transitions.dart'; diff --git a/packages/unit/test/widget/draggable_test.dart b/packages/unit/test/widget/draggable_test.dart index 003271d883..38d8994579 100644 --- a/packages/unit/test/widget/draggable_test.dart +++ b/packages/unit/test/widget/draggable_test.dart @@ -1,4 +1,4 @@ -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; import 'package:test/test.dart'; import '../engine/mock_events.dart'; @@ -11,7 +11,7 @@ void main() { List accepted = []; - tester.pumpWidget(new Navigator( + tester.pumpWidget(new MaterialApp( routes: { '/': (RouteArguments args) { return new Column([ new Draggable( diff --git a/packages/unit/test/widget/navigator_test.dart b/packages/unit/test/widget/navigator_test.dart index 17649e022b..88dd13bdcf 100644 --- a/packages/unit/test/widget/navigator_test.dart +++ b/packages/unit/test/widget/navigator_test.dart @@ -1,4 +1,4 @@ -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; import 'package:test/test.dart'; import 'widget_tester.dart'; @@ -45,7 +45,7 @@ void main() { '/second': (RouteArguments args) => new SecondComponent(), }; - tester.pumpWidget(new Navigator(routes: routes)); + tester.pumpWidget(new MaterialApp(routes: routes)); expect(tester.findText('X'), isNotNull); expect(tester.findText('Y'), isNull); diff --git a/packages/unit/test/widget/remember_scroll_position_test.dart b/packages/unit/test/widget/remember_scroll_position_test.dart index 7645244514..904ba7123e 100644 --- a/packages/unit/test/widget/remember_scroll_position_test.dart +++ b/packages/unit/test/widget/remember_scroll_position_test.dart @@ -34,9 +34,12 @@ void main() { GlobalKey navigatorKey = new GlobalKey(); tester.pumpWidget(new Navigator( key: navigatorKey, - routes: { - '/': (RouteArguments args) => new Container(child: new ThePositiveNumbers()), - '/second': (RouteArguments args) => new Container(child: new ThePositiveNumbers()), + onGenerateRoute: (NamedRouteSettings settings) { + if (settings.name == '/') + return new PageRoute(builder: (_) => new Container(child: new ThePositiveNumbers())); + else if (settings.name == '/second') + return new PageRoute(builder: (_) => new Container(child: new ThePositiveNumbers())); + return null; } )); @@ -105,7 +108,7 @@ void main() { expect(tester.findText('15'), isNotNull); expect(tester.findText('16'), isNull); expect(tester.findText('100'), isNull); - + }); }); }