blob: b50b951f21d8fada1ce12aab87d8e7166a065b71 [file] [log] [blame]
// 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 'package:flutter/material.dart';
import 'package:flutter/rendering.dart' show debugDumpRenderTree, debugDumpLayerTree, debugDumpSemanticsTree, DebugSemanticsDumpOrder;
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 StatelessWidget {
@override
Widget build(BuildContext context) {
return new AlertDialog(
title: const Text('Not Implemented'),
content: const Text('This feature has not yet been implemented.'),
actions: <Widget>[
new FlatButton(
onPressed: debugDumpApp,
child: new Row(
children: <Widget>[
const Icon(
Icons.dvr,
size: 18.0,
),
new Container(
width: 8.0,
),
const Text('DUMP APP TO CONSOLE'),
],
),
),
new FlatButton(
onPressed: () {
Navigator.pop(context, false);
},
child: const Text('OH WELL'),
),
],
);
}
}
class StockHome extends StatefulWidget {
const StockHome(this.stocks, this.configuration, this.updater);
final StockData stocks;
final StockConfiguration configuration;
final ValueChanged<StockConfiguration> updater;
@override
StockHomeState createState() => new StockHomeState();
}
class StockHomeState extends State<StockHome> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
final TextEditingController _searchQuery = new TextEditingController();
bool _isSearching = false;
bool _autorefresh = false;
void _handleSearchBegin() {
ModalRoute.of(context).addLocalHistoryEntry(new LocalHistoryEntry(
onRemove: () {
setState(() {
_isSearching = false;
_searchQuery.clear();
});
},
));
setState(() {
_isSearching = true;
});
}
void _handleStockModeChange(StockMode value) {
if (widget.updater != null)
widget.updater(widget.configuration.copyWith(stockMode: value));
}
void _handleStockMenu(BuildContext context, _StockMenuItem value) {
switch (value) {
case _StockMenuItem.autorefresh:
setState(() {
_autorefresh = !_autorefresh;
});
break;
case _StockMenuItem.refresh:
showDialog<void>(
context: context,
builder: (BuildContext context) => 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 ListView(
children: <Widget>[
const DrawerHeader(child: const Center(child: const Text('Stocks'))),
const ListTile(
leading: const Icon(Icons.assessment),
title: const Text('Stock List'),
selected: true,
),
const ListTile(
leading: const Icon(Icons.account_balance),
title: const Text('Account Balance'),
enabled: false,
),
new ListTile(
leading: const Icon(Icons.dvr),
title: const Text('Dump App to Console'),
onTap: () {
try {
debugDumpApp();
debugDumpRenderTree();
debugDumpLayerTree();
debugDumpSemanticsTree(DebugSemanticsDumpOrder.traversalOrder);
} catch (e, stack) {
debugPrint('Exception while dumping app:\n$e\n$stack');
}
},
),
const Divider(),
new ListTile(
leading: const Icon(Icons.thumb_up),
title: const Text('Optimistic'),
trailing: new Radio<StockMode>(
value: StockMode.optimistic,
groupValue: widget.configuration.stockMode,
onChanged: _handleStockModeChange,
),
onTap: () {
_handleStockModeChange(StockMode.optimistic);
},
),
new ListTile(
leading: const Icon(Icons.thumb_down),
title: const Text('Pessimistic'),
trailing: new Radio<StockMode>(
value: StockMode.pessimistic,
groupValue: widget.configuration.stockMode,
onChanged: _handleStockModeChange,
),
onTap: () {
_handleStockModeChange(StockMode.pessimistic);
},
),
const Divider(),
new ListTile(
leading: const Icon(Icons.settings),
title: const Text('Settings'),
onTap: _handleShowSettings,
),
new ListTile(
leading: const Icon(Icons.help),
title: const Text('About'),
onTap: _handleShowAbout,
),
],
),
);
}
void _handleShowSettings() {
Navigator.popAndPushNamed(context, '/settings');
}
void _handleShowAbout() {
showAboutDialog(context: context);
}
Widget buildAppBar() {
return new AppBar(
elevation: 0.0,
title: new Text(StockStrings.of(context).title()),
actions: <Widget>[
new IconButton(
icon: const Icon(Icons.search),
onPressed: _handleSearchBegin,
tooltip: 'Search',
),
new PopupMenuButton<_StockMenuItem>(
onSelected: (_StockMenuItem value) { _handleStockMenu(context, value); },
itemBuilder: (BuildContext context) => <PopupMenuItem<_StockMenuItem>>[
new CheckedPopupMenuItem<_StockMenuItem>(
value: _StockMenuItem.autorefresh,
checked: _autorefresh,
child: const Text('Autorefresh'),
),
const PopupMenuItem<_StockMenuItem>(
value: _StockMenuItem.refresh,
child: const Text('Refresh'),
),
const PopupMenuItem<_StockMenuItem>(
value: _StockMenuItem.speedUp,
child: const Text('Increase animation speed'),
),
const PopupMenuItem<_StockMenuItem>(
value: _StockMenuItem.speedDown,
child: const Text('Decrease animation speed'),
),
],
),
],
bottom: new TabBar(
tabs: <Widget>[
new Tab(text: StockStrings.of(context).market()),
new Tab(text: StockStrings.of(context).portfolio()),
],
),
);
}
static Iterable<Stock> _getStockList(StockData stocks, Iterable<String> symbols) {
return symbols.map<Stock>((String symbol) => stocks[symbol])
.where((Stock stock) => stock != null);
}
Iterable<Stock> _filterBySearchQuery(Iterable<Stock> stocks) {
if (_searchQuery.text.isEmpty)
return stocks;
final RegExp regexp = new RegExp(_searchQuery.text, caseSensitive: false);
return stocks.where((Stock stock) => stock.symbol.contains(regexp));
}
void _buyStock(Stock stock) {
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);
},
),
));
}
Widget _buildStockList(BuildContext context, Iterable<Stock> stocks, StockHomeTab tab) {
return new StockList(
stocks: stocks.toList(),
onAction: _buyStock,
onOpen: (Stock stock) {
Navigator.pushNamed(context, '/stock:${stock.symbol}');
},
onShow: (Stock stock) {
_scaffoldKey.currentState.showBottomSheet<Null>((BuildContext context) => new StockSymbolBottomSheet(stock: stock));
},
);
}
Widget _buildStockTab(BuildContext context, StockHomeTab tab, List<String> stockSymbols) {
return new AnimatedBuilder(
key: new ValueKey<StockHomeTab>(tab),
animation: new Listenable.merge(<Listenable>[_searchQuery, widget.stocks]),
builder: (BuildContext context, Widget child) {
return _buildStockList(context, _filterBySearchQuery(_getStockList(widget.stocks, stockSymbols)).toList(), tab);
},
);
}
static const List<String> portfolioSymbols = const <String>['AAPL','FIZZ', 'FIVE', 'FLAT', 'ZINC', 'ZNGA'];
Widget buildSearchBar() {
return new AppBar(
leading: new BackButton(
color: Theme.of(context).accentColor,
),
title: new TextField(
controller: _searchQuery,
autofocus: true,
decoration: const InputDecoration(
hintText: 'Search stocks',
),
),
backgroundColor: Theme.of(context).canvasColor,
);
}
void _handleCreateCompany() {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) => new _CreateCompanySheet(),
);
}
Widget buildFloatingActionButton() {
return new FloatingActionButton(
tooltip: 'Create company',
child: const Icon(Icons.add),
backgroundColor: Theme.of(context).accentColor,
onPressed: _handleCreateCompany,
);
}
@override
Widget build(BuildContext context) {
return new DefaultTabController(
length: 2,
child: new Scaffold(
key: _scaffoldKey,
appBar: _isSearching ? buildSearchBar() : buildAppBar(),
floatingActionButton: buildFloatingActionButton(),
drawer: _buildDrawer(context),
body: new TabBarView(
children: <Widget>[
_buildStockTab(context, StockHomeTab.market, widget.stocks.allSymbols),
_buildStockTab(context, StockHomeTab.portfolio, portfolioSymbols),
],
),
),
);
}
}
class _CreateCompanySheet extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Column(
children: const <Widget>[
const TextField(
autofocus: true,
decoration: const InputDecoration(
hintText: 'Company Name',
),
),
const Text('(This demo is not yet complete.)'),
// For example, we could add a button that actually updates the list
// and then contacts the server, etc.
],
);
}
}