// 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) { setState(() { stock.percentChange = 100.0 * (1.0 / stock.lastSale); stock.lastSale += 1.0; }); }, onOpen: (Stock stock) { 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() ); } }