diff --git a/examples/stocks/lib/main.dart b/examples/stocks/lib/main.dart index a70739b93ea..aaacab0d9f4 100644 --- a/examples/stocks/lib/main.dart +++ b/examples/stocks/lib/main.dart @@ -10,7 +10,8 @@ import 'dart:sky' as sky; import 'package:sky/animation.dart'; import 'package:sky/material.dart'; -import 'package:sky/widgets.dart'; +import 'package:sky/painting.dart'; +import 'package:sky/src/fn3.dart'; import 'stock_data.dart'; @@ -22,53 +23,16 @@ part 'stock_row.dart'; part 'stock_settings.dart'; part 'stock_types.dart'; -class StocksApp extends App { +class StocksApp extends StatefulComponent { + StocksAppState createState() => new StocksAppState(); +} - NavigationState _navigationState; - - void initState() { - _navigationState = new NavigationState([ - new Route( - name: '/', - builder: (navigator, route) => new StockHome(navigator, _stocks, optimismSetting, modeUpdater) - ), - new Route( - name: '/settings', - builder: (navigator, route) => new StockSettings(navigator, optimismSetting, backupSetting, settingsUpdater) - ), - ]); - super.initState(); - } - - void onBack() { - if (_navigationState.hasPrevious()) { - setState(() { - _navigationState.pop(); - }); - } else { - super.onBack(); - } - } - - StockMode optimismSetting = StockMode.optimistic; - BackupMode backupSetting = BackupMode.disabled; - void modeUpdater(StockMode optimism) { - setState(() { - optimismSetting = optimism; - }); - } - void settingsUpdater({ StockMode optimism, BackupMode backup }) { - setState(() { - if (optimism != null) - optimismSetting = optimism; - if (backup != null) - backupSetting = backup; - }); - } +class StocksAppState extends State { final List _stocks = []; - void didMount() { - super.didMount(); + + void initState(BuildContext context) { + super.initState(context); new StockDataFetcher((StockData data) { setState(() { data.appendTo(_stocks); @@ -76,32 +40,47 @@ class StocksApp extends App { }); } - Widget build() { + StockMode _optimismSetting = StockMode.optimistic; + BackupMode _backupSetting = BackupMode.disabled; + void modeUpdater(StockMode optimism) { + setState(() { + _optimismSetting = optimism; + }); + } + void settingsUpdater({ StockMode optimism, BackupMode backup }) { + setState(() { + if (optimism != null) + _optimismSetting = optimism; + if (backup != null) + _backupSetting = backup; + }); + } - ThemeData theme; - if (optimismSetting == StockMode.optimistic) { - theme = new ThemeData( - brightness: ThemeBrightness.light, - primarySwatch: Colors.purple - ); - } else { - theme = new ThemeData( - brightness: ThemeBrightness.dark, - accentColor: Colors.redAccent[200] - ); + ThemeData get theme { + switch (_optimismSetting) { + case StockMode.optimistic: + return new ThemeData( + brightness: ThemeBrightness.light, + primarySwatch: Colors.purple + ); + case StockMode.pessimistic: + return new ThemeData( + brightness: ThemeBrightness.dark, + accentColor: Colors.redAccent[200] + ); } + } - return new Theme( - data: theme, - child: new DefaultTextStyle( - style: Typography.error, // if you see this, you've forgotten to correctly configure the text style! - child: new Title( - title: 'Stocks', - child: new Navigator(_navigationState) - ) - ) - ); - } + Widget build(BuildContext context) { + return new App( + title: 'Stocks', + theme: theme, + routes: { + '/': (navigator, route) => new StockHome(navigator, _stocks, _optimismSetting, modeUpdater), + '/settings': (navigator, route) => new StockSettings(navigator, _optimismSetting, _backupSetting, settingsUpdater) + } + ); + } } void main() { diff --git a/examples/stocks/lib/stock_arrow.dart b/examples/stocks/lib/stock_arrow.dart index 394eda319c2..a1f56b56dfe 100644 --- a/examples/stocks/lib/stock_arrow.dart +++ b/examples/stocks/lib/stock_arrow.dart @@ -4,8 +4,7 @@ part of stocks; -class StockArrow extends Component { - +class StockArrow extends StatelessComponent { StockArrow({ Key key, this.percentChange }) : super(key: key); final double percentChange; @@ -22,7 +21,7 @@ class StockArrow extends Component { return Colors.red[_colorIndexForPercentChange(percentChange)]; } - Widget build() { + Widget build(BuildContext context) { // TODO(jackson): This should change colors with the theme Color color = _colorForPercentChange(percentChange); const double kSize = 40.0; @@ -65,5 +64,4 @@ class StockArrow extends Component { margin: const EdgeDims.symmetric(horizontal: 5.0) ); } - } diff --git a/examples/stocks/lib/stock_home.dart b/examples/stocks/lib/stock_home.dart index c85dd54f369..ee47f6e92f2 100644 --- a/examples/stocks/lib/stock_home.dart +++ b/examples/stocks/lib/stock_home.dart @@ -9,20 +9,17 @@ typedef void ModeUpdater(StockMode mode); const Duration _kSnackbarSlideDuration = const Duration(milliseconds: 200); class StockHome extends StatefulComponent { - StockHome(this.navigator, this.stocks, this.stockMode, this.modeUpdater); - Navigator navigator; - List stocks; - StockMode stockMode; - ModeUpdater modeUpdater; + final NavigatorState navigator; + final List stocks; + final StockMode stockMode; + final ModeUpdater modeUpdater; - void syncConstructorArguments(StockHome source) { - navigator = source.navigator; - stocks = source.stocks; - stockMode = source.stockMode; - modeUpdater = source.modeUpdater; - } + StockHomeState createState() => new StockHomeState(); +} + +class StockHomeState extends State { bool _isSearching = false; String _searchQuery; @@ -31,7 +28,7 @@ class StockHome extends StatefulComponent { bool _isSnackBarShowing = false; void _handleSearchBegin() { - navigator.pushState(this, (_) { + config.navigator.pushState(this, (_) { setState(() { _isSearching = false; _searchQuery = null; @@ -43,9 +40,9 @@ class StockHome extends StatefulComponent { } void _handleSearchEnd() { - assert(navigator.currentRoute is RouteState); - assert((navigator.currentRoute as RouteState).owner == this); // TODO(ianh): remove cast once analyzer is cleverer - navigator.pop(); + assert(config.navigator.currentRoute is RouteState); + assert((config.navigator.currentRoute as RouteState).owner == this); // TODO(ianh): remove cast once analyzer is cleverer + config.navigator.pop(); setState(() { _isSearching = false; _searchQuery = null; @@ -82,15 +79,12 @@ class StockHome extends StatefulComponent { } void _handleStockModeChange(StockMode value) { - setState(() { - stockMode = value; - }); - if (modeUpdater != null) - modeUpdater(value); + if (config.modeUpdater != null) + config.modeUpdater(value); } void _handleMenuShow() { - showStockMenu(navigator, + showStockMenu(config.navigator, autorefresh: _autorefresh, onAutorefreshChanged: _handleAutorefreshChanged ); @@ -104,7 +98,7 @@ class StockHome extends StatefulComponent { level: 3, showing: _drawerShowing, onDismissed: _handleDrawerDismissed, - navigator: navigator, + navigator: config.navigator, children: [ new DrawerHeader(child: new Text('Stocks')), new DrawerItem( @@ -122,7 +116,7 @@ class StockHome extends StatefulComponent { onPressed: () => _handleStockModeChange(StockMode.optimistic), child: new Row([ new Flexible(child: new Text('Optimistic')), - new Radio(value: StockMode.optimistic, groupValue: stockMode, onChanged: _handleStockModeChange) + new Radio(value: StockMode.optimistic, groupValue: config.stockMode, onChanged: _handleStockModeChange) ]) ), new DrawerItem( @@ -130,7 +124,7 @@ class StockHome extends StatefulComponent { onPressed: () => _handleStockModeChange(StockMode.pessimistic), child: new Row([ new Flexible(child: new Text('Pessimistic')), - new Radio(value: StockMode.pessimistic, groupValue: stockMode, onChanged: _handleStockModeChange) + new Radio(value: StockMode.pessimistic, groupValue: config.stockMode, onChanged: _handleStockModeChange) ]) ), new DrawerDivider(), @@ -146,23 +140,26 @@ class StockHome extends StatefulComponent { } void _handleShowSettings() { - navigator.pop(); - navigator.pushNamed('/settings'); + config.navigator.pop(); + config.navigator.pushNamed('/settings'); } Widget buildToolBar() { return new ToolBar( left: new IconButton( icon: "navigation/menu", - onPressed: _handleOpenDrawer), + onPressed: _handleOpenDrawer + ), center: new Text('Stocks'), right: [ new IconButton( icon: "action/search", - onPressed: _handleSearchBegin), + onPressed: _handleSearchBegin + ), new IconButton( icon: "navigation/more_vert", - onPressed: _handleMenuShow) + onPressed: _handleMenuShow + ) ] ); } @@ -181,12 +178,12 @@ class StockHome extends StatefulComponent { return stocks.where((stock) => stock.symbol.contains(regexp)); } - Widget buildMarketStockList() { - return new Stocklist(stocks: _filterBySearchQuery(stocks).toList()); + Widget buildMarketStockList(BuildContext context) { + return new Stocklist(stocks: _filterBySearchQuery(config.stocks).toList()); } - Widget buildPortfolioStocklist() { - return new Stocklist(stocks: _filterBySearchQuery(_filterByPortfolio(stocks)).toList()); + Widget buildPortfolioStocklist(BuildContext context) { + return new Stocklist(stocks: _filterBySearchQuery(_filterByPortfolio(config.stocks)).toList()); } Widget buildTabNavigator() { @@ -216,7 +213,7 @@ class StockHome extends StatefulComponent { return new ToolBar( left: new IconButton( icon: "navigation/arrow_back", - color: Theme.of(this).accentColor, + color: Theme.of(context).accentColor, onPressed: _handleSearchEnd ), center: new Input( @@ -224,7 +221,7 @@ class StockHome extends StatefulComponent { placeholder: 'Search stocks', onChanged: _handleSearchQueryChanged ), - backgroundColor: Theme.of(this).canvasColor + backgroundColor: Theme.of(context).canvasColor ); } @@ -255,17 +252,14 @@ class StockHome extends StatefulComponent { } Widget buildFloatingActionButton() { - return new TransitionProxy( - transitionKey: snackBarKey, - child: new FloatingActionButton( - child: new Icon(type: 'content/add', size: 24), - backgroundColor: Colors.redAccent[200], - onPressed: _handleStockPurchased - ) + return new FloatingActionButton( + child: new Icon(type: 'content/add', size: 24), + backgroundColor: Colors.redAccent[200], + onPressed: _handleStockPurchased ); } - Widget build() { + Widget build(BuildContext context) { return new Scaffold( toolbar: _isSearching ? buildSearchBar() : buildToolBar(), body: buildTabNavigator(), diff --git a/examples/stocks/lib/stock_list.dart b/examples/stocks/lib/stock_list.dart index 42ea3842b99..a65d7353ca3 100644 --- a/examples/stocks/lib/stock_list.dart +++ b/examples/stocks/lib/stock_list.dart @@ -4,18 +4,18 @@ part of stocks; -class Stocklist extends Component { +class Stocklist extends StatelessComponent { Stocklist({ Key key, this.stocks }) : super(key: key); final List stocks; - Widget build() { + Widget build(BuildContext context) { return new Material( type: MaterialType.canvas, child: new ScrollableList( items: stocks, itemExtent: StockRow.kHeight, - itemBuilder: (Stock stock) => new StockRow(stock: stock) + itemBuilder: (BuildContext context, Stock stock) => new StockRow(stock: stock) ) ); } diff --git a/examples/stocks/lib/stock_menu.dart b/examples/stocks/lib/stock_menu.dart index a61073a2042..f9c32788a64 100644 --- a/examples/stocks/lib/stock_menu.dart +++ b/examples/stocks/lib/stock_menu.dart @@ -4,19 +4,27 @@ part of stocks; -Future showStockMenu(Navigator navigator, { bool autorefresh, ValueChanged onAutorefreshChanged }) { - return showMenu( +enum _MenuItems { add, remove, autorefresh } + +Future showStockMenu(NavigatorState navigator, { bool autorefresh, ValueChanged onAutorefreshChanged }) async { + switch (await showMenu( navigator: navigator, position: new MenuPosition( right: sky.view.paddingRight, top: sky.view.paddingTop ), - builder: (Navigator navigator) { + builder: (NavigatorState navigator) { return [ - new PopupMenuItem(child: new Text('Add stock')), - new PopupMenuItem(child: new Text('Remove stock')), new PopupMenuItem( - onPressed: () => onAutorefreshChanged(!autorefresh), + value: _MenuItems.add, + child: new Text('Add stock') + ), + new PopupMenuItem( + value: _MenuItems.remove, + child: new Text('Remove stock') + ), + new PopupMenuItem( + value: _MenuItems.autorefresh, child: new Row([ new Flexible(child: new Text('Autorefresh')), new Checkbox( @@ -28,5 +36,28 @@ Future showStockMenu(Navigator navigator, { bool autorefresh, ValueChanged onAut ), ]; } - ); + )) { + case _MenuItems.autorefresh: + onAutorefreshChanged(!autorefresh); + break; + case _MenuItems.add: + case _MenuItems.remove: + await showDialog(navigator, (NavigatorState navigator) { + return new Dialog( + title: new Text('Not Implemented'), + content: new Text('This feature has not yet been implemented.'), + actions: [ + new FlatButton( + child: new Text('OH WELL'), + onPressed: () { + navigator.pop(false); + } + ), + ] + ); + }); + break; + default: + // menu was canceled. + } } \ No newline at end of file diff --git a/examples/stocks/lib/stock_row.dart b/examples/stocks/lib/stock_row.dart index f2f4e680db2..4ebe859a298 100644 --- a/examples/stocks/lib/stock_row.dart +++ b/examples/stocks/lib/stock_row.dart @@ -4,15 +4,14 @@ part of stocks; -class StockRow extends Component { - +class StockRow extends StatelessComponent { StockRow({ Stock stock }) : this.stock = stock, super(key: new Key(stock.symbol)); final Stock stock; static const double kHeight = 79.0; - Widget build() { + Widget build(BuildContext context) { String lastSale = "\$${stock.lastSale.toStringAsFixed(2)}"; String changeInPrice = "${stock.percentChange.toStringAsFixed(2)}%"; @@ -32,7 +31,7 @@ class StockRow extends Component { new Flexible( child: new Text( changeInPrice, - style: Theme.of(this).text.caption.copyWith(textAlign: TextAlign.right) + style: Theme.of(context).text.caption.copyWith(textAlign: TextAlign.right) ) ) ]; @@ -43,7 +42,7 @@ class StockRow extends Component { height: kHeight, decoration: new BoxDecoration( border: new Border( - bottom: new BorderSide(color: Theme.of(this).dividerColor) + bottom: new BorderSide(color: Theme.of(context).dividerColor) ) ), child: new Row([ @@ -55,7 +54,7 @@ class StockRow extends Component { child: new Row( children, alignItems: FlexAlignItems.baseline, - textBaseline: DefaultTextStyle.of(this).textBaseline + textBaseline: DefaultTextStyle.of(context).textBaseline ) ) ]) diff --git a/examples/stocks/lib/stock_settings.dart b/examples/stocks/lib/stock_settings.dart index 19f31250b05..269afe19a61 100644 --- a/examples/stocks/lib/stock_settings.dart +++ b/examples/stocks/lib/stock_settings.dart @@ -10,42 +10,32 @@ typedef void SettingsUpdater({ }); class StockSettings extends StatefulComponent { + const StockSettings(this.navigator, this.optimism, this.backup, this.updater); - StockSettings(this.navigator, this.optimism, this.backup, this.updater); + final NavigatorState navigator; + final StockMode optimism; + final BackupMode backup; + final SettingsUpdater updater; - Navigator navigator; - StockMode optimism; - BackupMode backup; - SettingsUpdater updater; - - void syncConstructorArguments(StockSettings source) { - navigator = source.navigator; - optimism = source.optimism; - backup = source.backup; - updater = source.updater; - } + StockSettingsState createState() => new StockSettingsState(); +} +class StockSettingsState extends State { void _handleOptimismChanged(bool value) { - setState(() { - optimism = value ? StockMode.optimistic : StockMode.pessimistic; - }); - sendUpdates(); + sendUpdates(value ? StockMode.optimistic : StockMode.pessimistic, config.backup); } void _handleBackupChanged(bool value) { - setState(() { - backup = value ? BackupMode.enabled : BackupMode.disabled; - }); - sendUpdates(); + sendUpdates(config.optimism, value ? BackupMode.enabled : BackupMode.disabled); } void _confirmOptimismChange() { - switch (optimism) { + switch (config.optimism) { case StockMode.optimistic: _handleOptimismChanged(false); break; case StockMode.pessimistic: - showDialog(navigator, (navigator) { + showDialog(config.navigator, (NavigatorState navigator) { return new Dialog( title: new Text("Change mode?"), content: new Text("Optimistic mode means everything is awesome. Are you sure you can handle that?"), @@ -72,24 +62,25 @@ class StockSettings extends StatefulComponent { } } - void sendUpdates() { - if (updater != null) - updater( + void sendUpdates(StockMode optimism, BackupMode backup) { + if (config.updater != null) + config.updater( optimism: optimism, backup: backup ); } - Widget buildToolBar() { + Widget buildToolBar(BuildContext context) { return new ToolBar( left: new IconButton( icon: 'navigation/arrow_back', - onPressed: navigator.pop), + onPressed: config.navigator.pop + ), center: new Text('Settings') ); } - Widget buildSettingsPane() { + Widget buildSettingsPane(BuildContext context) { // TODO(ianh): Once we have the gesture API hooked up, fix https://github.com/domokit/mojo/issues/281 // (whereby tapping the widgets below causes both the widget and the menu item to fire their callbacks) return new Material( @@ -103,15 +94,21 @@ class StockSettings extends StatefulComponent { onPressed: () => _confirmOptimismChange(), child: new Row([ new Flexible(child: new Text('Everything is awesome')), - new Checkbox(value: optimism == StockMode.optimistic, onChanged: (_) => _confirmOptimismChange()), + new Checkbox( + value: config.optimism == StockMode.optimistic, + onChanged: (_) => _confirmOptimismChange() + ), ]) ), new DrawerItem( icon: 'action/backup', - onPressed: () { _handleBackupChanged(!(backup == BackupMode.enabled)); }, + onPressed: () { _handleBackupChanged(!(config.backup == BackupMode.enabled)); }, child: new Row([ new Flexible(child: new Text('Back up stock list to the cloud')), - new Switch(value: backup == BackupMode.enabled, onChanged: _handleBackupChanged), + new Switch( + value: config.backup == BackupMode.enabled, + onChanged: _handleBackupChanged + ), ]) ), ]) @@ -120,10 +117,10 @@ class StockSettings extends StatefulComponent { ); } - Widget build() { + Widget build(BuildContext context) { return new Scaffold( - toolbar: buildToolBar(), - body: buildSettingsPane() + toolbar: buildToolBar(context), + body: buildSettingsPane(context) ); } } diff --git a/packages/flutter/lib/src/fn3.dart b/packages/flutter/lib/src/fn3.dart index 5f49c093d52..e6a93bbcc70 100644 --- a/packages/flutter/lib/src/fn3.dart +++ b/packages/flutter/lib/src/fn3.dart @@ -5,6 +5,7 @@ library fn3; export 'fn3/animated_component.dart'; +export 'fn3/app.dart'; export 'fn3/basic.dart'; export 'fn3/binding.dart'; export 'fn3/button_state.dart'; diff --git a/packages/flutter/lib/src/fn3/app.dart b/packages/flutter/lib/src/fn3/app.dart new file mode 100644 index 00000000000..201af6e88b4 --- /dev/null +++ b/packages/flutter/lib/src/fn3/app.dart @@ -0,0 +1,83 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:sky' as sky; + +import 'package:sky/material.dart'; +import 'package:sky/painting.dart'; +import 'package:sky/src/fn3/basic.dart'; +import 'package:sky/src/fn3/binding.dart'; +import 'package:sky/src/fn3/framework.dart'; +import 'package:sky/src/fn3/navigator.dart'; +import 'package:sky/src/fn3/theme.dart'; +import 'package:sky/src/fn3/title.dart'; + +const TextStyle _errorTextStyle = const TextStyle( + color: const Color(0xD0FF0000), + fontFamily: 'monospace', + fontSize: 48.0, + fontWeight: FontWeight.w900, + textAlign: TextAlign.right, + decoration: underline, + decorationColor: const Color(0xFFFF00), + decorationStyle: TextDecorationStyle.double +); + +class App extends StatefulComponent { + App({ + Key key, + this.title, + this.theme, + this.routes + }): super(key: key); + + final String title; + final ThemeData theme; + final Map routes; + + AppState createState() => new AppState(); +} + +class AppState extends State { + + GlobalObjectKey _navigator; + + void initState(BuildContext context) { + super.initState(context); + _navigator = new GlobalObjectKey(this); + WidgetFlutterBinding.instance.addEventListener(_backHandler); + } + + void dispose() { + WidgetFlutterBinding.instance.removeEventListener(_backHandler); + super.dispose(); + } + + void _backHandler(sky.Event event) { + assert(mounted); + if (event.type == 'back') { + NavigatorState navigator = _navigator.currentState; + assert(navigator != null); + if (navigator.hasPreviousRoute) + navigator.pop(); + } + } + + Widget build(BuildContext context) { + return new Theme( + data: config.theme, + child: new DefaultTextStyle( + style: _errorTextStyle, + child: new Title( + title: config.title, + child: new Navigator( + key: _navigator, + routes: config.routes + ) + ) + ) + ); + } + +} \ No newline at end of file diff --git a/packages/flutter/lib/src/fn3/checkbox.dart b/packages/flutter/lib/src/fn3/checkbox.dart index 0b138c6f441..a22146fc316 100644 --- a/packages/flutter/lib/src/fn3/checkbox.dart +++ b/packages/flutter/lib/src/fn3/checkbox.dart @@ -44,10 +44,11 @@ class Checkbox extends StatelessComponent { ? _kLightUncheckedColor : _kDarkUncheckedColor; return new _CheckboxWrapper( - value: value, - onChanged: onChanged, - uncheckedColor: uncheckedColor, - accentColor: themeData.accentColor); + value: value, + onChanged: onChanged, + uncheckedColor: uncheckedColor, + accentColor: themeData.accentColor + ); } } @@ -55,9 +56,16 @@ class Checkbox extends StatelessComponent { // order to get an accent color from a Theme but Components do not know how to // host RenderObjects. class _CheckboxWrapper extends LeafRenderObjectWidget { - _CheckboxWrapper({Key key, this.value, this.onChanged, this.uncheckedColor, - this.accentColor}) - : super(key: key); + _CheckboxWrapper({ + Key key, + this.value, + this.onChanged, + this.uncheckedColor, + this.accentColor + }): super(key: key) { + assert(uncheckedColor != null); + assert(accentColor != null); + } final bool value; final ValueChanged onChanged; @@ -65,7 +73,11 @@ class _CheckboxWrapper extends LeafRenderObjectWidget { final Color accentColor; _RenderCheckbox createRenderObject() => new _RenderCheckbox( - value: value, uncheckedColor: uncheckedColor, onChanged: onChanged); + value: value, + accentColor: accentColor, + uncheckedColor: uncheckedColor, + onChanged: onChanged + ); void updateRenderObject(_RenderCheckbox renderObject, _CheckboxWrapper oldWidget) { renderObject.value = value; @@ -76,25 +88,38 @@ class _CheckboxWrapper extends LeafRenderObjectWidget { } class _RenderCheckbox extends RenderToggleable { - _RenderCheckbox({bool value, Color uncheckedColor, ValueChanged onChanged}) - : _uncheckedColor = uncheckedColor, - super( - value: value, - onChanged: onChanged, - size: new Size(_kEdgeSize, _kEdgeSize)) {} + _RenderCheckbox({ + bool value, + Color uncheckedColor, + Color accentColor, + ValueChanged onChanged + }): _uncheckedColor = uncheckedColor, + _accentColor = accentColor, + super( + value: value, + onChanged: onChanged, + size: new Size(_kEdgeSize, _kEdgeSize) + ) { + assert(uncheckedColor != null); + assert(accentColor != null); + } Color _uncheckedColor; Color get uncheckedColor => _uncheckedColor; void set uncheckedColor(Color value) { - if (value == _uncheckedColor) return; + assert(value != null); + if (value == _uncheckedColor) + return; _uncheckedColor = value; markNeedsPaint(); } Color _accentColor; void set accentColor(Color value) { - if (value == _accentColor) return; + assert(value != null); + if (value == _accentColor) + return; _accentColor = value; markNeedsPaint(); } diff --git a/packages/flutter/lib/src/fn3/dialog.dart b/packages/flutter/lib/src/fn3/dialog.dart index 5f5438b8b6e..02aef0d459e 100644 --- a/packages/flutter/lib/src/fn3/dialog.dart +++ b/packages/flutter/lib/src/fn3/dialog.dart @@ -16,7 +16,7 @@ import 'package:sky/src/fn3/scrollable.dart'; import 'package:sky/src/fn3/theme.dart'; import 'package:sky/src/fn3/transitions.dart'; -typedef Widget DialogBuilder(Navigator navigator); +typedef Dialog DialogBuilder(NavigatorState navigator); /// A material design dialog /// @@ -132,7 +132,7 @@ class Dialog extends StatelessComponent { const Duration _kTransitionDuration = const Duration(milliseconds: 150); -class DialogRoute extends RouteBase { +class DialogRoute extends Route { DialogRoute({ this.completer, this.builder }); final Completer completer; diff --git a/packages/flutter/lib/src/fn3/navigator.dart b/packages/flutter/lib/src/fn3/navigator.dart index 834e0da0da4..b9e7b37de0d 100644 --- a/packages/flutter/lib/src/fn3/navigator.dart +++ b/packages/flutter/lib/src/fn3/navigator.dart @@ -8,11 +8,11 @@ import 'package:sky/src/fn3/focus.dart'; import 'package:sky/src/fn3/framework.dart'; import 'package:sky/src/fn3/transitions.dart'; -typedef Widget RouteBuilder(NavigatorState navigator, RouteBase route); +typedef Widget RouteBuilder(NavigatorState navigator, Route route); typedef void NotificationCallback(); -abstract class RouteBase { +abstract class Route { AnimationPerformance _performance; NotificationCallback onDismissed; NotificationCallback onCompleted; @@ -57,10 +57,10 @@ abstract class RouteBase { const Duration _kTransitionDuration = const Duration(milliseconds: 150); const Point _kTransitionStartPoint = const Point(0.0, 75.0); -class Route extends RouteBase { - Route({ this.name, this.builder }); - final String name; +class PageRoute extends Route { + PageRoute(this.builder); + final RouteBuilder builder; bool get isOpaque => true; @@ -81,16 +81,16 @@ class Route extends RouteBase { ) ); } - - String toString() => '$runtimeType(name="$name")'; } -class RouteState extends RouteBase { - RouteState({ this.callback, this.route, this.owner }); +typedef void RouteStateCallback(RouteState route); - Function callback; - RouteBase route; +class RouteState extends Route { + RouteState({ this.route, this.owner, this.callback }); + + Route route; State owner; + RouteStateCallback callback; bool get isOpaque => false; @@ -105,99 +105,81 @@ class RouteState extends RouteBase { Widget build(Key key, NavigatorState navigator, WatchableAnimationPerformance performance) => null; } -class NavigatorHistory { - - NavigatorHistory(List routes) { - for (Route route in routes) { - if (route.name != null) - namedRoutes[route.name] = route; - } - recents.add(routes[0]); - } - - List recents = new List(); - int index = 0; - Map namedRoutes = new Map(); - - RouteBase get currentRoute => recents[index]; - bool hasPrevious() => index > 0; - - void pushNamed(String name) { - Route route = namedRoutes[name]; - assert(route != null); - push(route); - } - - void push(RouteBase route) { - assert(!_debugCurrentlyHaveRoute(route)); - recents.insert(index + 1, route); - index++; - } - - void pop([dynamic result]) { - if (index > 0) { - RouteBase route = recents[index]; - route.popState(result); - index--; - } - } - - bool _debugCurrentlyHaveRoute(RouteBase route) { - return recents.any((candidate) => candidate == route); - } -} - class Navigator extends StatefulComponent { - Navigator(this.history, { Key key }) : super(key: key); + Navigator({ this.routes, Key key }) : super(key: key) { + // To use a navigator, you must at a minimum define the route with the name '/'. + assert(routes.containsKey('/')); + } - final NavigatorHistory history; + final Map routes; NavigatorState createState() => new NavigatorState(); } class NavigatorState extends State { - RouteBase get currentRoute => config.history.currentRoute; + + List _history = new List(); + int _currentPosition = 0; + + Route get currentRoute => _history[_currentPosition]; + bool get hasPreviousRoute => _history.length > 1; + + void initState(BuildContext context) { + super.initState(context); + PageRoute route = new PageRoute(config.routes['/']); + assert(route != null); + _history.add(route); + } void pushState(State owner, Function callback) { - RouteBase route = new RouteState( + push(new RouteState( + route: currentRoute, owner: owner, - callback: callback, - route: currentRoute - ); - push(route); + callback: callback + )); } void pushNamed(String name) { - setState(() { - config.history.pushNamed(name); - }); + PageRoute route = new PageRoute(config.routes[name]); + assert(route != null); + push(route); } - void push(RouteBase route) { + void push(Route route) { + assert(!_debugCurrentlyHaveRoute(route)); + _history.insert(_currentPosition + 1, route); setState(() { - config.history.push(route); + _currentPosition += 1; }); } void pop([dynamic result]) { - setState(() { - config.history.pop(result); - }); + if (_currentPosition > 0) { + Route route = _history[_currentPosition]; + route.popState(result); + setState(() { + _currentPosition -= 1; + }); + } + } + + bool _debugCurrentlyHaveRoute(Route route) { + return _history.any((candidate) => candidate == route); } Widget build(BuildContext context) { List visibleRoutes = new List(); - for (int i = config.history.recents.length-1; i >= 0; i -= 1) { - RouteBase route = config.history.recents[i]; + for (int i = _history.length-1; i >= 0; i -= 1) { + Route route = _history[i]; if (!route.hasContent) continue; WatchableAnimationPerformance performance = route.ensurePerformance( - direction: (i <= config.history.index) ? Direction.forward : Direction.reverse + direction: (i <= _currentPosition) ? Direction.forward : Direction.reverse ); route.onDismissed = () { setState(() { - assert(config.history.recents.contains(route)); - config.history.recents.remove(route); + assert(_history.contains(route)); + _history.remove(route); }); }; Key key = new ObjectKey(route); diff --git a/packages/flutter/lib/src/fn3/popup_menu.dart b/packages/flutter/lib/src/fn3/popup_menu.dart index c34d9b21349..b748cf59c97 100644 --- a/packages/flutter/lib/src/fn3/popup_menu.dart +++ b/packages/flutter/lib/src/fn3/popup_menu.dart @@ -6,8 +6,8 @@ import 'dart:async'; import 'dart:sky' as sky; import 'package:sky/animation.dart'; -import 'package:sky/painting.dart'; import 'package:sky/material.dart'; +import 'package:sky/painting.dart'; import 'package:sky/src/fn3/basic.dart'; import 'package:sky/src/fn3/focus.dart'; import 'package:sky/src/fn3/framework.dart'; @@ -15,6 +15,7 @@ import 'package:sky/src/fn3/gesture_detector.dart'; import 'package:sky/src/fn3/navigator.dart'; import 'package:sky/src/fn3/popup_menu_item.dart'; import 'package:sky/src/fn3/scrollable.dart'; +import 'package:sky/src/fn3/theme.dart'; import 'package:sky/src/fn3/transitions.dart'; const Duration _kMenuDuration = const Duration(milliseconds: 300); @@ -26,6 +27,8 @@ const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep; const double _kMenuHorizontalPadding = 16.0; const double _kMenuVerticalPadding = 8.0; +typedef List PopupMenuItemsBuilder(NavigatorState navigator); + class PopupMenu extends StatefulComponent { PopupMenu({ Key key, @@ -49,25 +52,10 @@ class PopupMenu extends StatefulComponent { class PopupMenuState extends State { void initState(BuildContext context) { super.initState(context); - _updateBoxPainter(); config.performance.addListener(_performanceChanged); } - BoxPainter _painter; - - void _updateBoxPainter() { - _painter = new BoxPainter( - new BoxDecoration( - backgroundColor: Colors.grey[50], - borderRadius: 2.0, - boxShadow: shadows[config.level] - ) - ); - } - void didUpdateConfig(PopupMenu oldConfig) { - if (config.level != config.level) - _updateBoxPainter(); if (config.performance != oldConfig.performance) { oldConfig.performance.removeListener(_performanceChanged); config.performance.addListener(_performanceChanged); @@ -85,7 +73,19 @@ class PopupMenuState extends State { }); } + BoxPainter _painter; + + void _updateBoxPainter(BoxDecoration decoration) { + if (_painter == null || _painter.decoration != decoration) + _painter = new BoxPainter(decoration); + } + Widget build(BuildContext context) { + _updateBoxPainter(new BoxDecoration( + backgroundColor: Theme.of(context).canvasColor, + borderRadius: 2.0, + boxShadow: shadows[config.level] + )); double unit = 1.0 / (config.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 < config.items.length; ++i) { @@ -151,7 +151,7 @@ class MenuPosition { final double left; } -class MenuRoute extends RouteBase { +class MenuRoute extends Route { MenuRoute({ this.completer, this.position, this.builder, this.level }); final Completer completer; @@ -194,8 +194,6 @@ class MenuRoute extends RouteBase { } } -typedef List PopupMenuItemsBuilder(NavigatorState navigator); - Future showMenu({ NavigatorState navigator, MenuPosition position, PopupMenuItemsBuilder builder, int level: 4 }) { Completer completer = new Completer(); navigator.push(new MenuRoute( diff --git a/packages/flutter/lib/src/material/typography.dart b/packages/flutter/lib/src/material/typography.dart index 8be679e9a51..37a26268522 100644 --- a/packages/flutter/lib/src/material/typography.dart +++ b/packages/flutter/lib/src/material/typography.dart @@ -63,6 +63,7 @@ class Typography { // TODO(abarth): Maybe this should be hard-coded in Scaffold? static const String typeface = 'font-family: sans-serif'; + // TODO(ianh): Remove this when we remove fn2, now that it's hard-coded in App. static const TextStyle error = const TextStyle( color: const Color(0xD0FF0000), fontFamily: 'monospace', diff --git a/packages/flutter/lib/src/rendering/sky_binding.dart b/packages/flutter/lib/src/rendering/sky_binding.dart index 02a89bd6c5f..bab387b3f06 100644 --- a/packages/flutter/lib/src/rendering/sky_binding.dart +++ b/packages/flutter/lib/src/rendering/sky_binding.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// TODO(ianh): rename this file 'binding.dart' + import 'dart:sky' as sky; import 'package:sky/animation.dart'; @@ -39,6 +41,7 @@ class BindingHitTestEntry extends HitTestEntry { } /// The glue between the render tree and the sky engine +// TODO(ianh): rename this class FlutterBinding class SkyBinding extends HitTestTarget { SkyBinding({ RenderBox root: null, RenderView renderViewOverride }) { diff --git a/packages/unit/test/widget/navigator_test.dart b/packages/unit/test/widget/navigator_test.dart index 7532178791e..7b6ab8925f8 100644 --- a/packages/unit/test/widget/navigator_test.dart +++ b/packages/unit/test/widget/navigator_test.dart @@ -50,18 +50,12 @@ void main() { test('Can navigator navigate to and from a stateful component', () { WidgetTester tester = new WidgetTester(); - final NavigatorHistory routes = new NavigatorHistory([ - new Route( - name: '/', - builder: (navigator, route) => new FirstComponent(navigator) - ), - new Route( - name: '/second', - builder: (navigator, route) => new SecondComponent(navigator) - ) - ]); + final Map routes = { + '/': (navigator, route) => new FirstComponent(navigator), + '/second': (navigator, route) => new SecondComponent(navigator), + }; - tester.pumpFrame(new Navigator(routes)); + tester.pumpFrame(new Navigator(routes: routes)); expect(tester.findText('X'), isNotNull); expect(tester.findText('Y'), isNull);