Commit 8a900f90 authored by Ian Hickson's avatar Ian Hickson Committed by Hixie

Track scroll position

- Change RouteArguments to pass the route's BuildContext rather than
  the Navigator. This caused the bulk of the examples/ and .../test/
  changes (those are mostly mechanical changes). It also meant I could
  simplify Navigator.of().

- Make initState() actually get called when the State's Element is in
  the tree, so you can use Foo.of() functions there. Added a test for
  this also.

- Provide a RouteWidget so that routes have a position in the Widget
  tree. The bulk of the route logic is still in a longer-lived Route
  object for now.

- Make Route.setState() only rebuild the actual route, not the whole
  navigator.

- Provided a Route.of().

- Provided a Route.writeState / Route.readState API that tries to
  identify the clients by their runtimeType, their key, and their
  ancestors keys, up to the nearest ancestor with a GlobalKey.

- Made scrollables hook into this API to track state. Added a test to
  make sure this works.

- Fix the debug output of GestureDetector and the hashCode of
  MixedViewport.

- Fixed ScrollableWidgetListState<T> to handle infinite lists.
parent e77cad81
...@@ -44,9 +44,8 @@ class DialogMenuItem extends StatelessComponent { ...@@ -44,9 +44,8 @@ class DialogMenuItem extends StatelessComponent {
} }
class FeedFragment extends StatefulComponent { class FeedFragment extends StatefulComponent {
FeedFragment({ this.navigator, this.userData, this.onItemCreated, this.onItemDeleted }); FeedFragment({ this.userData, this.onItemCreated, this.onItemDeleted });
final NavigatorState navigator;
final UserData userData; final UserData userData;
final FitnessItemHandler onItemCreated; final FitnessItemHandler onItemCreated;
final FitnessItemHandler onItemDeleted; final FitnessItemHandler onItemDeleted;
...@@ -62,7 +61,7 @@ class FeedFragmentState extends State<FeedFragment> { ...@@ -62,7 +61,7 @@ class FeedFragmentState extends State<FeedFragment> {
setState(() { setState(() {
_fitnessMode = value; _fitnessMode = value;
}); });
config.navigator.pop(); Navigator.of(context).pop();
} }
void _showDrawer() { void _showDrawer() {
...@@ -93,8 +92,8 @@ class FeedFragmentState extends State<FeedFragment> { ...@@ -93,8 +92,8 @@ class FeedFragmentState extends State<FeedFragment> {
} }
void _handleShowSettings() { void _handleShowSettings() {
config.navigator.pop(); Navigator.of(context)..pop()
config.navigator.pushNamed('/settings'); ..pushNamed('/settings');
} }
// TODO(jackson): We should be localizing // TODO(jackson): We should be localizing
...@@ -122,7 +121,7 @@ class FeedFragmentState extends State<FeedFragment> { ...@@ -122,7 +121,7 @@ class FeedFragmentState extends State<FeedFragment> {
content: new Text("Item deleted."), content: new Text("Item deleted."),
actions: <SnackBarAction>[new SnackBarAction(label: "UNDO", onPressed: () { actions: <SnackBarAction>[new SnackBarAction(label: "UNDO", onPressed: () {
config.onItemCreated(item); config.onItemCreated(item);
config.navigator.pop(); Navigator.of(context).pop();
})] })]
); );
} }
...@@ -193,7 +192,7 @@ class FeedFragmentState extends State<FeedFragment> { ...@@ -193,7 +192,7 @@ class FeedFragmentState extends State<FeedFragment> {
void _handleActionButtonPressed() { void _handleActionButtonPressed() {
showDialog(context: context, child: new AddItemDialog()).then((routeName) { showDialog(context: context, child: new AddItemDialog()).then((routeName) {
if (routeName != null) if (routeName != null)
config.navigator.pushNamed(routeName); Navigator.of(context).pushNamed(routeName);
}); });
} }
......
...@@ -135,7 +135,6 @@ class FitnessAppState extends State<FitnessApp> { ...@@ -135,7 +135,6 @@ class FitnessAppState extends State<FitnessApp> {
routes: <String, RouteBuilder>{ routes: <String, RouteBuilder>{
'/': (RouteArguments args) { '/': (RouteArguments args) {
return new FeedFragment( return new FeedFragment(
navigator: args.navigator,
userData: _userData, userData: _userData,
onItemCreated: _handleItemCreated, onItemCreated: _handleItemCreated,
onItemDeleted: _handleItemDeleted onItemDeleted: _handleItemDeleted
...@@ -143,19 +142,16 @@ class FitnessAppState extends State<FitnessApp> { ...@@ -143,19 +142,16 @@ class FitnessAppState extends State<FitnessApp> {
}, },
'/meals/new': (RouteArguments args) { '/meals/new': (RouteArguments args) {
return new MealFragment( return new MealFragment(
navigator: args.navigator,
onCreated: _handleItemCreated onCreated: _handleItemCreated
); );
}, },
'/measurements/new': (RouteArguments args) { '/measurements/new': (RouteArguments args) {
return new MeasurementFragment( return new MeasurementFragment(
navigator: args.navigator,
onCreated: _handleItemCreated onCreated: _handleItemCreated
); );
}, },
'/settings': (RouteArguments args) { '/settings': (RouteArguments args) {
return new SettingsFragment( return new SettingsFragment(
navigator: args.navigator,
userData: _userData, userData: _userData,
updater: settingsUpdater updater: settingsUpdater
); );
......
...@@ -43,9 +43,8 @@ class MealRow extends FitnessItemRow { ...@@ -43,9 +43,8 @@ class MealRow extends FitnessItemRow {
} }
class MealFragment extends StatefulComponent { class MealFragment extends StatefulComponent {
MealFragment({ this.navigator, this.onCreated }); MealFragment({ this.onCreated });
NavigatorState navigator;
FitnessItemHandler onCreated; FitnessItemHandler onCreated;
MealFragmentState createState() => new MealFragmentState(); MealFragmentState createState() => new MealFragmentState();
...@@ -56,14 +55,14 @@ class MealFragmentState extends State<MealFragment> { ...@@ -56,14 +55,14 @@ class MealFragmentState extends State<MealFragment> {
void _handleSave() { void _handleSave() {
config.onCreated(new Meal(when: new DateTime.now(), description: _description)); config.onCreated(new Meal(when: new DateTime.now(), description: _description));
config.navigator.pop(); Navigator.of(context).pop();
} }
Widget buildToolBar() { Widget buildToolBar() {
return new ToolBar( return new ToolBar(
left: new IconButton( left: new IconButton(
icon: "navigation/close", icon: "navigation/close",
onPressed: config.navigator.pop), onPressed: Navigator.of(context).pop),
center: new Text('New Meal'), center: new Text('New Meal'),
right: <Widget>[ right: <Widget>[
// TODO(abarth): Should this be a FlatButton? // TODO(abarth): Should this be a FlatButton?
......
...@@ -104,9 +104,8 @@ class MeasurementDateDialogState extends State<MeasurementDateDialog> { ...@@ -104,9 +104,8 @@ class MeasurementDateDialogState extends State<MeasurementDateDialog> {
} }
class MeasurementFragment extends StatefulComponent { class MeasurementFragment extends StatefulComponent {
MeasurementFragment({ this.navigator, this.onCreated }); MeasurementFragment({ this.onCreated });
final NavigatorState navigator;
final FitnessItemHandler onCreated; final FitnessItemHandler onCreated;
MeasurementFragmentState createState() => new MeasurementFragmentState(); MeasurementFragmentState createState() => new MeasurementFragmentState();
...@@ -131,14 +130,14 @@ class MeasurementFragmentState extends State<MeasurementFragment> { ...@@ -131,14 +130,14 @@ class MeasurementFragmentState extends State<MeasurementFragment> {
); );
} }
config.onCreated(new Measurement(when: _when, weight: parsedWeight)); config.onCreated(new Measurement(when: _when, weight: parsedWeight));
config.navigator.pop(); Navigator.of(context).pop();
} }
Widget buildToolBar() { Widget buildToolBar() {
return new ToolBar( return new ToolBar(
left: new IconButton( left: new IconButton(
icon: "navigation/close", icon: "navigation/close",
onPressed: config.navigator.pop), onPressed: Navigator.of(context).pop),
center: new Text('New Measurement'), center: new Text('New Measurement'),
right: <Widget>[ right: <Widget>[
// TODO(abarth): Should this be a FlatButton? // TODO(abarth): Should this be a FlatButton?
......
...@@ -10,9 +10,8 @@ typedef void SettingsUpdater({ ...@@ -10,9 +10,8 @@ typedef void SettingsUpdater({
}); });
class SettingsFragment extends StatefulComponent { class SettingsFragment extends StatefulComponent {
SettingsFragment({ this.navigator, this.userData, this.updater }); SettingsFragment({ this.userData, this.updater });
final NavigatorState navigator;
final UserData userData; final UserData userData;
final SettingsUpdater updater; final SettingsUpdater updater;
...@@ -29,7 +28,7 @@ class SettingsFragmentState extends State<SettingsFragment> { ...@@ -29,7 +28,7 @@ class SettingsFragmentState extends State<SettingsFragment> {
return new ToolBar( return new ToolBar(
left: new IconButton( left: new IconButton(
icon: "navigation/arrow_back", icon: "navigation/arrow_back",
onPressed: config.navigator.pop onPressed: () => Navigator.of(context).pop()
), ),
center: new Text('Settings') center: new Text('Settings')
); );
...@@ -48,7 +47,7 @@ class SettingsFragmentState extends State<SettingsFragment> { ...@@ -48,7 +47,7 @@ class SettingsFragmentState extends State<SettingsFragment> {
void _handleGoalWeightChanged(String goalWeight) { void _handleGoalWeightChanged(String goalWeight) {
// TODO(jackson): Looking for null characters to detect enter key is a hack // TODO(jackson): Looking for null characters to detect enter key is a hack
if (goalWeight.endsWith("\u{0}")) { if (goalWeight.endsWith("\u{0}")) {
config.navigator.pop(double.parse(goalWeight.replaceAll("\u{0}", ""))); Navigator.of(context).pop(double.parse(goalWeight.replaceAll("\u{0}", "")));
} else { } else {
setState(() { setState(() {
try { try {
......
...@@ -108,10 +108,10 @@ class GameDemoState extends State<GameDemo> { ...@@ -108,10 +108,10 @@ class GameDemoState extends State<GameDemo> {
_sounds, _sounds,
(int lastScore) { (int lastScore) {
setState(() { _lastScore = lastScore; }); setState(() { _lastScore = lastScore; });
args.navigator.pop(); Navigator.of(args.context).pop();
} }
); );
args.navigator.pushNamed('/game'); Navigator.of(args.context).pushNamed('/game');
}, },
texture: _spriteSheetUI['btn_play_up.png'], texture: _spriteSheetUI['btn_play_up.png'],
textureDown: _spriteSheetUI['btn_play_down.png'], textureDown: _spriteSheetUI['btn_play_down.png'],
......
...@@ -92,8 +92,8 @@ class StocksAppState extends State<StocksApp> { ...@@ -92,8 +92,8 @@ class StocksAppState extends State<StocksApp> {
title: 'Stocks', title: 'Stocks',
theme: theme, theme: theme,
routes: <String, RouteBuilder>{ routes: <String, RouteBuilder>{
'/': (RouteArguments args) => new StockHome(args.navigator, _stocks, _symbols, _optimismSetting, modeUpdater), '/': (RouteArguments args) => new StockHome(_stocks, _symbols, _optimismSetting, modeUpdater),
'/settings': (RouteArguments args) => new StockSettings(args.navigator, _optimismSetting, _backupSetting, settingsUpdater) '/settings': (RouteArguments args) => new StockSettings(_optimismSetting, _backupSetting, settingsUpdater)
}, },
onGenerateRoute: _getRoute onGenerateRoute: _getRoute
); );
......
...@@ -7,9 +7,8 @@ part of stocks; ...@@ -7,9 +7,8 @@ part of stocks;
typedef void ModeUpdater(StockMode mode); typedef void ModeUpdater(StockMode mode);
class StockHome extends StatefulComponent { class StockHome extends StatefulComponent {
StockHome(this.navigator, this.stocks, this.symbols, this.stockMode, this.modeUpdater); StockHome(this.stocks, this.symbols, this.stockMode, this.modeUpdater);
final NavigatorState navigator;
final Map<String, Stock> stocks; final Map<String, Stock> stocks;
final List<String> symbols; final List<String> symbols;
final StockMode stockMode; final StockMode stockMode;
...@@ -25,7 +24,7 @@ class StockHomeState extends State<StockHome> { ...@@ -25,7 +24,7 @@ class StockHomeState extends State<StockHome> {
String _searchQuery; String _searchQuery;
void _handleSearchBegin() { void _handleSearchBegin() {
config.navigator.pushState(this, (_) { Navigator.of(context).pushState(this, (_) {
setState(() { setState(() {
_isSearching = false; _isSearching = false;
_searchQuery = null; _searchQuery = null;
...@@ -38,10 +37,10 @@ class StockHomeState extends State<StockHome> { ...@@ -38,10 +37,10 @@ class StockHomeState extends State<StockHome> {
void _handleSearchEnd() { void _handleSearchEnd() {
assert(() { assert(() {
final StateRoute currentRoute = config.navigator.currentRoute; final StateRoute currentRoute = Navigator.of(context).currentRoute;
return currentRoute.owner == this; return currentRoute.owner == this;
}); });
config.navigator.pop(); Navigator.of(context).pop();
} }
void _handleSearchQueryChanged(String query) { void _handleSearchQueryChanged(String query) {
...@@ -92,13 +91,13 @@ class StockHomeState extends State<StockHome> { ...@@ -92,13 +91,13 @@ class StockHomeState extends State<StockHome> {
new FlatButton( new FlatButton(
child: new Text('USE IT'), child: new Text('USE IT'),
onPressed: () { onPressed: () {
config.navigator.pop(false); Navigator.of(context).pop(false);
} }
), ),
new FlatButton( new FlatButton(
child: new Text('OH WELL'), child: new Text('OH WELL'),
onPressed: () { onPressed: () {
config.navigator.pop(false); Navigator.of(context).pop(false);
} }
), ),
] ]
...@@ -142,8 +141,8 @@ class StockHomeState extends State<StockHome> { ...@@ -142,8 +141,8 @@ class StockHomeState extends State<StockHome> {
} }
void _handleShowSettings() { void _handleShowSettings() {
config.navigator.pop(); Navigator.of(context)..pop()
config.navigator.pushNamed('/settings'); ..pushNamed('/settings');
} }
Widget buildToolBar() { Widget buildToolBar() {
...@@ -193,7 +192,7 @@ class StockHomeState extends State<StockHome> { ...@@ -193,7 +192,7 @@ class StockHomeState extends State<StockHome> {
onOpen: (Stock stock, Key arrowKey) { onOpen: (Stock stock, Key arrowKey) {
Set<Key> mostValuableKeys = new Set<Key>(); Set<Key> mostValuableKeys = new Set<Key>();
mostValuableKeys.add(arrowKey); mostValuableKeys.add(arrowKey);
config.navigator.pushNamed('/stock/${stock.symbol}', mostValuableKeys: mostValuableKeys); Navigator.of(context).pushNamed('/stock/${stock.symbol}', mostValuableKeys: mostValuableKeys);
} }
); );
} }
...@@ -239,7 +238,7 @@ class StockHomeState extends State<StockHome> { ...@@ -239,7 +238,7 @@ class StockHomeState extends State<StockHome> {
} }
void _handleUndo() { void _handleUndo() {
config.navigator.pop(); Navigator.of(context).pop();
} }
void _handleStockPurchased() { void _handleStockPurchased() {
......
...@@ -10,9 +10,8 @@ typedef void SettingsUpdater({ ...@@ -10,9 +10,8 @@ typedef void SettingsUpdater({
}); });
class StockSettings extends StatefulComponent { class StockSettings extends StatefulComponent {
const StockSettings(this.navigator, this.optimism, this.backup, this.updater); const StockSettings(this.optimism, this.backup, this.updater);
final NavigatorState navigator;
final StockMode optimism; final StockMode optimism;
final BackupMode backup; final BackupMode backup;
final SettingsUpdater updater; final SettingsUpdater updater;
...@@ -41,19 +40,19 @@ class StockSettingsState extends State<StockSettings> { ...@@ -41,19 +40,19 @@ class StockSettingsState extends State<StockSettings> {
title: new Text("Change mode?"), title: new Text("Change mode?"),
content: new Text("Optimistic mode means everything is awesome. Are you sure you can handle that?"), content: new Text("Optimistic mode means everything is awesome. Are you sure you can handle that?"),
onDismiss: () { onDismiss: () {
config.navigator.pop(false); Navigator.of(context).pop(false);
}, },
actions: <Widget>[ actions: <Widget>[
new FlatButton( new FlatButton(
child: new Text('NO THANKS'), child: new Text('NO THANKS'),
onPressed: () { onPressed: () {
config.navigator.pop(false); Navigator.of(context).pop(false);
} }
), ),
new FlatButton( new FlatButton(
child: new Text('AGREE'), child: new Text('AGREE'),
onPressed: () { onPressed: () {
config.navigator.pop(true); Navigator.of(context).pop(true);
} }
), ),
] ]
...@@ -75,7 +74,7 @@ class StockSettingsState extends State<StockSettings> { ...@@ -75,7 +74,7 @@ class StockSettingsState extends State<StockSettings> {
return new ToolBar( return new ToolBar(
left: new IconButton( left: new IconButton(
icon: 'navigation/arrow_back', icon: 'navigation/arrow_back',
onPressed: config.navigator.pop onPressed: () => Navigator.of(context).pop()
), ),
center: new Text('Settings') center: new Text('Settings')
); );
......
...@@ -65,8 +65,7 @@ class Dot extends StatelessComponent { ...@@ -65,8 +65,7 @@ class Dot extends StatelessComponent {
} }
class ExampleDragSource extends StatelessComponent { class ExampleDragSource extends StatelessComponent {
ExampleDragSource({ Key key, this.navigator, this.name, this.color }) : super(key: key); ExampleDragSource({ Key key, this.name, this.color }) : super(key: key);
final NavigatorState navigator;
final String name; final String name;
final Color color; final Color color;
...@@ -75,7 +74,6 @@ class ExampleDragSource extends StatelessComponent { ...@@ -75,7 +74,6 @@ class ExampleDragSource extends StatelessComponent {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Draggable( return new Draggable(
navigator: navigator,
data: new DragData(name), data: new DragData(name),
child: new Dot(color: color, size: kDotSize), child: new Dot(color: color, size: kDotSize),
feedback: new Transform( feedback: new Transform(
...@@ -91,13 +89,7 @@ class ExampleDragSource extends StatelessComponent { ...@@ -91,13 +89,7 @@ class ExampleDragSource extends StatelessComponent {
} }
} }
class DragAndDropApp extends StatefulComponent { class DragAndDropApp extends StatelessComponent {
DragAndDropApp({ this.navigator });
final NavigatorState navigator;
DragAndDropAppState createState() => new DragAndDropAppState();
}
class DragAndDropAppState extends State<DragAndDropApp> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Scaffold( return new Scaffold(
toolBar: new ToolBar( toolBar: new ToolBar(
...@@ -107,9 +99,9 @@ class DragAndDropAppState extends State<DragAndDropApp> { ...@@ -107,9 +99,9 @@ class DragAndDropAppState extends State<DragAndDropApp> {
style: Theme.of(context).text.body1.copyWith(textAlign: TextAlign.center), style: Theme.of(context).text.body1.copyWith(textAlign: TextAlign.center),
child: new Column(<Widget>[ child: new Column(<Widget>[
new Flexible(child: new Row(<Widget>[ new Flexible(child: new Row(<Widget>[
new ExampleDragSource(navigator: config.navigator, name: 'Orange', color: const Color(0xFFFF9000)), new ExampleDragSource(name: 'Orange', color: const Color(0xFFFF9000)),
new ExampleDragSource(navigator: config.navigator, name: 'Teal', color: const Color(0xFF00FFFF)), new ExampleDragSource(name: 'Teal', color: const Color(0xFF00FFFF)),
new ExampleDragSource(navigator: config.navigator, name: 'Yellow', color: const Color(0xFFFFF000)), new ExampleDragSource(name: 'Yellow', color: const Color(0xFFFFF000)),
], ],
alignItems: FlexAlignItems.center, alignItems: FlexAlignItems.center,
justifyContent: FlexJustifyContent.spaceAround justifyContent: FlexJustifyContent.spaceAround
...@@ -130,7 +122,7 @@ void main() { ...@@ -130,7 +122,7 @@ void main() {
runApp(new MaterialApp( runApp(new MaterialApp(
title: 'Drag and Drop Flutter Demo', title: 'Drag and Drop Flutter Demo',
routes: <String, RouteBuilder>{ routes: <String, RouteBuilder>{
'/': (RouteArguments args) => new DragAndDropApp(navigator: args.navigator) '/': (RouteArguments args) => new DragAndDropApp()
} }
)); ));
} }
...@@ -12,11 +12,11 @@ final Map<String, RouteBuilder> routes = <String, RouteBuilder>{ ...@@ -12,11 +12,11 @@ final Map<String, RouteBuilder> routes = <String, RouteBuilder>{
new Text("You are at home"), new Text("You are at home"),
new RaisedButton( new RaisedButton(
child: new Text('GO SHOPPING'), child: new Text('GO SHOPPING'),
onPressed: () => args.navigator.pushNamed('/shopping') onPressed: () => Navigator.of(args.context).pushNamed('/shopping')
), ),
new RaisedButton( new RaisedButton(
child: new Text('START ADVENTURE'), child: new Text('START ADVENTURE'),
onPressed: () => args.navigator.pushNamed('/adventure') onPressed: () => Navigator.of(args.context).pushNamed('/adventure')
)], )],
justifyContent: FlexJustifyContent.center justifyContent: FlexJustifyContent.center
) )
...@@ -28,11 +28,11 @@ final Map<String, RouteBuilder> routes = <String, RouteBuilder>{ ...@@ -28,11 +28,11 @@ final Map<String, RouteBuilder> routes = <String, RouteBuilder>{
new Text("Village Shop"), new Text("Village Shop"),
new RaisedButton( new RaisedButton(
child: new Text('RETURN HOME'), child: new Text('RETURN HOME'),
onPressed: () => args.navigator.pop() onPressed: () => Navigator.of(args.context).pop()
), ),
new RaisedButton( new RaisedButton(
child: new Text('GO TO DUNGEON'), child: new Text('GO TO DUNGEON'),
onPressed: () => args.navigator.pushNamed('/adventure') onPressed: () => Navigator.of(args.context).pushNamed('/adventure')
)], )],
justifyContent: FlexJustifyContent.center justifyContent: FlexJustifyContent.center
) )
...@@ -44,7 +44,7 @@ final Map<String, RouteBuilder> routes = <String, RouteBuilder>{ ...@@ -44,7 +44,7 @@ final Map<String, RouteBuilder> routes = <String, RouteBuilder>{
new Text("Monster's Lair"), new Text("Monster's Lair"),
new RaisedButton( new RaisedButton(
child: new Text('RUN!!!'), child: new Text('RUN!!!'),
onPressed: () => args.navigator.pop() onPressed: () => Navigator.of(args.context).pop()
)], )],
justifyContent: FlexJustifyContent.center justifyContent: FlexJustifyContent.center
) )
......
...@@ -26,7 +26,7 @@ class IconTheme extends InheritedWidget { ...@@ -26,7 +26,7 @@ class IconTheme extends InheritedWidget {
bool updateShouldNotify(IconTheme old) => data != old.data; bool updateShouldNotify(IconTheme old) => data != old.data;
void debugFillDescription(List<String> description) { void debugFillDescription(List<String> description) {
super.debugFillDescription(description); super.debugFillDescription(description);
description.add('$data'); description.add('$data');
} }
} }
...@@ -37,19 +37,16 @@ enum DragAnchor { ...@@ -37,19 +37,16 @@ enum DragAnchor {
class Draggable extends StatefulComponent { class Draggable extends StatefulComponent {
Draggable({ Draggable({
Key key, Key key,
this.navigator,
this.data, this.data,
this.child, this.child,
this.feedback, this.feedback,
this.feedbackOffset: Offset.zero, this.feedbackOffset: Offset.zero,
this.dragAnchor: DragAnchor.child this.dragAnchor: DragAnchor.child
}) : super(key: key) { }) : super(key: key) {
assert(navigator != null);
assert(child != null); assert(child != null);
assert(feedback != null); assert(feedback != null);
} }
final NavigatorState navigator;
final dynamic data; final dynamic data;
final Widget child; final Widget child;
final Widget feedback; final Widget feedback;
...@@ -91,12 +88,12 @@ class _DraggableState extends State<Draggable> { ...@@ -91,12 +88,12 @@ class _DraggableState extends State<Draggable> {
} }
); );
_route.update(point); _route.update(point);
config.navigator.push(_route); Navigator.of(context).push(_route);
} }
void _updateDrag(PointerInputEvent event) { void _updateDrag(PointerInputEvent event) {
if (_route != null) { if (_route != null) {
config.navigator.setState(() { Navigator.of(context).setState(() {
_route.update(new Point(event.x, event.y)); _route.update(new Point(event.x, event.y));
}); });
} }
...@@ -104,7 +101,7 @@ class _DraggableState extends State<Draggable> { ...@@ -104,7 +101,7 @@ class _DraggableState extends State<Draggable> {
void _cancelDrag(PointerInputEvent event) { void _cancelDrag(PointerInputEvent event) {
if (_route != null) { if (_route != null) {
config.navigator.popRoute(_route, DragEndKind.canceled); Navigator.of(context).popRoute(_route, DragEndKind.canceled);
assert(_route == null); assert(_route == null);
} }
} }
...@@ -112,7 +109,7 @@ class _DraggableState extends State<Draggable> { ...@@ -112,7 +109,7 @@ class _DraggableState extends State<Draggable> {
void _drop(PointerInputEvent event) { void _drop(PointerInputEvent event) {
if (_route != null) { if (_route != null) {
_route.update(new Point(event.x, event.y)); _route.update(new Point(event.x, event.y));
config.navigator.popRoute(_route, DragEndKind.dropped); Navigator.of(context).popRoute(_route, DragEndKind.dropped);
assert(_route == null); assert(_route == null);
} }
} }
......
...@@ -111,8 +111,9 @@ class Focus extends StatefulComponent { ...@@ -111,8 +111,9 @@ class Focus extends StatefulComponent {
return true; return true;
} }
// Don't call moveTo() from your build() function, it's intended to be called // Don't call moveTo() and moveScopeTo() from your build()
// from event listeners, e.g. in response to a finger tap or tab key. // functions, it's intended to be called from event listeners, e.g.
// in response to a finger tap or tab key.
static void moveTo(BuildContext context, Widget widget) { static void moveTo(BuildContext context, Widget widget) {
assert(widget != null); assert(widget != null);
...@@ -122,7 +123,7 @@ class Focus extends StatefulComponent { ...@@ -122,7 +123,7 @@ class Focus extends StatefulComponent {
focusScope.focusState._setFocusedWidget(widget.key); focusScope.focusState._setFocusedWidget(widget.key);
} }
static void _moveScopeTo(BuildContext context, Focus component) { static void moveScopeTo(BuildContext context, Focus component) {
assert(component != null); assert(component != null);
assert(component.key != null); assert(component.key != null);
_FocusScope focusScope = context.inheritedWidgetOfType(_FocusScope); _FocusScope focusScope = context.inheritedWidgetOfType(_FocusScope);
...@@ -209,8 +210,6 @@ class FocusState extends State<Focus> { ...@@ -209,8 +210,6 @@ class FocusState extends State<Focus> {
void initState() { void initState() {
super.initState(); super.initState();
if (config.autofocus)
Focus._moveScopeTo(context, config);
_updateWidgetRemovalListener(_focusedWidget); _updateWidgetRemovalListener(_focusedWidget);
_updateScopeRemovalListener(_focusedScope); _updateScopeRemovalListener(_focusedScope);
} }
......
...@@ -1023,10 +1023,14 @@ abstract class ComponentElement<T extends Widget> extends BuildableElement<T> { ...@@ -1023,10 +1023,14 @@ abstract class ComponentElement<T extends Widget> extends BuildableElement<T> {
super.mount(parent, newSlot); super.mount(parent, newSlot);
assert(_child == null); assert(_child == null);
assert(_active); assert(_active);
rebuild(); _firstBuild();
assert(_child != null); assert(_child != null);
} }
void _firstBuild() {
rebuild();
}
/// Reinvokes the build() method of the StatelessComponent object (for /// Reinvokes the build() method of the StatelessComponent object (for
/// stateless components) or the State object (for stateful components) and /// stateless components) or the State object (for stateful components) and
/// then updates the widget tree. /// then updates the widget tree.
...@@ -1098,6 +1102,13 @@ class StatefulComponentElement<T extends StatefulComponent, U extends State<T>> ...@@ -1098,6 +1102,13 @@ class StatefulComponentElement<T extends StatefulComponent, U extends State<T>>
assert(_state._config == null); assert(_state._config == null);
_state._config = widget; _state._config = widget;
assert(_state._debugLifecycleState == _StateLifecycle.created); assert(_state._debugLifecycleState == _StateLifecycle.created);
}
U get state => _state;
U _state;
void _firstBuild() {
assert(_state._debugLifecycleState == _StateLifecycle.created);
try { try {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(true); _debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
_state.initState(); _state.initState();
...@@ -1111,11 +1122,9 @@ class StatefulComponentElement<T extends StatefulComponent, U extends State<T>> ...@@ -1111,11 +1122,9 @@ class StatefulComponentElement<T extends StatefulComponent, U extends State<T>>
return false; return false;
}); });
assert(() { _state._debugLifecycleState = _StateLifecycle.ready; return true; }); assert(() { _state._debugLifecycleState = _StateLifecycle.ready; return true; });
super._firstBuild();
} }
U get state => _state;
U _state;
void update(T newWidget) { void update(T newWidget) {
super.update(newWidget); super.update(newWidget);
assert(widget == newWidget); assert(widget == newWidget);
......
...@@ -245,6 +245,7 @@ class _GestureDetectorState extends State<GestureDetector> { ...@@ -245,6 +245,7 @@ class _GestureDetectorState extends State<GestureDetector> {
} }
void debugFillDescription(List<String> description) { void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
List<String> gestures = <String>[]; List<String> gestures = <String>[];
if (_tap != null) if (_tap != null)
gestures.add('tap'); gestures.add('tap');
...@@ -262,7 +263,7 @@ class _GestureDetectorState extends State<GestureDetector> { ...@@ -262,7 +263,7 @@ class _GestureDetectorState extends State<GestureDetector> {
gestures.add('pan'); gestures.add('pan');
if (_scale != null) if (_scale != null)
gestures.add('scale'); gestures.add('scale');
if (gestures.isEmpty); if (gestures.isEmpty)
gestures.add('<none>'); gestures.add('<none>');
description.add('gestures: ${gestures.join(", ")}'); description.add('gestures: ${gestures.join(", ")}');
} }
......
...@@ -63,7 +63,7 @@ class _ChildKey { ...@@ -63,7 +63,7 @@ class _ChildKey {
return type == typedOther.type && return type == typedOther.type &&
key == typedOther.key; key == typedOther.key;
} }
int get hashCode => 373 * 37 * type.hashCode + key.hashCode; int get hashCode => ((373 * 37) + type.hashCode) * 37 + key.hashCode;
String toString() => "_ChildKey(type: $type, key: $key)"; String toString() => "_ChildKey(type: $type, key: $key)";
} }
......
...@@ -21,8 +21,8 @@ Color debugGridColor = const Color(0x7F7F2020); ...@@ -21,8 +21,8 @@ Color debugGridColor = const Color(0x7F7F2020);
const String kDefaultRouteName = '/'; const String kDefaultRouteName = '/';
class RouteArguments { class RouteArguments {
const RouteArguments(this.navigator, { this.previousPerformance, this.nextPerformance }); const RouteArguments({ this.context, this.previousPerformance, this.nextPerformance });
final NavigatorState navigator; final BuildContext context;
final PerformanceView previousPerformance; final PerformanceView previousPerformance;
final PerformanceView nextPerformance; final PerformanceView nextPerformance;
} }
...@@ -49,17 +49,13 @@ class Navigator extends StatefulComponent { ...@@ -49,17 +49,13 @@ class Navigator extends StatefulComponent {
static NavigatorState of(BuildContext context) { static NavigatorState of(BuildContext context) {
NavigatorState result; NavigatorState result;
bool visitor(Element element) { context.visitAncestorElements((Element element) {
if (element is StatefulComponentElement) { if (element is StatefulComponentElement && element.state is NavigatorState) {
if (element.state is NavigatorState) { result = element.state;
result = element.state; return false;
return false;
}
} }
return true; return true;
} });
if (visitor(context))
context.visitAncestorElements(visitor);
return result; return result;
} }
...@@ -268,9 +264,9 @@ class NavigatorState extends State<Navigator> { ...@@ -268,9 +264,9 @@ class NavigatorState extends State<Navigator> {
if (_desiredHeroes.hasInstructions) { if (_desiredHeroes.hasInstructions) {
if ((_desiredHeroes.to == route || _desiredHeroes.from == route) && nextHeroPerformance == null) if ((_desiredHeroes.to == route || _desiredHeroes.from == route) && nextHeroPerformance == null)
nextHeroPerformance = route.performance; nextHeroPerformance = route.performance;
visibleRoutes.add(route._internalBuild(nextContentRoute, buildTargetHeroes: _desiredHeroes.to == route)); visibleRoutes.add(new _RouteWidget(route: route, nextRoute: nextContentRoute, buildTargetHeroes: _desiredHeroes.to == route));
} else { } else {
visibleRoutes.add(route._internalBuild(nextContentRoute)); visibleRoutes.add(new _RouteWidget(route: route, nextRoute: nextContentRoute));
} }
if (route.isActuallyOpaque) { if (route.isActuallyOpaque) {
assert(!_desiredHeroes.hasInstructions || assert(!_desiredHeroes.hasInstructions ||
...@@ -330,6 +326,82 @@ class NavigatorState extends State<Navigator> { ...@@ -330,6 +326,82 @@ class NavigatorState extends State<Navigator> {
} }
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<String> 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<Key> keys;
void addKey(Key key) {
assert(key != null);
assert(key is! GlobalKey);
keys ??= <Key>[];
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 { abstract class Route {
Route() { Route() {
_subtreeKey = new GlobalKey(label: debugLabel); _subtreeKey = new GlobalKey(label: debugLabel);
...@@ -390,10 +462,13 @@ abstract class Route { ...@@ -390,10 +462,13 @@ abstract class Route {
NavigatorState get navigator => _navigator; NavigatorState get navigator => _navigator;
NavigatorState _navigator; NavigatorState _navigator;
_RouteWidgetState _widgetState;
void setState(void fn()) { void setState(void fn()) {
assert(navigator != null); if (_widgetState != null)
navigator.setState(fn); _widgetState.setState(fn);
else
fn();
} }
void didPush(NavigatorState navigator) { void didPush(NavigatorState navigator) {
...@@ -419,18 +494,21 @@ abstract class Route { ...@@ -419,18 +494,21 @@ abstract class Route {
} }
} }
/// Called by the navigator.build() function if hasContent is true, to get the /// Called (indirectly, via a RouteWidget) by the navigator.build()
/// subtree for this route. /// function if hasContent is true, to get the subtree for this
/// route.
/// ///
/// If buildTargetHeroes is true, then getHeroesToAnimate() will be called /// If buildTargetHeroes is true, then getHeroesToAnimate() will be called
/// after this build, before the next build, and this build should render the /// 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 /// 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 /// will be false, and the tree should be built at the first frame of the
/// transition animation, whatever that is. /// transition animation, whatever that is.
Widget _internalBuild(Route nextRoute, { bool buildTargetHeroes: false }) { Widget _internalBuild(BuildContext context, Route nextRoute, { bool buildTargetHeroes: false }) {
assert(navigator != null); assert(navigator != null);
assert(_widgetState != null);
assert(hasContent);
return keySubtree(build(new RouteArguments( return keySubtree(build(new RouteArguments(
navigator, context: context,
previousPerformance: performance, previousPerformance: performance,
nextPerformance: nextRoute?.performance nextPerformance: nextRoute?.performance
))); )));
...@@ -446,12 +524,12 @@ abstract class Route { ...@@ -446,12 +524,12 @@ abstract class Route {
Map<Object, HeroHandle> getHeroesToAnimate([Set<Key> mostValuableKeys]) => const <Object, HeroHandle>{}; Map<Object, HeroHandle> getHeroesToAnimate([Set<Key> mostValuableKeys]) => const <Object, HeroHandle>{};
bool _hasActiveHeroes = false; bool _hasActiveHeroes = false;
GlobalKey _subtreeKey;
/// Returns the BuildContext for the root of the subtree built for this route, /// Returns the BuildContext for the root of the subtree built for this route,
/// assuming that internalBuild used keySubtree to build that subtree. /// assuming that internalBuild used keySubtree to build that subtree.
/// This is only valid after a build phase. /// This is only valid after a build phase.
BuildContext get context => _subtreeKey.currentContext; BuildContext get subtreeContext => _subtreeKey.currentContext;
GlobalKey _subtreeKey;
/// Wraps the given subtree in a route-specific GlobalKey. /// Wraps the given subtree in a route-specific GlobalKey.
Widget keySubtree(Widget child) { Widget keySubtree(Widget child) {
...@@ -465,6 +543,52 @@ abstract class Route { ...@@ -465,6 +543,52 @@ abstract class Route {
/// change what subtree is built for this route. /// change what subtree is built for this route.
Widget build(RouteArguments args); 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 get debugLabel => '$runtimeType';
String toString() => '$runtimeType(performance: $performance; key: $_subtreeKey)'; String toString() => '$runtimeType(performance: $performance; key: $_subtreeKey)';
...@@ -487,7 +611,7 @@ abstract class PerformanceRoute extends Route { ...@@ -487,7 +611,7 @@ abstract class PerformanceRoute extends Route {
Duration get transitionDuration; Duration get transitionDuration;
Widget _internalBuild(Route nextRoute, { bool buildTargetHeroes: false }) { Widget _internalBuild(BuildContext context, Route nextRoute, { bool buildTargetHeroes: false }) {
assert(hasContent); assert(hasContent);
assert(transitionDuration > Duration.ZERO); assert(transitionDuration > Duration.ZERO);
if (buildTargetHeroes && performance.progress != 1.0) { if (buildTargetHeroes && performance.progress != 1.0) {
...@@ -496,11 +620,11 @@ abstract class PerformanceRoute extends Route { ...@@ -496,11 +620,11 @@ abstract class PerformanceRoute extends Route {
fakePerformance.progress = 1.0; fakePerformance.progress = 1.0;
return new OffStage( return new OffStage(
child: keySubtree( child: keySubtree(
build(new RouteArguments(navigator, previousPerformance: fakePerformance)) build(new RouteArguments(context: context, previousPerformance: fakePerformance))
) )
); );
} }
return super._internalBuild(nextRoute, buildTargetHeroes: buildTargetHeroes); return super._internalBuild(context, nextRoute, buildTargetHeroes: buildTargetHeroes);
} }
void didPush(NavigatorState navigator) { void didPush(NavigatorState navigator) {
...@@ -539,7 +663,7 @@ class PageRoute extends PerformanceRoute { ...@@ -539,7 +663,7 @@ class PageRoute extends PerformanceRoute {
Duration get transitionDuration => _kTransitionDuration; Duration get transitionDuration => _kTransitionDuration;
Map<Object, HeroHandle> getHeroesToAnimate([Set<Key> mostValuableKeys]) { Map<Object, HeroHandle> getHeroesToAnimate([Set<Key> mostValuableKeys]) {
return Hero.of(context, mostValuableKeys); return Hero.of(subtreeContext, mostValuableKeys);
} }
Widget build(RouteArguments args) { Widget build(RouteArguments args) {
......
...@@ -16,6 +16,7 @@ import 'framework.dart'; ...@@ -16,6 +16,7 @@ import 'framework.dart';
import 'gesture_detector.dart'; import 'gesture_detector.dart';
import 'homogeneous_viewport.dart'; import 'homogeneous_viewport.dart';
import 'mixed_viewport.dart'; import 'mixed_viewport.dart';
import 'navigator.dart';
// The gesture velocity properties are pixels/second, config min,max limits are pixels/ms // The gesture velocity properties are pixels/second, config min,max limits are pixels/ms
const double _kMillisecondsPerSecond = 1000.0; const double _kMillisecondsPerSecond = 1000.0;
...@@ -54,15 +55,14 @@ abstract class Scrollable extends StatefulComponent { ...@@ -54,15 +55,14 @@ abstract class Scrollable extends StatefulComponent {
abstract class ScrollableState<T extends Scrollable> extends State<T> { abstract class ScrollableState<T extends Scrollable> extends State<T> {
void initState() { void initState() {
super.initState(); super.initState();
if (config.initialScrollOffset is double)
_scrollOffset = config.initialScrollOffset;
_animation = new SimulationStepper(_setScrollOffset); _animation = new SimulationStepper(_setScrollOffset);
_scrollOffset = Route.of(context)?.readState(context) ?? config.initialScrollOffset ?? 0.0;
} }
SimulationStepper _animation; SimulationStepper _animation;
double _scrollOffset = 0.0;
double get scrollOffset => _scrollOffset; double get scrollOffset => _scrollOffset;
double _scrollOffset;
Offset get scrollOffsetVector { Offset get scrollOffsetVector {
if (config.scrollDirection == ScrollDirection.horizontal) if (config.scrollDirection == ScrollDirection.horizontal)
...@@ -178,6 +178,7 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -178,6 +178,7 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
setState(() { setState(() {
_scrollOffset = newScrollOffset; _scrollOffset = newScrollOffset;
}); });
Route.of(context)?.writeState(context, _scrollOffset);
dispatchOnScroll(); dispatchOnScroll();
} }
...@@ -510,8 +511,8 @@ abstract class ScrollableWidgetListState<T extends ScrollableWidgetList> extends ...@@ -510,8 +511,8 @@ abstract class ScrollableWidgetListState<T extends ScrollableWidgetList> extends
} }
if (itemCount != _previousItemCount) { if (itemCount != _previousItemCount) {
scrollBehaviorUpdateNeeded = true;
_previousItemCount = itemCount; _previousItemCount = itemCount;
scrollBehaviorUpdateNeeded = true;
} }
if (scrollBehaviorUpdateNeeded) if (scrollBehaviorUpdateNeeded)
...@@ -558,6 +559,8 @@ abstract class ScrollableWidgetListState<T extends ScrollableWidgetList> extends ...@@ -558,6 +559,8 @@ abstract class ScrollableWidgetListState<T extends ScrollableWidgetList> extends
} }
double get _contentExtent { double get _contentExtent {
if (itemCount == null)
return null;
double contentExtent = config.itemExtent * itemCount; double contentExtent = config.itemExtent * itemCount;
if (config.padding != null) if (config.padding != null)
contentExtent += _leadingPadding + _trailingPadding; contentExtent += _leadingPadding + _trailingPadding;
......
...@@ -15,7 +15,6 @@ void main() { ...@@ -15,7 +15,6 @@ void main() {
routes: <String, RouteBuilder>{ routes: <String, RouteBuilder>{
'/': (RouteArguments args) { return new Column(<Widget>[ '/': (RouteArguments args) { return new Column(<Widget>[
new Draggable( new Draggable(
navigator: args.navigator,
data: 1, data: 1,
child: new Text('Source'), child: new Text('Source'),
feedback: new Text('Dragging') feedback: new Text('Dragging')
......
...@@ -8,12 +8,12 @@ void main() { ...@@ -8,12 +8,12 @@ void main() {
test('Drawer control test', () { test('Drawer control test', () {
testWidgets((WidgetTester tester) { testWidgets((WidgetTester tester) {
NavigatorState navigator; BuildContext context;
tester.pumpWidget( tester.pumpWidget(
new MaterialApp( new MaterialApp(
routes: <String, RouteBuilder>{ routes: <String, RouteBuilder>{
'/': (RouteArguments args) { '/': (RouteArguments args) {
navigator = args.navigator; context = args.context;
return new Container(); return new Container();
} }
} }
...@@ -21,12 +21,12 @@ void main() { ...@@ -21,12 +21,12 @@ void main() {
); );
tester.pump(); // no effect tester.pump(); // no effect
expect(tester.findText('drawer'), isNull); expect(tester.findText('drawer'), isNull);
showDrawer(context: navigator.context, child: new Text('drawer')); showDrawer(context: context, child: new Text('drawer'));
tester.pump(); // drawer should be starting to animate in tester.pump(); // drawer should be starting to animate in
expect(tester.findText('drawer'), isNotNull); expect(tester.findText('drawer'), isNotNull);
tester.pump(new Duration(seconds: 1)); // animation done tester.pump(new Duration(seconds: 1)); // animation done
expect(tester.findText('drawer'), isNotNull); expect(tester.findText('drawer'), isNotNull);
navigator.pop(); Navigator.of(context).pop();
tester.pump(); // drawer should be starting to animate away tester.pump(); // drawer should be starting to animate away
expect(tester.findText('drawer'), isNotNull); expect(tester.findText('drawer'), isNotNull);
tester.pump(new Duration(seconds: 1)); // animation done tester.pump(new Duration(seconds: 1)); // animation done
...@@ -36,13 +36,13 @@ void main() { ...@@ -36,13 +36,13 @@ void main() {
test('Drawer tap test', () { test('Drawer tap test', () {
testWidgets((WidgetTester tester) { testWidgets((WidgetTester tester) {
NavigatorState navigator; BuildContext context;
tester.pumpWidget(new Container()); // throw away the old App and its Navigator tester.pumpWidget(new Container()); // throw away the old App and its Navigator
tester.pumpWidget( tester.pumpWidget(
new MaterialApp( new MaterialApp(
routes: <String, RouteBuilder>{ routes: <String, RouteBuilder>{
'/': (RouteArguments args) { '/': (RouteArguments args) {
navigator = args.navigator; context = args.context;
return new Container(); return new Container();
} }
} }
...@@ -50,7 +50,7 @@ void main() { ...@@ -50,7 +50,7 @@ void main() {
); );
tester.pump(); // no effect tester.pump(); // no effect
expect(tester.findText('drawer'), isNull); expect(tester.findText('drawer'), isNull);
showDrawer(context: navigator.context, child: new Text('drawer')); showDrawer(context: context, child: new Text('drawer'));
tester.pump(); // drawer should be starting to animate in tester.pump(); // drawer should be starting to animate in
expect(tester.findText('drawer'), isNotNull); expect(tester.findText('drawer'), isNotNull);
tester.pump(new Duration(seconds: 1)); // animation done tester.pump(new Duration(seconds: 1)); // animation done
......
// 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/widgets.dart';
import 'package:test/test.dart';
import 'widget_tester.dart';
List<String> ancestors = <String>[];
class TestComponent extends StatefulComponent {
TestComponentState createState() => new TestComponentState();
}
class TestComponentState extends State<TestComponent> {
void initState() {
super.initState();
context.visitAncestorElements((Element element) {
ancestors.add(element.widget.runtimeType.toString());
return true;
});
}
Widget build(BuildContext context) => new Container();
}
void main() {
test('initState() is called when we are in the tree', () {
testWidgets((WidgetTester tester) {
tester.pumpWidget(new Container(child: new TestComponent()));
expect(ancestors, equals(<String>['Container', 'RenderObjectToWidgetAdapter<RenderBox>']));
});
});
}
...@@ -4,14 +4,10 @@ import 'package:test/test.dart'; ...@@ -4,14 +4,10 @@ import 'package:test/test.dart';
import 'widget_tester.dart'; import 'widget_tester.dart';
class FirstComponent extends StatelessComponent { class FirstComponent extends StatelessComponent {
FirstComponent(this.navigator);
final NavigatorState navigator;
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new GestureDetector( return new GestureDetector(
onTap: () { onTap: () {
navigator.pushNamed('/second'); Navigator.of(context).pushNamed('/second');
}, },
child: new Container( child: new Container(
decoration: new BoxDecoration( decoration: new BoxDecoration(
...@@ -24,17 +20,13 @@ class FirstComponent extends StatelessComponent { ...@@ -24,17 +20,13 @@ class FirstComponent extends StatelessComponent {
} }
class SecondComponent extends StatefulComponent { class SecondComponent extends StatefulComponent {
SecondComponent(this.navigator);
final NavigatorState navigator;
SecondComponentState createState() => new SecondComponentState(); SecondComponentState createState() => new SecondComponentState();
} }
class SecondComponentState extends State<SecondComponent> { class SecondComponentState extends State<SecondComponent> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new GestureDetector( return new GestureDetector(
onTap: config.navigator.pop, onTap: Navigator.of(context).pop,
child: new Container( child: new Container(
decoration: new BoxDecoration( decoration: new BoxDecoration(
backgroundColor: new Color(0xFFFF00FF) backgroundColor: new Color(0xFFFF00FF)
...@@ -49,8 +41,8 @@ void main() { ...@@ -49,8 +41,8 @@ void main() {
test('Can navigator navigate to and from a stateful component', () { test('Can navigator navigate to and from a stateful component', () {
testWidgets((WidgetTester tester) { testWidgets((WidgetTester tester) {
final Map<String, RouteBuilder> routes = <String, RouteBuilder>{ final Map<String, RouteBuilder> routes = <String, RouteBuilder>{
'/': (RouteArguments args) => new FirstComponent(args.navigator), '/': (RouteArguments args) => new FirstComponent(),
'/second': (RouteArguments args) => new SecondComponent(args.navigator), '/second': (RouteArguments args) => new SecondComponent(),
}; };
tester.pumpWidget(new Navigator(routes: routes)); tester.pumpWidget(new Navigator(routes: routes));
......
// 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 'package:flutter/widgets.dart';
import 'package:test/test.dart';
import 'widget_tester.dart';
class ThePositiveNumbers extends ScrollableWidgetList {
ThePositiveNumbers() : super(itemExtent: 100.0);
ThePositiveNumbersState createState() => new ThePositiveNumbersState();
}
class ThePositiveNumbersState extends ScrollableWidgetListState<ThePositiveNumbers> {
ScrollBehavior createScrollBehavior() => new UnboundedBehavior();
int get itemCount => null;
List<Widget> buildItems(BuildContext context, int start, int count) {
List<Widget> result = new List<Widget>();
for (int index = start; index < start + count; index += 1)
result.add(new Text('$index', key: new ValueKey<int>(index)));
return result;
}
}
void main() {
test('whether we remember our scroll position', () {
testWidgets((WidgetTester tester) {
GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
tester.pumpWidget(new Navigator(
key: navigatorKey,
routes: <String, RouteBuilder>{
'/': (RouteArguments args) => new Container(child: new ThePositiveNumbers()),
'/second': (RouteArguments args) => new Container(child: new ThePositiveNumbers()),
}
));
// we're 600 pixels high, each item is 100 pixels high, scroll position is
// zero, so we should have exactly 6 items, 0..5.
expect(tester.findText('0'), isNotNull);
expect(tester.findText('1'), isNotNull);
expect(tester.findText('2'), isNotNull);
expect(tester.findText('3'), isNotNull);
expect(tester.findText('4'), isNotNull);
expect(tester.findText('5'), isNotNull);
expect(tester.findText('6'), isNull);
expect(tester.findText('10'), isNull);
expect(tester.findText('100'), isNull);
StatefulComponentElement<ThePositiveNumbers, ThePositiveNumbersState> target =
tester.findElement((Element element) => element.widget is ThePositiveNumbers);
target.state.scrollTo(1000.0);
tester.pump(new Duration(seconds: 1));
// we're 600 pixels high, each item is 100 pixels high, scroll position is
// 1000, so we should have exactly 6 items, 10..15.
expect(tester.findText('0'), isNull);
expect(tester.findText('8'), isNull);
expect(tester.findText('9'), isNull);
expect(tester.findText('10'), isNotNull);
expect(tester.findText('11'), isNotNull);
expect(tester.findText('12'), isNotNull);
expect(tester.findText('13'), isNotNull);
expect(tester.findText('14'), isNotNull);
expect(tester.findText('15'), isNotNull);
expect(tester.findText('16'), isNull);
expect(tester.findText('100'), isNull);
navigatorKey.currentState.pushNamed('/second');
tester.pump(); // navigating always takes two frames
tester.pump(new Duration(seconds: 1));
// same as the first list again
expect(tester.findText('0'), isNotNull);
expect(tester.findText('1'), isNotNull);
expect(tester.findText('2'), isNotNull);
expect(tester.findText('3'), isNotNull);
expect(tester.findText('4'), isNotNull);
expect(tester.findText('5'), isNotNull);
expect(tester.findText('6'), isNull);
expect(tester.findText('10'), isNull);
expect(tester.findText('100'), isNull);
navigatorKey.currentState.pop();
tester.pump(); // navigating always takes two frames
tester.pump(new Duration(seconds: 1));
// we're 600 pixels high, each item is 100 pixels high, scroll position is
// 1000, so we should have exactly 6 items, 10..15.
expect(tester.findText('0'), isNull);
expect(tester.findText('8'), isNull);
expect(tester.findText('9'), isNull);
expect(tester.findText('10'), isNotNull);
expect(tester.findText('11'), isNotNull);
expect(tester.findText('12'), isNotNull);
expect(tester.findText('13'), isNotNull);
expect(tester.findText('14'), isNotNull);
expect(tester.findText('15'), isNotNull);
expect(tester.findText('16'), isNull);
expect(tester.findText('100'), isNull);
});
});
}
...@@ -16,7 +16,7 @@ void main() { ...@@ -16,7 +16,7 @@ void main() {
return new GestureDetector( return new GestureDetector(
onTap: () { onTap: () {
showSnackBar( showSnackBar(
context: args.navigator.context, context: args.context,
placeholderKey: placeholderKey, placeholderKey: placeholderKey,
content: new Text(helloSnackBar) content: new Text(helloSnackBar)
); );
......
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