| // 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:collection'; |
| |
| import 'package:flutter/material.dart'; |
| import 'package:flutter/rendering.dart' show debugDumpRenderTree, debugDumpLayerTree, debugDumpSemanticsTree; |
| import 'package:flutter/scheduler.dart' show timeDilation; |
| import 'stock_data.dart'; |
| import 'stock_list.dart'; |
| import 'stock_strings.dart'; |
| import 'stock_symbol_viewer.dart'; |
| import 'stock_types.dart'; |
| |
| typedef void ModeUpdater(StockMode mode); |
| |
| enum _StockMenuItem { autorefresh, refresh, speedUp, speedDown } |
| enum StockHomeTab { market, portfolio } |
| |
| class _NotImplementedDialog extends StatelessComponent { |
| Widget build(BuildContext context) { |
| return new Dialog( |
| title: new Text('Not Implemented'), |
| content: new Text('This feature has not yet been implemented.'), |
| actions: <Widget>[ |
| new FlatButton( |
| child: new Row( |
| children: <Widget>[ |
| new Icon( |
| icon: Icons.dvr, |
| size: 18.0 |
| ), |
| new Container( |
| width: 8.0 |
| ), |
| new Text('DUMP APP TO CONSOLE'), |
| ] |
| ), |
| onPressed: () { debugDumpApp(); } |
| ), |
| new FlatButton( |
| child: new Text('OH WELL'), |
| onPressed: () { |
| Navigator.pop(context, false); |
| } |
| ) |
| ] |
| ); |
| } |
| } |
| |
| class StockHome extends StatefulComponent { |
| const StockHome(this.stocks, this.symbols, this.configuration, this.updater); |
| |
| final Map<String, Stock> stocks; |
| final List<String> symbols; |
| final StockConfiguration configuration; |
| final ValueChanged<StockConfiguration> updater; |
| |
| StockHomeState createState() => new StockHomeState(); |
| } |
| |
| class StockHomeState extends State<StockHome> { |
| |
| final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); |
| bool _isSearching = false; |
| InputValue _searchQuery = InputValue.empty; |
| bool _autorefresh = false; |
| |
| void _handleSearchBegin() { |
| ModalRoute.of(context).addLocalHistoryEntry(new LocalHistoryEntry( |
| onRemove: () { |
| setState(() { |
| _isSearching = false; |
| _searchQuery = InputValue.empty; |
| }); |
| } |
| )); |
| setState(() { |
| _isSearching = true; |
| }); |
| } |
| |
| void _handleSearchEnd() { |
| Navigator.pop(context); |
| } |
| |
| void _handleSearchQueryChanged(InputValue query) { |
| setState(() { |
| _searchQuery = query; |
| }); |
| } |
| |
| void _handleStockModeChange(StockMode value) { |
| if (config.updater != null) |
| config.updater(config.configuration.copyWith(stockMode: value)); |
| } |
| |
| void _handleStockMenu(BuildContext context, _StockMenuItem value) { |
| switch(value) { |
| case _StockMenuItem.autorefresh: |
| setState(() { |
| _autorefresh = !_autorefresh; |
| }); |
| break; |
| case _StockMenuItem.refresh: |
| showDialog( |
| context: context, |
| child: new _NotImplementedDialog() |
| ); |
| break; |
| case _StockMenuItem.speedUp: |
| timeDilation /= 5.0; |
| break; |
| case _StockMenuItem.speedDown: |
| timeDilation *= 5.0; |
| break; |
| } |
| } |
| |
| Widget _buildDrawer(BuildContext context) { |
| return new Drawer( |
| child: new Block(children: <Widget>[ |
| new DrawerHeader(child: new Text('Stocks')), |
| new DrawerItem( |
| icon: Icons.assessment, |
| selected: true, |
| child: new Text('Stock List') |
| ), |
| new DrawerItem( |
| icon: Icons.account_balance, |
| onPressed: () { |
| showDialog( |
| context: context, |
| child: new Dialog( |
| title: new Text('Not Implemented'), |
| content: new Text('This feature has not yet been implemented.'), |
| actions: <Widget>[ |
| new FlatButton( |
| onPressed: () { |
| Navigator.pop(context, false); |
| }, |
| child: new Text('USE IT') |
| ), |
| new FlatButton( |
| onPressed: () { |
| Navigator.pop(context, false); |
| }, |
| child: new Text('OH WELL') |
| ), |
| ] |
| ) |
| ); |
| }, |
| child: new Text('Account Balance') |
| ), |
| new DrawerItem( |
| icon: Icons.dvr, |
| onPressed: () { |
| try { |
| debugDumpApp(); |
| debugDumpRenderTree(); |
| debugDumpLayerTree(); |
| debugDumpSemanticsTree(); |
| } catch (e, stack) { |
| debugPrint('Exception while dumping app:\n$e\n$stack'); |
| } |
| }, |
| child: new Text('Dump App to Console') |
| ), |
| new Divider(), |
| new DrawerItem( |
| icon: Icons.thumb_up, |
| onPressed: () => _handleStockModeChange(StockMode.optimistic), |
| child: new Row( |
| children: <Widget>[ |
| new Flexible(child: new Text('Optimistic')), |
| new Radio<StockMode>(value: StockMode.optimistic, groupValue: config.configuration.stockMode, onChanged: _handleStockModeChange) |
| ] |
| ) |
| ), |
| new DrawerItem( |
| icon: Icons.thumb_down, |
| onPressed: () => _handleStockModeChange(StockMode.pessimistic), |
| child: new Row( |
| children: <Widget>[ |
| new Flexible(child: new Text('Pessimistic')), |
| new Radio<StockMode>(value: StockMode.pessimistic, groupValue: config.configuration.stockMode, onChanged: _handleStockModeChange) |
| ] |
| ) |
| ), |
| new Divider(), |
| new DrawerItem( |
| icon: Icons.settings, |
| onPressed: _handleShowSettings, |
| child: new Text('Settings')), |
| new DrawerItem( |
| icon: Icons.help, |
| child: new Text('Help & Feedback')) |
| ]) |
| ); |
| } |
| |
| void _handleShowSettings() { |
| Navigator.popAndPushNamed(context, '/settings'); |
| } |
| |
| Widget buildToolBar() { |
| return new ToolBar( |
| elevation: 0, |
| center: new Text(StockStrings.of(context).title()), |
| right: <Widget>[ |
| new IconButton( |
| icon: Icons.search, |
| onPressed: _handleSearchBegin, |
| tooltip: 'Search' |
| ), |
| new PopupMenuButton<_StockMenuItem>( |
| onSelected: (_StockMenuItem value) { _handleStockMenu(context, value); }, |
| items: <PopupMenuItem<_StockMenuItem>>[ |
| new CheckedPopupMenuItem<_StockMenuItem>( |
| value: _StockMenuItem.autorefresh, |
| checked: _autorefresh, |
| child: new Text('Autorefresh') |
| ), |
| new PopupMenuItem<_StockMenuItem>( |
| value: _StockMenuItem.refresh, |
| child: new Text('Refresh') |
| ), |
| new PopupMenuItem<_StockMenuItem>( |
| value: _StockMenuItem.speedUp, |
| child: new Text('Increase animation speed') |
| ), |
| new PopupMenuItem<_StockMenuItem>( |
| value: _StockMenuItem.speedDown, |
| child: new Text('Decrease animation speed') |
| ) |
| ] |
| ) |
| ], |
| tabBar: new TabBar<StockHomeTab>( |
| labels: <StockHomeTab, TabLabel>{ |
| StockHomeTab.market: new TabLabel(text: StockStrings.of(context).market()), |
| StockHomeTab.portfolio: new TabLabel(text: StockStrings.of(context).portfolio()) |
| } |
| ) |
| ); |
| } |
| |
| Iterable<Stock> _getStockList(Iterable<String> symbols) { |
| return symbols.map((String symbol) => config.stocks[symbol]) |
| .where((Stock stock) => stock != null); |
| } |
| |
| Iterable<Stock> _filterBySearchQuery(Iterable<Stock> stocks) { |
| if (_searchQuery.text.isEmpty) |
| return stocks; |
| RegExp regexp = new RegExp(_searchQuery.text, caseSensitive: false); |
| return stocks.where((Stock stock) => stock.symbol.contains(regexp)); |
| } |
| |
| void _buyStock(Stock stock, Key arrowKey) { |
| setState(() { |
| stock.percentChange = 100.0 * (1.0 / stock.lastSale); |
| stock.lastSale += 1.0; |
| }); |
| _scaffoldKey.currentState.showSnackBar(new SnackBar( |
| content: new Text("Purchased ${stock.symbol} for ${stock.lastSale}"), |
| action: new SnackBarAction( |
| label: "BUY MORE", |
| onPressed: () { |
| _buyStock(stock, arrowKey); |
| } |
| ) |
| )); |
| } |
| |
| Widget _buildStockList(BuildContext context, Iterable<Stock> stocks, StockHomeTab tab) { |
| return new StockList( |
| keySalt: tab, |
| stocks: stocks.toList(), |
| onAction: _buyStock, |
| onOpen: (Stock stock, Key arrowKey) { |
| Set<Key> mostValuableKeys = new HashSet<Key>(); |
| mostValuableKeys.add(arrowKey); |
| Navigator.pushNamed(context, '/stock/${stock.symbol}', mostValuableKeys: mostValuableKeys); |
| }, |
| onShow: (Stock stock, Key arrowKey) { |
| _scaffoldKey.currentState.showBottomSheet((BuildContext context) => new StockSymbolBottomSheet(stock: stock)); |
| } |
| ); |
| } |
| |
| Widget _buildStockTab(BuildContext context, StockHomeTab tab, List<String> stockSymbols) { |
| return new Container( |
| key: new ValueKey<StockHomeTab>(tab), |
| child: _buildStockList(context, _filterBySearchQuery(_getStockList(stockSymbols)).toList(), tab) |
| ); |
| } |
| |
| static const List<String> portfolioSymbols = const <String>["AAPL","FIZZ", "FIVE", "FLAT", "ZINC", "ZNGA"]; |
| |
| // TODO(abarth): Should we factor this into a SearchBar in the framework? |
| Widget buildSearchBar() { |
| return new ToolBar( |
| left: new IconButton( |
| icon: Icons.arrow_back, |
| color: Theme.of(context).accentColor, |
| onPressed: _handleSearchEnd, |
| tooltip: 'Back' |
| ), |
| center: new Input( |
| value: _searchQuery, |
| autofocus: true, |
| hintText: 'Search stocks', |
| onChanged: _handleSearchQueryChanged |
| ), |
| backgroundColor: Theme.of(context).canvasColor |
| ); |
| } |
| |
| void _handleCreateCompany() { |
| showModalBottomSheet( |
| context: context, |
| builder: (BuildContext context) => new _CreateCompanySheet() |
| ); |
| } |
| |
| Widget buildFloatingActionButton() { |
| return new FloatingActionButton( |
| tooltip: 'Create company', |
| child: new Icon(icon: Icons.add), |
| backgroundColor: Colors.redAccent[200], |
| onPressed: _handleCreateCompany |
| ); |
| } |
| |
| Widget build(BuildContext context) { |
| return new TabBarSelection<StockHomeTab>( |
| values: <StockHomeTab>[StockHomeTab.market, StockHomeTab.portfolio], |
| child: new Scaffold( |
| key: _scaffoldKey, |
| toolBar: _isSearching ? buildSearchBar() : buildToolBar(), |
| floatingActionButton: buildFloatingActionButton(), |
| drawer: _buildDrawer(context), |
| body: new TabBarView( |
| children: <Widget>[ |
| _buildStockTab(context, StockHomeTab.market, config.symbols), |
| _buildStockTab(context, StockHomeTab.portfolio, portfolioSymbols), |
| ] |
| ) |
| ) |
| ); |
| } |
| } |
| |
| class _CreateCompanySheet extends StatefulComponent { |
| _CreateCompanySheetState createState() => new _CreateCompanySheetState(); |
| } |
| |
| class _CreateCompanySheetState extends State<_CreateCompanySheet> { |
| InputValue _companyName = InputValue.empty; |
| |
| void _handleCompanyNameChanged(InputValue value) { |
| setState(() { |
| _companyName = value; |
| }); |
| } |
| |
| Widget build(BuildContext context) { |
| // TODO(ianh): Fill this out. |
| return new Column( |
| children: <Widget>[ |
| new Input( |
| autofocus: true, |
| hintText: 'Company Name', |
| value: _companyName, |
| onChanged: _handleCompanyNameChanged |
| ), |
| ] |
| ); |
| } |
| } |