
Identify specific parts of a Stock row with a Global Key that can be regenerated later, and pass that key back to event handlers so they can use them to do the transition.
284 lines
8.0 KiB
Dart
284 lines
8.0 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);
|
|
|
|
const Duration _kSnackbarSlideDuration = const Duration(milliseconds: 200);
|
|
|
|
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> {
|
|
|
|
bool _isSearching = false;
|
|
String _searchQuery;
|
|
|
|
AnimationStatus _snackBarStatus = AnimationStatus.dismissed;
|
|
bool _isSnackBarShowing = false;
|
|
|
|
void _handleSearchBegin() {
|
|
config.navigator.pushState(this, (_) {
|
|
setState(() {
|
|
_isSearching = false;
|
|
_searchQuery = null;
|
|
});
|
|
});
|
|
setState(() {
|
|
_isSearching = true;
|
|
});
|
|
}
|
|
|
|
void _handleSearchEnd() {
|
|
assert(config.navigator.currentRoute is StateRoute);
|
|
assert((config.navigator.currentRoute as StateRoute).owner == this); // TODO(ianh): remove cast once analyzer is cleverer
|
|
config.navigator.pop();
|
|
setState(() {
|
|
_isSearching = false;
|
|
_searchQuery = null;
|
|
});
|
|
}
|
|
|
|
void _handleSearchQueryChanged(String query) {
|
|
setState(() {
|
|
_searchQuery = query;
|
|
});
|
|
}
|
|
|
|
bool _drawerShowing = false;
|
|
AnimationStatus _drawerStatus = AnimationStatus.dismissed;
|
|
|
|
void _handleOpenDrawer() {
|
|
setState(() {
|
|
_drawerShowing = true;
|
|
_drawerStatus = AnimationStatus.forward;
|
|
});
|
|
}
|
|
|
|
void _handleDrawerDismissed() {
|
|
setState(() {
|
|
_drawerStatus = AnimationStatus.dismissed;
|
|
});
|
|
}
|
|
|
|
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
|
|
);
|
|
}
|
|
|
|
Drawer buildDrawer() {
|
|
if (_drawerStatus == AnimationStatus.dismissed)
|
|
return null;
|
|
assert(_drawerShowing); // TODO(mpcomplete): this is always true
|
|
return new Drawer(
|
|
level: 3,
|
|
showing: _drawerShowing,
|
|
onDismissed: _handleDrawerDismissed,
|
|
navigator: config.navigator,
|
|
children: [
|
|
new DrawerHeader(child: new Text('Stocks')),
|
|
new DrawerItem(
|
|
icon: 'action/assessment',
|
|
selected: true,
|
|
child: new Text('Stock List')
|
|
),
|
|
new DrawerItem(
|
|
icon: 'action/account_balance',
|
|
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([
|
|
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([
|
|
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(
|
|
left: new IconButton(
|
|
icon: "navigation/menu",
|
|
onPressed: _handleOpenDrawer
|
|
),
|
|
center: new Text('Stocks'),
|
|
right: [
|
|
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((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.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: (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",
|
|
color: Theme.of(context).accentColor,
|
|
onPressed: _handleSearchEnd
|
|
),
|
|
center: new Input(
|
|
key: searchFieldKey,
|
|
placeholder: 'Search stocks',
|
|
onChanged: _handleSearchQueryChanged
|
|
),
|
|
backgroundColor: Theme.of(context).canvasColor
|
|
);
|
|
}
|
|
|
|
void _handleUndo() {
|
|
setState(() {
|
|
_isSnackBarShowing = false;
|
|
});
|
|
}
|
|
|
|
GlobalKey snackBarKey = new GlobalKey(label: 'snackbar');
|
|
Widget buildSnackBar() {
|
|
if (_snackBarStatus == AnimationStatus.dismissed)
|
|
return null;
|
|
return new SnackBar(
|
|
showing: _isSnackBarShowing,
|
|
content: new Text("Stock purchased!"),
|
|
actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)],
|
|
onDismissed: () { setState(() { _snackBarStatus = AnimationStatus.dismissed; }); }
|
|
);
|
|
}
|
|
|
|
void _handleStockPurchased() {
|
|
setState(() {
|
|
_isSnackBarShowing = true;
|
|
_snackBarStatus = AnimationStatus.forward;
|
|
});
|
|
}
|
|
|
|
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: buildSnackBar(),
|
|
floatingActionButton: buildFloatingActionButton(),
|
|
drawer: buildDrawer()
|
|
);
|
|
}
|
|
}
|