Port stocks to fn3 and introduce an App component.
diff --git a/examples/stocks/lib/main.dart b/examples/stocks/lib/main.dart
index a70739b..aaacab0 100644
--- a/examples/stocks/lib/main.dart
+++ b/examples/stocks/lib/main.dart
@@ -10,7 +10,8 @@
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_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<StocksApp> {
final List<Stock> _stocks = [];
- void didMount() {
- super.didMount();
+
+ void initState(BuildContext context) {
+ super.initState(context);
new StockDataFetcher((StockData data) {
setState(() {
data.appendTo(_stocks);
@@ -76,32 +40,47 @@
});
}
- 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: <String, RouteBuilder>{
+ '/': (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 394eda3..a1f56b5 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 @@
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 @@
margin: const EdgeDims.symmetric(horizontal: 5.0)
);
}
-
}
diff --git a/examples/stocks/lib/stock_home.dart b/examples/stocks/lib/stock_home.dart
index c85dd54..ee47f6e 100644
--- a/examples/stocks/lib/stock_home.dart
+++ b/examples/stocks/lib/stock_home.dart
@@ -9,20 +9,17 @@
const Duration _kSnackbarSlideDuration = const Duration(milliseconds: 200);
class StockHome extends StatefulComponent {
-
StockHome(this.navigator, this.stocks, this.stockMode, this.modeUpdater);
- Navigator navigator;
- List<Stock> stocks;
- StockMode stockMode;
- ModeUpdater modeUpdater;
+ final NavigatorState navigator;
+ final List<Stock> 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<StockHome> {
bool _isSearching = false;
String _searchQuery;
@@ -31,7 +28,7 @@
bool _isSnackBarShowing = false;
void _handleSearchBegin() {
- navigator.pushState(this, (_) {
+ config.navigator.pushState(this, (_) {
setState(() {
_isSearching = false;
_searchQuery = null;
@@ -43,9 +40,9 @@
}
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 @@
}
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 @@
level: 3,
showing: _drawerShowing,
onDismissed: _handleDrawerDismissed,
- navigator: navigator,
+ navigator: config.navigator,
children: [
new DrawerHeader(child: new Text('Stocks')),
new DrawerItem(
@@ -122,7 +116,7 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
placeholder: 'Search stocks',
onChanged: _handleSearchQueryChanged
),
- backgroundColor: Theme.of(this).canvasColor
+ backgroundColor: Theme.of(context).canvasColor
);
}
@@ -255,17 +252,14 @@
}
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 42ea384..a65d735 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<Stock> stocks;
- Widget build() {
+ Widget build(BuildContext context) {
return new Material(
type: MaterialType.canvas,
child: new ScrollableList<Stock>(
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 a61073a..f9c3278 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 <PopupMenuItem>[
- 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 @@
),
];
}
- );
+ )) {
+ 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 f2f4e68..4ebe859 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 @@
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 @@
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 @@
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 19f3125..269afe1 100644
--- a/examples/stocks/lib/stock_settings.dart
+++ b/examples/stocks/lib/stock_settings.dart
@@ -10,42 +10,32 @@
});
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;
+ StockSettingsState createState() => new StockSettingsState();
+}
- void syncConstructorArguments(StockSettings source) {
- navigator = source.navigator;
- optimism = source.optimism;
- backup = source.backup;
- updater = source.updater;
- }
-
+class StockSettingsState extends State<StockSettings> {
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 @@
}
}
- 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 @@
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 @@
);
}
- 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 5f49c09..e6a93bb 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 0000000..201af6e
--- /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<String, RouteBuilder> routes;
+
+ AppState createState() => new AppState();
+}
+
+class AppState extends State<App> {
+
+ 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 0b138c6..a22146f 100644
--- a/packages/flutter/lib/src/fn3/checkbox.dart
+++ b/packages/flutter/lib/src/fn3/checkbox.dart
@@ -44,10 +44,11 @@
? _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 @@
// 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 @@
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 _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 5f5438b..02aef0d 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/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 @@
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 834e0da..b9e7b37 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/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 @@
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 @@
)
);
}
-
- 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 @@
Widget build(Key key, NavigatorState navigator, WatchableAnimationPerformance performance) => null;
}
-class NavigatorHistory {
-
- NavigatorHistory(List<Route> routes) {
- for (Route route in routes) {
- if (route.name != null)
- namedRoutes[route.name] = route;
- }
- recents.add(routes[0]);
- }
-
- List<RouteBase> recents = new List<RouteBase>();
- int index = 0;
- Map<String, RouteBase> namedRoutes = new Map<String, RouteBase>();
-
- 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<String, RouteBuilder> routes;
NavigatorState createState() => new NavigatorState();
}
class NavigatorState extends State<Navigator> {
- RouteBase get currentRoute => config.history.currentRoute;
+
+ List<Route> _history = new List<Route>();
+ 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<Widget> visibleRoutes = new List<Widget>();
- 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 c34d9b2..b748cf5 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: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/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 _kMenuHorizontalPadding = 16.0;
const double _kMenuVerticalPadding = 8.0;
+typedef List<PopupMenuItem> PopupMenuItemsBuilder(NavigatorState navigator);
+
class PopupMenu extends StatefulComponent {
PopupMenu({
Key key,
@@ -49,25 +52,10 @@
class PopupMenuState extends State<PopupMenu> {
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 @@
});
}
+ 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<Widget> children = [];
for (int i = 0; i < config.items.length; ++i) {
@@ -151,7 +151,7 @@
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 @@
}
}
-typedef List<PopupMenuItem> 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 8be679e..37a2626 100644
--- a/packages/flutter/lib/src/material/typography.dart
+++ b/packages/flutter/lib/src/material/typography.dart
@@ -63,6 +63,7 @@
// 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 02a89bd..bab387b 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 @@
}
/// 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 7532178..7b6ab89 100644
--- a/packages/unit/test/widget/navigator_test.dart
+++ b/packages/unit/test/widget/navigator_test.dart
@@ -50,18 +50,12 @@
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<String, RouteBuilder> routes = <String, RouteBuilder>{
+ '/': (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);