
Add type annotations in many places. Fix some identifiers to have more lint-satisfying names. Make all operator==s consistent in style. Reorder some functions for consistency. Make ParentData no longer dynamic, and fix all the code around that.
273 lines
7.9 KiB
Dart
273 lines
7.9 KiB
Dart
// 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.
|
|
|
|
part of stocks;
|
|
|
|
typedef void ModeUpdater(StockMode mode);
|
|
|
|
class StockHome extends StatefulComponent {
|
|
StockHome(this.navigator, this.stocks, this.symbols, this.stockMode, this.modeUpdater);
|
|
|
|
final NavigatorState navigator;
|
|
final Map<String, Stock> stocks;
|
|
final List<String> symbols;
|
|
final StockMode stockMode;
|
|
final ModeUpdater modeUpdater;
|
|
|
|
StockHomeState createState() => new StockHomeState();
|
|
}
|
|
|
|
class StockHomeState extends State<StockHome> {
|
|
|
|
final GlobalKey<PlaceholderState> _snackBarPlaceholderKey = new GlobalKey<PlaceholderState>();
|
|
bool _isSearching = false;
|
|
String _searchQuery;
|
|
|
|
void _handleSearchBegin() {
|
|
config.navigator.pushState(this, (_) {
|
|
setState(() {
|
|
_isSearching = false;
|
|
_searchQuery = null;
|
|
});
|
|
});
|
|
setState(() {
|
|
_isSearching = true;
|
|
});
|
|
}
|
|
|
|
void _handleSearchEnd() {
|
|
assert(() {
|
|
final StateRoute currentRoute = config.navigator.currentRoute;
|
|
assert(currentRoute.owner == this);
|
|
});
|
|
config.navigator.pop();
|
|
setState(() {
|
|
_isSearching = false;
|
|
_searchQuery = null;
|
|
});
|
|
}
|
|
|
|
void _handleSearchQueryChanged(String query) {
|
|
setState(() {
|
|
_searchQuery = query;
|
|
});
|
|
}
|
|
|
|
bool _autorefresh = false;
|
|
void _handleAutorefreshChanged(bool value) {
|
|
setState(() {
|
|
_autorefresh = value;
|
|
});
|
|
}
|
|
|
|
void _handleStockModeChange(StockMode value) {
|
|
if (config.modeUpdater != null)
|
|
config.modeUpdater(value);
|
|
}
|
|
|
|
void _handleMenuShow() {
|
|
showStockMenu(config.navigator,
|
|
autorefresh: _autorefresh,
|
|
onAutorefreshChanged: _handleAutorefreshChanged
|
|
);
|
|
}
|
|
|
|
void _showDrawer() {
|
|
showDrawer(
|
|
navigator: config.navigator,
|
|
child: new Block(<Widget>[
|
|
new DrawerHeader(child: new Text('Stocks')),
|
|
new DrawerItem(
|
|
icon: 'action/assessment',
|
|
selected: true,
|
|
child: new Text('Stock List')
|
|
),
|
|
new DrawerItem(
|
|
icon: 'action/account_balance',
|
|
onPressed: () {
|
|
showDialog(config.navigator, (NavigatorState navigator) {
|
|
return new Dialog(
|
|
title: new Text('Not Implemented'),
|
|
content: new Text('This feature has not yet been implemented.'),
|
|
actions: <Widget>[
|
|
new FlatButton(
|
|
child: new Text('USE IT'),
|
|
enabled: false,
|
|
onPressed: () {
|
|
navigator.pop(false);
|
|
}
|
|
),
|
|
new FlatButton(
|
|
child: new Text('OH WELL'),
|
|
onPressed: () {
|
|
navigator.pop(false);
|
|
}
|
|
),
|
|
]
|
|
);
|
|
});
|
|
},
|
|
child: new Text('Account Balance')
|
|
),
|
|
new DrawerItem(
|
|
icon: 'device/dvr',
|
|
onPressed: () { debugDumpApp(); },
|
|
child: new Text('Dump App to Console')
|
|
),
|
|
new DrawerDivider(),
|
|
new DrawerItem(
|
|
icon: 'action/thumb_up',
|
|
onPressed: () => _handleStockModeChange(StockMode.optimistic),
|
|
child: new Row(<Widget>[
|
|
new Flexible(child: new Text('Optimistic')),
|
|
new Radio(value: StockMode.optimistic, groupValue: config.stockMode, onChanged: _handleStockModeChange)
|
|
])
|
|
),
|
|
new DrawerItem(
|
|
icon: 'action/thumb_down',
|
|
onPressed: () => _handleStockModeChange(StockMode.pessimistic),
|
|
child: new Row(<Widget>[
|
|
new Flexible(child: new Text('Pessimistic')),
|
|
new Radio(value: StockMode.pessimistic, groupValue: config.stockMode, onChanged: _handleStockModeChange)
|
|
])
|
|
),
|
|
new DrawerDivider(),
|
|
new DrawerItem(
|
|
icon: 'action/settings',
|
|
onPressed: _handleShowSettings,
|
|
child: new Text('Settings')),
|
|
new DrawerItem(
|
|
icon: 'action/help',
|
|
child: new Text('Help & Feedback'))
|
|
])
|
|
);
|
|
}
|
|
|
|
void _handleShowSettings() {
|
|
config.navigator.pop();
|
|
config.navigator.pushNamed('/settings');
|
|
}
|
|
|
|
Widget buildToolBar() {
|
|
return new ToolBar(
|
|
level: 0,
|
|
left: new IconButton(
|
|
icon: "navigation/menu",
|
|
onPressed: _showDrawer
|
|
),
|
|
center: new Text('Stocks'),
|
|
right: <Widget>[
|
|
new IconButton(
|
|
icon: "action/search",
|
|
onPressed: _handleSearchBegin
|
|
),
|
|
new IconButton(
|
|
icon: "navigation/more_vert",
|
|
onPressed: _handleMenuShow
|
|
)
|
|
]
|
|
);
|
|
}
|
|
|
|
int selectedTabIndex = 0;
|
|
|
|
Iterable<Stock> _getStockList(Iterable<String> symbols) {
|
|
return symbols.map((String symbol) => config.stocks[symbol]);
|
|
}
|
|
|
|
Iterable<Stock> _filterBySearchQuery(Iterable<Stock> stocks) {
|
|
if (_searchQuery == null)
|
|
return stocks;
|
|
RegExp regexp = new RegExp(_searchQuery, caseSensitive: false);
|
|
return stocks.where((Stock stock) => stock.symbol.contains(regexp));
|
|
}
|
|
|
|
Widget buildStockList(BuildContext context, Iterable<Stock> stocks) {
|
|
return new StockList(
|
|
stocks: stocks.toList(),
|
|
onAction: (Stock stock, GlobalKey row, GlobalKey arrowKey, GlobalKey symbolKey, GlobalKey priceKey) {
|
|
setState(() {
|
|
stock.percentChange = 100.0 * (1.0 / stock.lastSale);
|
|
stock.lastSale += 1.0;
|
|
});
|
|
},
|
|
onOpen: (Stock stock, GlobalKey row, GlobalKey arrowKey, GlobalKey symbolKey, GlobalKey priceKey) {
|
|
config.navigator.pushNamed('/stock/${stock.symbol}');
|
|
}
|
|
);
|
|
}
|
|
|
|
static const List<String> portfolioSymbols = const <String>["AAPL","FIZZ", "FIVE", "FLAT", "ZINC", "ZNGA"];
|
|
|
|
Widget buildTabNavigator() {
|
|
return new TabNavigator(
|
|
views: <TabNavigatorView>[
|
|
new TabNavigatorView(
|
|
label: const TabLabel(text: 'MARKET'),
|
|
builder: (BuildContext context) => buildStockList(context, _filterBySearchQuery(_getStockList(config.symbols)).toList())
|
|
),
|
|
new TabNavigatorView(
|
|
label: const TabLabel(text: 'PORTFOLIO'),
|
|
builder: (BuildContext context) => buildStockList(context, _filterBySearchQuery(_getStockList(portfolioSymbols)).toList())
|
|
)
|
|
],
|
|
selectedIndex: selectedTabIndex,
|
|
onChanged: (int tabIndex) {
|
|
setState(() { selectedTabIndex = tabIndex; } );
|
|
}
|
|
);
|
|
}
|
|
|
|
static GlobalKey searchFieldKey = new GlobalKey();
|
|
|
|
// TODO(abarth): Should we factor this into a SearchBar in the framework?
|
|
Widget buildSearchBar() {
|
|
return new ToolBar(
|
|
left: new IconButton(
|
|
icon: "navigation/arrow_back",
|
|
colorFilter: new ui.ColorFilter.mode(Theme.of(context).accentColor, ui.TransferMode.srcATop),
|
|
onPressed: _handleSearchEnd
|
|
),
|
|
center: new Input(
|
|
key: searchFieldKey,
|
|
placeholder: 'Search stocks',
|
|
onChanged: _handleSearchQueryChanged
|
|
),
|
|
backgroundColor: Theme.of(context).canvasColor
|
|
);
|
|
}
|
|
|
|
void _handleUndo() {
|
|
config.navigator.pop();
|
|
}
|
|
|
|
void _handleStockPurchased() {
|
|
showSnackBar(
|
|
navigator: config.navigator,
|
|
placeholderKey: _snackBarPlaceholderKey,
|
|
content: new Text("Stock purchased!"),
|
|
actions: <SnackBarAction>[
|
|
new SnackBarAction(label: "UNDO", onPressed: _handleUndo)
|
|
]
|
|
);
|
|
}
|
|
|
|
Widget buildFloatingActionButton() {
|
|
return new FloatingActionButton(
|
|
child: new Icon(type: 'content/add', size: 24),
|
|
backgroundColor: Colors.redAccent[200],
|
|
onPressed: _handleStockPurchased
|
|
);
|
|
}
|
|
|
|
Widget build(BuildContext context) {
|
|
return new Scaffold(
|
|
toolBar: _isSearching ? buildSearchBar() : buildToolBar(),
|
|
body: buildTabNavigator(),
|
|
snackBar: new Placeholder(key: _snackBarPlaceholderKey),
|
|
floatingActionButton: buildFloatingActionButton()
|
|
);
|
|
}
|
|
}
|