stock_home.dart 10.6 KB
Newer Older
1 2 3 4
// 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.

5
import 'package:flutter/material.dart';
6
import 'package:flutter/rendering.dart' show debugDumpRenderTree, debugDumpLayerTree, debugDumpSemanticsTree, DebugSemanticsDumpOrder;
7
import 'package:flutter/scheduler.dart' show timeDilation;
8 9 10 11 12
import 'stock_data.dart';
import 'stock_list.dart';
import 'stock_strings.dart';
import 'stock_symbol_viewer.dart';
import 'stock_types.dart';
13 14 15

typedef void ModeUpdater(StockMode mode);

16
enum _StockMenuItem { autorefresh, refresh, speedUp, speedDown }
17 18
enum StockHomeTab { market, portfolio }

19
class _NotImplementedDialog extends StatelessWidget {
20
  @override
21
  Widget build(BuildContext context) {
22
    return new AlertDialog(
23 24
      title: const Text('Not Implemented'),
      content: const Text('This feature has not yet been implemented.'),
25 26
      actions: <Widget>[
        new FlatButton(
27
          onPressed: debugDumpApp,
28 29
          child: new Row(
            children: <Widget>[
30
              const Icon(
Ian Hickson's avatar
Ian Hickson committed
31
                Icons.dvr,
32
                size: 18.0,
33 34
              ),
              new Container(
35
                width: 8.0,
36
              ),
37
              const Text('DUMP APP TO CONSOLE'),
38 39
            ],
          ),
40 41 42 43
        ),
        new FlatButton(
          onPressed: () {
            Navigator.pop(context, false);
Ian Hickson's avatar
Ian Hickson committed
44
          },
45
          child: const Text('OH WELL'),
46 47
        ),
      ],
48 49 50 51
    );
  }
}

52
class StockHome extends StatefulWidget {
53
  const StockHome(this.stocks, this.configuration, this.updater);
54

55
  final StockData stocks;
56 57
  final StockConfiguration configuration;
  final ValueChanged<StockConfiguration> updater;
58

59
  @override
60 61 62 63
  StockHomeState createState() => new StockHomeState();
}

class StockHomeState extends State<StockHome> {
64
  final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
65
  final TextEditingController _searchQuery = new TextEditingController();
66
  bool _isSearching = false;
67
  bool _autorefresh = false;
68

69
  void _handleSearchBegin() {
Hixie's avatar
Hixie committed
70 71
    ModalRoute.of(context).addLocalHistoryEntry(new LocalHistoryEntry(
      onRemove: () {
Adam Barth's avatar
Adam Barth committed
72 73
        setState(() {
          _isSearching = false;
74
          _searchQuery.clear();
Adam Barth's avatar
Adam Barth committed
75
        });
76
      },
Adam Barth's avatar
Adam Barth committed
77
    ));
78 79 80 81 82
    setState(() {
      _isSearching = true;
    });
  }

Adam Barth's avatar
Adam Barth committed
83
  void _handleStockModeChange(StockMode value) {
84 85
    if (widget.updater != null)
      widget.updater(widget.configuration.copyWith(stockMode: value));
86 87
  }

88
  void _handleStockMenu(BuildContext context, _StockMenuItem value) {
89
    switch (value) {
90 91 92 93 94 95
      case _StockMenuItem.autorefresh:
        setState(() {
          _autorefresh = !_autorefresh;
        });
        break;
      case _StockMenuItem.refresh:
96
        showDialog<Null>(
97 98 99 100 101 102 103 104 105 106 107
          context: context,
          child: new _NotImplementedDialog()
        );
        break;
      case _StockMenuItem.speedUp:
        timeDilation /= 5.0;
        break;
      case _StockMenuItem.speedDown:
        timeDilation *= 5.0;
        break;
    }
108 109
  }

110 111
  Widget _buildDrawer(BuildContext context) {
    return new Drawer(
112
      child: new ListView(
113
        children: <Widget>[
114
          const DrawerHeader(child: const Center(child: const Text('Stocks'))),
115
          const ListTile(
116 117
            leading: const Icon(Icons.assessment),
            title: const Text('Stock List'),
118 119
            selected: true,
          ),
120
          const ListTile(
121 122
            leading: const Icon(Icons.account_balance),
            title: const Text('Account Balance'),
123
            enabled: false,
124
          ),
125
          new ListTile(
126 127
            leading: const Icon(Icons.dvr),
            title: const Text('Dump App to Console'),
128
            onTap: () {
129 130 131 132
              try {
                debugDumpApp();
                debugDumpRenderTree();
                debugDumpLayerTree();
133
                debugDumpSemanticsTree(DebugSemanticsDumpOrder.traversal);
134 135 136 137 138
              } catch (e, stack) {
                debugPrint('Exception while dumping app:\n$e\n$stack');
              }
            },
          ),
139
          const Divider(),
140
          new ListTile(
141 142
            leading: const Icon(Icons.thumb_up),
            title: const Text('Optimistic'),
143 144
            trailing: new Radio<StockMode>(
              value: StockMode.optimistic,
145
              groupValue: widget.configuration.stockMode,
146
              onChanged: _handleStockModeChange,
147 148 149 150
            ),
            onTap: () {
              _handleStockModeChange(StockMode.optimistic);
            },
151
          ),
152
          new ListTile(
153 154
            leading: const Icon(Icons.thumb_down),
            title: const Text('Pessimistic'),
155 156
            trailing: new Radio<StockMode>(
              value: StockMode.pessimistic,
157
              groupValue: widget.configuration.stockMode,
158
              onChanged: _handleStockModeChange,
159 160 161 162
            ),
            onTap: () {
              _handleStockModeChange(StockMode.pessimistic);
            },
163
          ),
164
          const Divider(),
165
          new ListTile(
166 167
            leading: const Icon(Icons.settings),
            title: const Text('Settings'),
168 169 170
            onTap: _handleShowSettings,
          ),
          new ListTile(
171 172
            leading: const Icon(Icons.help),
            title: const Text('About'),
173 174
            onTap: _handleShowAbout,
          ),
175 176
        ],
      ),
177 178 179
    );
  }

Adam Barth's avatar
Adam Barth committed
180
  void _handleShowSettings() {
Hixie's avatar
Hixie committed
181
    Navigator.popAndPushNamed(context, '/settings');
182 183
  }

184 185 186 187
  void _handleShowAbout() {
    showAboutDialog(context: context);
  }

188 189
  Widget buildAppBar() {
    return new AppBar(
190
      elevation: 0.0,
191 192
      title: new Text(StockStrings.of(context).title()),
      actions: <Widget>[
193
        new IconButton(
194
          icon: const Icon(Icons.search),
Hixie's avatar
Hixie committed
195
          onPressed: _handleSearchBegin,
196
          tooltip: 'Search',
197
        ),
198 199
        new PopupMenuButton<_StockMenuItem>(
          onSelected: (_StockMenuItem value) { _handleStockMenu(context, value); },
200
          itemBuilder: (BuildContext context) => <PopupMenuItem<_StockMenuItem>>[
Hans Muller's avatar
Hans Muller committed
201
            new CheckedPopupMenuItem<_StockMenuItem>(
202
              value: _StockMenuItem.autorefresh,
203
              checked: _autorefresh,
204
              child: const Text('Autorefresh'),
205
            ),
206
            const PopupMenuItem<_StockMenuItem>(
207
              value: _StockMenuItem.refresh,
208
              child: const Text('Refresh'),
209
            ),
210
            const PopupMenuItem<_StockMenuItem>(
211
              value: _StockMenuItem.speedUp,
212
              child: const Text('Increase animation speed'),
213
            ),
214
            const PopupMenuItem<_StockMenuItem>(
215
              value: _StockMenuItem.speedDown,
216
              child: const Text('Decrease animation speed'),
217 218 219
            ),
          ],
        ),
220
      ],
Hans Muller's avatar
Hans Muller committed
221 222 223 224
      bottom: new TabBar(
        tabs: <Widget>[
          new Tab(text: StockStrings.of(context).market()),
          new Tab(text: StockStrings.of(context).portfolio()),
225 226
        ],
      ),
227
    );
228 229
  }

230 231
  static Iterable<Stock> _getStockList(StockData stocks, Iterable<String> symbols) {
    return symbols.map<Stock>((String symbol) => stocks[symbol])
232
        .where((Stock stock) => stock != null);
233 234 235
  }

  Iterable<Stock> _filterBySearchQuery(Iterable<Stock> stocks) {
236
    if (_searchQuery.text.isEmpty)
237
      return stocks;
238
    final RegExp regexp = new RegExp(_searchQuery.text, caseSensitive: false);
Hixie's avatar
Hixie committed
239
    return stocks.where((Stock stock) => stock.symbol.contains(regexp));
240 241
  }

242
  void _buyStock(Stock stock) {
Hixie's avatar
Hixie committed
243 244 245 246
    setState(() {
      stock.percentChange = 100.0 * (1.0 / stock.lastSale);
      stock.lastSale += 1.0;
    });
247
    _scaffoldKey.currentState.showSnackBar(new SnackBar(
248
      content: new Text('Purchased ${stock.symbol} for ${stock.lastSale}'),
249
      action: new SnackBarAction(
250
        label: 'BUY MORE',
251
        onPressed: () {
252
          _buyStock(stock);
253 254
        },
      ),
Hixie's avatar
Hixie committed
255 256 257
    ));
  }

258
  Widget _buildStockList(BuildContext context, Iterable<Stock> stocks, StockHomeTab tab) {
259 260
    return new StockList(
      stocks: stocks.toList(),
Hixie's avatar
Hixie committed
261
      onAction: _buyStock,
262
      onOpen: (Stock stock) {
263
        Navigator.pushNamed(context, '/stock:${stock.symbol}');
264
      },
265
      onShow: (Stock stock) {
266
        _scaffoldKey.currentState.showBottomSheet<Null>((BuildContext context) => new StockSymbolBottomSheet(stock: stock));
267
      },
268
    );
269 270
  }

271
  Widget _buildStockTab(BuildContext context, StockHomeTab tab, List<String> stockSymbols) {
272
    return new AnimatedBuilder(
273
      key: new ValueKey<StockHomeTab>(tab),
274 275 276 277
      animation: new Listenable.merge(<Listenable>[_searchQuery, widget.stocks]),
      builder: (BuildContext context, Widget child) {
        return _buildStockList(context, _filterBySearchQuery(_getStockList(widget.stocks, stockSymbols)).toList(), tab);
      },
278 279 280
    );
  }

281
  static const List<String> portfolioSymbols = const <String>['AAPL','FIZZ', 'FIVE', 'FLAT', 'ZINC', 'ZNGA'];
Hixie's avatar
Hixie committed
282

283
  Widget buildSearchBar() {
284
    return new AppBar(
285
      leading: new BackButton(
Adam Barth's avatar
Adam Barth committed
286
        color: Theme.of(context).accentColor,
287
      ),
288
      title: new TextField(
289
        controller: _searchQuery,
290
        autofocus: true,
291 292 293
        decoration: const InputDecoration(
          hintText: 'Search stocks',
        ),
294
      ),
295
      backgroundColor: Theme.of(context).canvasColor,
296 297 298
    );
  }

Hixie's avatar
Hixie committed
299
  void _handleCreateCompany() {
300
    showModalBottomSheet<Null>(
Adam Barth's avatar
Adam Barth committed
301
      context: context,
302
      builder: (BuildContext context) => new _CreateCompanySheet(),
Matt Perry's avatar
Matt Perry committed
303
    );
304 305 306
  }

  Widget buildFloatingActionButton() {
307
    return new FloatingActionButton(
308
      tooltip: 'Create company',
309
      child: const Icon(Icons.add),
310
      backgroundColor: Colors.redAccent,
311
      onPressed: _handleCreateCompany,
312
    );
313 314
  }

315
  @override
316
  Widget build(BuildContext context) {
Hans Muller's avatar
Hans Muller committed
317 318
    return new DefaultTabController(
      length: 2,
319 320
      child: new Scaffold(
        key: _scaffoldKey,
321
        appBar: _isSearching ? buildSearchBar() : buildAppBar(),
322 323
        floatingActionButton: buildFloatingActionButton(),
        drawer: _buildDrawer(context),
Hans Muller's avatar
Hans Muller committed
324
        body: new TabBarView(
Adam Barth's avatar
Adam Barth committed
325
          children: <Widget>[
326
            _buildStockTab(context, StockHomeTab.market, widget.stocks.allSymbols),
Adam Barth's avatar
Adam Barth committed
327
            _buildStockTab(context, StockHomeTab.portfolio, portfolioSymbols),
328 329 330
          ],
        ),
      ),
331
    );
332 333
  }
}
334

335
class _CreateCompanySheet extends StatelessWidget {
336
  @override
337 338 339
  Widget build(BuildContext context) {
    return new Column(
      children: <Widget>[
340
        const TextField(
341
          autofocus: true,
342 343 344
          decoration: const InputDecoration(
            hintText: 'Company Name',
          ),
345
        ),
346 347 348
        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.
349
      ],
350 351 352
    );
  }
}