├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── lib ├── main.dart └── src │ ├── app │ ├── controller │ │ └── stocks.dart │ ├── model │ │ ├── stock_arrow.dart │ │ ├── stock_data.dart │ │ ├── stock_list.dart │ │ ├── stock_row.dart │ │ ├── stock_strings.dart │ │ └── stock_symbol_viewer.dart │ └── view │ │ ├── stock_settings.dart │ │ ├── stock_types.dart │ │ └── stocks.dart │ ├── controller.dart │ ├── home │ ├── controller │ │ └── stock_home.dart │ └── view │ │ └── stock_home.dart │ ├── i18n │ ├── .dartignore │ ├── regenerate.md │ ├── stock_messages_all.dart │ ├── stock_messages_en.dart │ ├── stock_messages_es.dart │ ├── stocks_en.arb │ └── stocks_es.arb │ ├── model.dart │ └── view.dart ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Good to keep this file with the code. 2 | #.gitignore 3 | /.DS_Store 4 | /.atom/ 5 | /.idea/ 6 | /.vscode/ 7 | /.packages 8 | /.pub/ 9 | /build/ 10 | 11 | /.flutter-plugins 12 | /doc/api/ 13 | /.dart_tool/ 14 | 15 | # Android Studio 16 | /*.iml 17 | 18 | # Except for application packages 19 | #pubspec.lock 20 | 21 | # Don't need the Android stuff 22 | /android/ 23 | 24 | # Ignore App signing files 25 | /*.jks 26 | 27 | # Don't need the iOS stuff 28 | /ios/ 29 | 30 | # Avoid committing generated JavaScript files: 31 | /*.dart.js 32 | /*.info.json # Produced by the --dump-info flag. 33 | /*.js # When generated by dart2js. Don't specify *.js if your 34 | # project includes source files written in JavaScript. 35 | /*.js_ 36 | /*.js.deps 37 | /*.js.map 38 | 39 | ### Eclipse ### 40 | /*.pydevproject 41 | /.project 42 | /.metadata 43 | /bin/** 44 | /tmp/** 45 | /tmp/**/* 46 | /*.tmp 47 | /*.bak 48 | /*.swp 49 | /*~.nib 50 | 51 | /.classpath 52 | /.settings/ 53 | /.loadpath 54 | 55 | 56 | # Miscellaneous 57 | *.log 58 | *.pyc 59 | *.swp 60 | .DS_Store 61 | .atom/ 62 | .buildlog/ 63 | .history 64 | .svn/ 65 | 66 | # IntelliJ related 67 | *.ipr 68 | *.iws 69 | 70 | # Visual Studio Code related 71 | .vscode/ 72 | 73 | # Flutter/Dart/Pub related 74 | **/doc/api/ 75 | .dart_tool/ 76 | .pub-cache/ 77 | .pub/ 78 | 79 | 80 | # Android related 81 | **/android/captures/ 82 | 83 | 84 | # iOS/XCode related 85 | **/ios/**/*.mode1v3 86 | **/ios/**/*.mode2v3 87 | **/ios/**/*.moved-aside 88 | **/ios/**/*.pbxuser 89 | **/ios/**/*.perspectivev3 90 | **/ios/**/*sync/ 91 | **/ios/**/.sconsign.dblite 92 | **/ios/**/.tags* 93 | **/ios/**/.vagrant/ 94 | **/ios/**/DerivedData/ 95 | **/ios/**/Icon? 96 | **/ios/**/Pods/ 97 | **/ios/**/.symlinks/ 98 | **/ios/**/profile 99 | **/ios/**/xcuserdata 100 | **/ios/.generated/ 101 | **/ios/Flutter/App.framework 102 | **/ios/Flutter/Flutter.framework 103 | **/ios/Flutter/app.flx 104 | **/ios/Flutter/app.zip 105 | **/ios/Flutter/flutter_assets/ 106 | **/ios/ServiceDefinitions.json 107 | 108 | # Exceptions to above rules. 109 | !**/ios/**/default.mode1v3 110 | !**/ios/**/default.mode2v3 111 | !**/ios/**/default.pbxuser 112 | !**/ios/**/default.perspectivev3 113 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 114 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## 1.2.0 3 | February 16, 2024 4 | - Commit and Erase from laptop 5 | 6 | ## 1.1.1 7 | July 21, 2019 8 | - Add mvc_application: ^1.0.3 to pubspec.yaml 9 | 10 | ## 1.1.0 11 | July 20, 2019 12 | - class _Titles; class _Drawer in controller/stock_home.dart 13 | - three versions of build in view/stock_home.dart 14 | 15 | ## 1.0.2 16 | July 17, 2019 17 | - import 'package:flutter/material.dart'; in controller/stocks.dart 18 | 19 | ## 1.0.1 20 | July 17, 2019 21 | - import 'package:stocks_mvc/src/view.dart' show App, StocksApp; 22 | 23 | ## 1.0.0 24 | July 17, 2019 25 | - Initial public commit 26 | - Migrated to MVC design pattern in mvc_application Dart Package. 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 5 | 6 | 1. Definitions. 7 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 8 | 9 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 10 | 11 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with 12 | that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such 13 | entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 14 | 15 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 16 | 17 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 18 | 19 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, 20 | generated documentation, and conversions to other media types. 21 | 22 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or 23 | attached to the work (an example is provided in the Appendix below). 24 | 25 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, 26 | elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works 27 | that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 28 | 29 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, 30 | that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. 31 | For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, 32 | including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, 33 | the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing 34 | by the copyright owner as "Not a Contribution." 35 | 36 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 37 | 38 | 2. Grant of Copyright License. 39 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, 40 | no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, 41 | sublicense, and distribute the Work and such Derivative Works in Source or Object form. 42 | 43 | 3. Grant of Patent License. 44 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, 45 | royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, 46 | and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily 47 | infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. 48 | If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or 49 | a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted 50 | to You under this License for that Work shall terminate as of the date such litigation is filed. 51 | 52 | 4. Redistribution. 53 | You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, 54 | and in Source or Object form, provided that You meet the following conditions: 55 | 56 | 1.You must give any other recipients of the Work or Derivative Works a copy of this License; and 57 | 58 | 2.You must cause any modified files to carry prominent notices stating that You changed the files; and 59 | 60 | 3.You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, 61 | and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 62 | 63 | 4.If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include 64 | a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part 65 | of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; 66 | within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, 67 | if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 68 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, 69 | reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, 70 | and distribution of the Work otherwise complies with the conditions stated in this License. 71 | 72 | 5. Submission of Contributions. 73 | Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms 74 | and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms 75 | of any separate license agreement you may have executed with Licensor regarding such Contributions. 76 | 77 | 6. Trademarks. 78 | This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, 79 | except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 80 | 81 | 7. Disclaimer of Warranty. 82 | Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) 83 | on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, 84 | any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. 85 | You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with 86 | Your exercise of permissions under this License. 87 | 88 | 8. Limitation of Liability. 89 | In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law 90 | (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, 91 | including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or 92 | out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, 93 | or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 94 | 95 | 9. Accepting Warranty or Additional Liability. 96 | While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, 97 | indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, 98 | You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if 99 | You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, 100 | such Contributor by reason of your accepting any such warranty or additional liability. 101 | 102 | END OF TERMS AND CONDITIONS 103 | 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stocks_mvc 2 | 3 | The Flutter Stocks Example using MVC 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (C) 2019 Andrious Solutions 3 | /// 4 | /// This program is free software; you can redistribute it and/or 5 | /// modify it under the terms of the GNU General Public License 6 | /// as published by the Free Software Foundation; either version 3 7 | /// of the License, or any later version. 8 | /// 9 | /// You may obtain a copy of the License at 10 | /// 11 | /// http://www.apache.org/licenses/LICENSE-2.0 12 | /// 13 | /// 14 | /// Unless required by applicable law or agreed to in writing, software 15 | /// distributed under the License is distributed on an "AS IS" BASIS, 16 | /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | /// See the License for the specific language governing permissions and 18 | /// limitations under the License. 19 | /// 20 | /// Created 09 Jul 2019 21 | /// 22 | /// 23 | 24 | import 'package:flutter/material.dart' show runApp; 25 | 26 | import 'package:stocks_mvc/src/view.dart' show App, StocksApp; 27 | 28 | void main() => runApp(MyApp()); 29 | 30 | class MyApp extends App { 31 | @override 32 | createView() => StocksApp(); 33 | } 34 | -------------------------------------------------------------------------------- /lib/src/app/controller/stocks.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (C) 2019 Andrious Solutions 3 | /// 4 | /// This program is free software; you can redistribute it and/or 5 | /// modify it under the terms of the GNU General Public License 6 | /// as published by the Free Software Foundation; either version 3 7 | /// of the License, or any later version. 8 | /// 9 | /// You may obtain a copy of the License at 10 | /// 11 | /// http://www.apache.org/licenses/LICENSE-2.0 12 | /// 13 | /// 14 | /// Unless required by applicable law or agreed to in writing, software 15 | /// distributed under the License is distributed on an "AS IS" BASIS, 16 | /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | /// See the License for the specific language governing permissions and 18 | /// limitations under the License. 19 | /// 20 | /// Created 09 Jul 2019 21 | /// 22 | /// 23 | 24 | import 'package:flutter/material.dart'; 25 | 26 | import 'package:stocks_mvc/src/model.dart'; 27 | import 'package:stocks_mvc/src/view.dart'; 28 | import 'package:stocks_mvc/src/controller.dart'; 29 | 30 | class AppStocks extends AppController { 31 | factory AppStocks([StateMVC state]) { 32 | _this ??= AppStocks._(state); 33 | return _this; 34 | } 35 | static AppStocks _this; 36 | 37 | AppStocks._([StateMVC state]) : super(state); 38 | 39 | @override 40 | void initState() { 41 | _state = stateMVC; 42 | _stocks = StockData(); 43 | } 44 | static AppView _state; 45 | 46 | static StockData get stocks => _stocks; 47 | static StockData _stocks; 48 | 49 | static ThemeData get theme { 50 | switch (stockMode) { 51 | case StockMode.optimistic: 52 | return ThemeData( 53 | brightness: Brightness.light, 54 | primarySwatch: Colors.purple, 55 | ); 56 | case StockMode.pessimistic: 57 | return ThemeData( 58 | brightness: Brightness.dark, 59 | accentColor: Colors.redAccent, 60 | ); 61 | } 62 | return null; 63 | } 64 | static set theme(ThemeData v) { 65 | _state?.theme = v; 66 | _state?.refresh(); 67 | } 68 | 69 | static StockMode get stockMode => _stockMode; 70 | static set stockMode(StockMode v) { 71 | _stockMode = v; 72 | _state?.theme = theme; 73 | _state?.refresh(); 74 | } 75 | static StockMode _stockMode = StockMode.optimistic; 76 | 77 | static bool get debugShowGrid => _state?.debugShowMaterialGrid; 78 | static set debugShowGrid(bool v) { 79 | _state?.debugShowMaterialGrid = v; 80 | _state?.refresh(); 81 | } 82 | 83 | static bool get debugShowSizes => _state?.debugPaintSizeEnabled; 84 | static set debugShowSizes(bool v) { 85 | _state?.debugPaintSizeEnabled = v; 86 | _state?.refresh(); 87 | } 88 | 89 | static bool get debugShowBaselines => _state?.debugPaintBaselinesEnabled; 90 | static set debugShowBaselines(bool v) { 91 | _state?.debugPaintBaselinesEnabled = v; 92 | _state?.refresh(); 93 | } 94 | 95 | static bool get debugShowLayers => _state?.debugPaintLayerBordersEnabled; 96 | static set debugShowLayers(bool v) { 97 | _state?.debugPaintLayerBordersEnabled = v; 98 | _state?.refresh(); 99 | } 100 | 101 | static bool get debugShowPointers => _state?.debugPaintPointersEnabled; 102 | static set debugShowPointers(bool v) { 103 | _state?.debugPaintPointersEnabled = v; 104 | _state?.refresh(); 105 | } 106 | 107 | static bool get debugShowRainbow => _state?.debugRepaintRainbowEnabled; 108 | static set debugShowRainbow(bool v) { 109 | _state?.debugRepaintRainbowEnabled = v; 110 | _state?.refresh(); 111 | } 112 | 113 | static bool get showPerformanceOverlay => _state?.showPerformanceOverlay; 114 | static set showPerformanceOverlay(bool v) { 115 | _state?.showPerformanceOverlay = v; 116 | _state?.refresh(); 117 | } 118 | 119 | static bool get showSemanticsDebugger => _state?.showSemanticsDebugger; 120 | static set showSemanticsDebugger(bool v) { 121 | _state?.showSemanticsDebugger = v; 122 | _state?.refresh(); 123 | } 124 | 125 | static BackupMode get backupMode => _backupMode; 126 | static set backupMode(BackupMode v) { 127 | _backupMode = v; 128 | _state?.refresh(); 129 | } 130 | static BackupMode _backupMode = BackupMode.enabled; 131 | 132 | static final _StocksLocalizationsDelegate localizationsDelegate = 133 | _StocksLocalizationsDelegate(); 134 | 135 | static StockSymbolPage symbolPage({String symbol}) => 136 | StockSymbolPage(symbol: symbol, stocks: AppStocks.stocks); 137 | } 138 | 139 | class _StocksLocalizationsDelegate extends LocalizationsDelegate { 140 | @override 141 | Future load(Locale locale) => StockStrings.load(locale); 142 | 143 | @override 144 | bool isSupported(Locale locale) => 145 | locale.languageCode == 'es' || locale.languageCode == 'en'; 146 | 147 | @override 148 | bool shouldReload(_StocksLocalizationsDelegate old) => false; 149 | } 150 | -------------------------------------------------------------------------------- /lib/src/app/model/stock_arrow.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:math' as math; 6 | 7 | import 'package:flutter/material.dart'; 8 | 9 | class StockArrowPainter extends CustomPainter { 10 | StockArrowPainter({this.color, this.percentChange}); 11 | 12 | final Color color; 13 | final double percentChange; 14 | 15 | @override 16 | void paint(Canvas canvas, Size size) { 17 | final Paint paint = Paint()..color = color; 18 | paint.strokeWidth = 1.0; 19 | const double padding = 2.0; 20 | assert(padding > 21 | paint.strokeWidth / 2.0); // make sure the circle remains inside the box 22 | final double r = 23 | (size.shortestSide - padding) / 2.0; // radius of the circle 24 | final double centerX = padding + r; 25 | final double centerY = padding + r; 26 | 27 | // Draw the arrow. 28 | const double w = 8.0; 29 | double h = 5.0; 30 | double arrowY; 31 | if (percentChange < 0.0) { 32 | h = -h; 33 | arrowY = centerX + 1.0; 34 | } else { 35 | arrowY = centerX - 1.0; 36 | } 37 | final Path path = Path(); 38 | path.moveTo(centerX, arrowY - h); // top of the arrow 39 | path.lineTo(centerX + w, arrowY + h); 40 | path.lineTo(centerX - w, arrowY + h); 41 | path.close(); 42 | paint.style = PaintingStyle.fill; 43 | canvas.drawPath(path, paint); 44 | 45 | // Draw a circle that circumscribes the arrow. 46 | paint.style = PaintingStyle.stroke; 47 | canvas.drawCircle(Offset(centerX, centerY), r, paint); 48 | } 49 | 50 | @override 51 | bool shouldRepaint(StockArrowPainter oldDelegate) { 52 | return oldDelegate.color != color || 53 | oldDelegate.percentChange != percentChange; 54 | } 55 | } 56 | 57 | class StockArrow extends StatelessWidget { 58 | const StockArrow({Key key, this.percentChange}) : super(key: key); 59 | 60 | final double percentChange; 61 | 62 | int _colorIndexForPercentChange(double percentChange) { 63 | const double maxPercent = 10.0; 64 | final double normalizedPercentChange = 65 | math.min(percentChange.abs(), maxPercent) / maxPercent; 66 | return 100 + (normalizedPercentChange * 8.0).floor() * 100; 67 | } 68 | 69 | Color _colorForPercentChange(double percentChange) { 70 | if (percentChange > 0) 71 | return Colors.green[_colorIndexForPercentChange(percentChange)]; 72 | return Colors.red[_colorIndexForPercentChange(percentChange)]; 73 | } 74 | 75 | @override 76 | Widget build(BuildContext context) { 77 | return Container( 78 | width: 40.0, 79 | height: 40.0, 80 | margin: const EdgeInsets.symmetric(horizontal: 5.0), 81 | child: CustomPaint( 82 | painter: StockArrowPainter( 83 | // TODO(jackson): This should change colors with the theme 84 | color: _colorForPercentChange(percentChange), 85 | percentChange: percentChange, 86 | ), 87 | ), 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/src/app/model/stock_data.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | // Snapshot from http://www.nasdaq.com/screening/company-list.aspx 6 | // Fetched 2/23/2014. 7 | // "Symbol","Name","LastSale","MarketCap","IPOyear","Sector","industry","Summary Quote", 8 | // Data in stock_data.json 9 | 10 | import 'dart:convert'; 11 | import 'dart:math' as math; 12 | 13 | import 'package:flutter/foundation.dart'; 14 | import 'package:http/http.dart' as http; 15 | 16 | final math.Random _rng = math.Random(); 17 | 18 | class Stock { 19 | Stock(this.symbol, this.name, this.lastSale, this.marketCap, 20 | this.percentChange); 21 | 22 | Stock.fromFields(List fields) { 23 | // FIXME: This class should only have static data, not lastSale, etc. 24 | // "Symbol","Name","LastSale","MarketCap","IPOyear","Sector","industry","Summary Quote", 25 | lastSale = 0.0; 26 | try { 27 | lastSale = double.parse(fields[2]); 28 | } catch (_) {} 29 | symbol = fields[0]; 30 | name = fields[1]; 31 | marketCap = fields[4]; 32 | percentChange = (_rng.nextDouble() * 20) - 10; 33 | } 34 | 35 | String symbol; 36 | String name; 37 | double lastSale; 38 | String marketCap; 39 | double percentChange; 40 | } 41 | 42 | class StockData extends ChangeNotifier { 43 | StockData() { 44 | if (actuallyFetchData) { 45 | _httpClient = http.Client(); 46 | _fetchNextChunk(); 47 | } 48 | } 49 | 50 | final List _symbols = []; 51 | final Map _stocks = {}; 52 | 53 | Iterable get allSymbols => _symbols; 54 | 55 | Stock operator [](String symbol) => _stocks[symbol]; 56 | 57 | bool get loading => _httpClient != null; 58 | 59 | void add(List data) { 60 | for (List fields in data) { 61 | final Stock stock = Stock.fromFields(fields.cast()); 62 | _symbols.add(stock.symbol); 63 | _stocks[stock.symbol] = stock; 64 | } 65 | _symbols.sort(); 66 | notifyListeners(); 67 | } 68 | 69 | static const int _chunkCount = 30; 70 | int _nextChunk = 0; 71 | 72 | String _urlToFetch(int chunk) { 73 | return 'https://domokit.github.io/examples/stocks/data/stock_data_$chunk.json'; 74 | } 75 | 76 | http.Client _httpClient; 77 | 78 | static bool actuallyFetchData = true; 79 | 80 | void _fetchNextChunk() { 81 | _httpClient 82 | .get(_urlToFetch(_nextChunk++)) 83 | .then((http.Response response) { 84 | final String json = response.body; 85 | if (json == null) { 86 | debugPrint('Failed to load stock data chunk ${_nextChunk - 1}'); 87 | _end(); 88 | return; 89 | } 90 | const JsonDecoder decoder = JsonDecoder(); 91 | add(decoder.convert(json)); 92 | if (_nextChunk < _chunkCount) { 93 | _fetchNextChunk(); 94 | } else { 95 | _end(); 96 | } 97 | }); 98 | } 99 | 100 | void _end() { 101 | _httpClient?.close(); 102 | _httpClient = null; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/src/app/model/stock_list.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:flutter/material.dart'; 6 | 7 | import 'package:stocks_mvc/src/app/model/stock_data.dart'; 8 | import 'package:stocks_mvc/src/app/model/stock_row.dart'; 9 | 10 | class StockList extends StatelessWidget { 11 | const StockList( 12 | {Key key, this.stocks, this.onOpen, this.onShow, this.onAction}) 13 | : super(key: key); 14 | 15 | final List stocks; 16 | final StockRowActionCallback onOpen; 17 | final StockRowActionCallback onShow; 18 | final StockRowActionCallback onAction; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return ListView.builder( 23 | key: const ValueKey('stock-list'), 24 | itemExtent: StockRow.kHeight, 25 | itemCount: stocks.length, 26 | itemBuilder: (BuildContext context, int index) { 27 | return StockRow( 28 | stock: stocks[index], 29 | onPressed: onOpen, 30 | onDoubleTap: onShow, 31 | onLongPressed: onAction, 32 | ); 33 | }, 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/app/model/stock_row.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:flutter/material.dart'; 6 | 7 | import 'package:stocks_mvc/src/app/model/stock_arrow.dart'; 8 | import 'package:stocks_mvc/src/app/model/stock_data.dart'; 9 | 10 | typedef StockRowActionCallback = void Function(Stock stock); 11 | 12 | class StockRow extends StatelessWidget { 13 | StockRow({ 14 | this.stock, 15 | this.onPressed, 16 | this.onDoubleTap, 17 | this.onLongPressed, 18 | }) : super(key: ObjectKey(stock)); 19 | 20 | final Stock stock; 21 | final StockRowActionCallback onPressed; 22 | final StockRowActionCallback onDoubleTap; 23 | final StockRowActionCallback onLongPressed; 24 | 25 | static const double kHeight = 79.0; 26 | 27 | GestureTapCallback _getHandler(StockRowActionCallback callback) { 28 | return callback == null ? null : () => callback(stock); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | final String lastSale = '\$${stock.lastSale.toStringAsFixed(2)}'; 34 | String changeInPrice = '${stock.percentChange.toStringAsFixed(2)}%'; 35 | if (stock.percentChange > 0) changeInPrice = '+' + changeInPrice; 36 | return InkWell( 37 | onTap: _getHandler(onPressed), 38 | onDoubleTap: _getHandler(onDoubleTap), 39 | onLongPress: _getHandler(onLongPressed), 40 | child: Container( 41 | padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 20.0), 42 | decoration: BoxDecoration( 43 | border: Border( 44 | bottom: BorderSide(color: Theme.of(context).dividerColor))), 45 | child: Row( 46 | children: [ 47 | Container( 48 | margin: const EdgeInsets.only(right: 5.0), 49 | child: Hero( 50 | tag: stock, 51 | child: StockArrow(percentChange: stock.percentChange), 52 | ), 53 | ), 54 | Expanded( 55 | child: Row( 56 | children: [ 57 | Expanded( 58 | flex: 2, 59 | child: Text(stock.symbol), 60 | ), 61 | Expanded( 62 | child: Text( 63 | lastSale, 64 | textAlign: TextAlign.right, 65 | ), 66 | ), 67 | Expanded( 68 | child: Text( 69 | changeInPrice, 70 | textAlign: TextAlign.right, 71 | ), 72 | ), 73 | ], 74 | crossAxisAlignment: CrossAxisAlignment.baseline, 75 | textBaseline: DefaultTextStyle.of(context).style.textBaseline, 76 | ), 77 | ), 78 | ], 79 | ), 80 | ), 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/src/app/model/stock_strings.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:intl/intl.dart'; 8 | import 'package:flutter/widgets.dart'; 9 | 10 | import 'package:stocks_mvc/src/i18n/stock_messages_all.dart'; 11 | 12 | // Information about how this file relates to i18n/stock_messages_all.dart and how the i18n files 13 | // were generated can be found in i18n/regenerate.md. 14 | 15 | class StockStrings { 16 | StockStrings(Locale locale) : _localeName = locale.toString(); 17 | 18 | final String _localeName; 19 | 20 | static Future load(Locale locale) { 21 | return initializeMessages(locale.toString()).then((Object _) { 22 | return StockStrings(locale); 23 | }); 24 | } 25 | 26 | static StockStrings of(BuildContext context) { 27 | return Localizations.of(context, StockStrings); 28 | } 29 | 30 | String title() { 31 | return Intl.message( 32 | '', 33 | name: 'title', 34 | desc: 'Title for the Stocks application', 35 | locale: _localeName, 36 | ); 37 | } 38 | 39 | String market() => Intl.message( 40 | 'MARKET', 41 | name: 'market', 42 | desc: 'Label for the Market tab', 43 | locale: _localeName, 44 | ); 45 | 46 | String portfolio() => Intl.message( 47 | 'PORTFOLIO', 48 | name: 'portfolio', 49 | desc: 'Label for the Portfolio tab', 50 | locale: _localeName, 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /lib/src/app/model/stock_symbol_viewer.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:flutter/material.dart'; 6 | 7 | import 'package:stocks_mvc/src/app/model/stock_arrow.dart'; 8 | import 'package:stocks_mvc/src/app/model/stock_data.dart'; 9 | 10 | class StockSymbolPage extends StatelessWidget { 11 | const StockSymbolPage({this.symbol, this.stocks}); 12 | 13 | final String symbol; 14 | final StockData stocks; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return AnimatedBuilder( 19 | animation: stocks, 20 | builder: (BuildContext context, Widget child) { 21 | final Stock stock = stocks[symbol]; 22 | return Scaffold( 23 | appBar: AppBar( 24 | title: Text(stock?.name ?? symbol), 25 | ), 26 | body: SingleChildScrollView( 27 | child: Container( 28 | margin: const EdgeInsets.all(20.0), 29 | child: Card( 30 | child: AnimatedCrossFade( 31 | duration: const Duration(milliseconds: 300), 32 | firstChild: const Padding( 33 | padding: EdgeInsets.all(20.0), 34 | child: Center(child: CircularProgressIndicator()), 35 | ), 36 | secondChild: stock != null 37 | ? _StockSymbolView( 38 | stock: stock, 39 | arrow: Hero( 40 | tag: stock, 41 | child: 42 | StockArrow(percentChange: stock.percentChange), 43 | ), 44 | ) 45 | : Padding( 46 | padding: const EdgeInsets.all(20.0), 47 | child: Center(child: Text('$symbol not found')), 48 | ), 49 | crossFadeState: stock == null && stocks.loading 50 | ? CrossFadeState.showFirst 51 | : CrossFadeState.showSecond, 52 | ), 53 | ), 54 | ), 55 | ), 56 | ); 57 | }, 58 | ); 59 | } 60 | } 61 | 62 | class StockSymbolBottomSheet extends StatelessWidget { 63 | const StockSymbolBottomSheet({this.stock}); 64 | 65 | final Stock stock; 66 | 67 | @override 68 | Widget build(BuildContext context) { 69 | return Container( 70 | padding: const EdgeInsets.all(10.0), 71 | decoration: const BoxDecoration( 72 | border: Border(top: BorderSide(color: Colors.black26))), 73 | child: _StockSymbolView( 74 | stock: stock, 75 | arrow: StockArrow(percentChange: stock.percentChange), 76 | ), 77 | ); 78 | } 79 | } 80 | 81 | class _StockSymbolView extends StatelessWidget { 82 | const _StockSymbolView({this.stock, this.arrow}); 83 | 84 | final Stock stock; 85 | final Widget arrow; 86 | 87 | @override 88 | Widget build(BuildContext context) { 89 | assert(stock != null); 90 | final String lastSale = '\$${stock.lastSale.toStringAsFixed(2)}'; 91 | String changeInPrice = '${stock.percentChange.toStringAsFixed(2)}%'; 92 | if (stock.percentChange > 0) changeInPrice = '+' + changeInPrice; 93 | 94 | final TextStyle headings = Theme.of(context).textTheme.body2; 95 | return Container( 96 | padding: const EdgeInsets.all(20.0), 97 | child: Column( 98 | children: [ 99 | Row( 100 | children: [ 101 | Text( 102 | '${stock.symbol}', 103 | style: Theme.of(context).textTheme.display2, 104 | ), 105 | arrow, 106 | ], 107 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 108 | ), 109 | Text('Last Sale', style: headings), 110 | Text('$lastSale ($changeInPrice)'), 111 | Container(height: 8.0), 112 | Text('Market Cap', style: headings), 113 | Text('${stock.marketCap}'), 114 | Container(height: 8.0), 115 | RichText( 116 | text: TextSpan( 117 | style: DefaultTextStyle.of(context) 118 | .style 119 | .merge(const TextStyle(fontSize: 8.0)), 120 | text: 'Prices may be delayed by ', 121 | children: const [ 122 | TextSpan( 123 | text: 'several', 124 | style: TextStyle(fontStyle: FontStyle.italic)), 125 | TextSpan(text: ' years.'), 126 | ], 127 | ), 128 | ), 129 | ], 130 | mainAxisSize: MainAxisSize.min, 131 | ), 132 | ); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /lib/src/app/view/stock_settings.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:flutter/material.dart'; 6 | 7 | import 'stock_types.dart'; 8 | 9 | import 'package:stocks_mvc/src/controller.dart'; 10 | 11 | class StockSettings extends StatefulWidget { 12 | @override 13 | StockSettingsState createState() => StockSettingsState(); 14 | } 15 | 16 | class StockSettingsState extends State { 17 | void _handleOptimismChanged(bool value) { 18 | value ??= false; 19 | AppStocks.stockMode = value ? StockMode.optimistic : StockMode.pessimistic; 20 | } 21 | 22 | void _handleBackupChanged(bool value) { 23 | value ??= true; 24 | AppStocks.backupMode = value ? BackupMode.enabled : BackupMode.disabled; 25 | } 26 | 27 | void _handleShowGridChanged(bool value) { 28 | AppStocks.debugShowGrid = value; 29 | } 30 | 31 | void _handleShowSizesChanged(bool value) { 32 | AppStocks.debugShowSizes = value; 33 | } 34 | 35 | void _handleShowBaselinesChanged(bool value) { 36 | AppStocks.debugShowBaselines = value; 37 | } 38 | 39 | void _handleShowLayersChanged(bool value) { 40 | AppStocks.debugShowLayers = value; 41 | } 42 | 43 | void _handleShowPointersChanged(bool value) { 44 | AppStocks.debugShowPointers = value; 45 | } 46 | 47 | void _handleShowRainbowChanged(bool value) { 48 | AppStocks.debugShowRainbow = value; 49 | } 50 | 51 | void _handleShowPerformanceOverlayChanged(bool value) { 52 | AppStocks.showPerformanceOverlay = value; 53 | } 54 | 55 | void _handleShowSemanticsDebuggerChanged(bool value) { 56 | AppStocks.showSemanticsDebugger = value; 57 | } 58 | 59 | Widget buildAppBar(BuildContext context) { 60 | return AppBar( 61 | title: const Text('Settings'), 62 | ); 63 | } 64 | 65 | Widget buildSettingsPane(BuildContext context) { 66 | final List rows = [ 67 | ListTile( 68 | leading: const Icon(Icons.thumb_up), 69 | title: const Text('Everything is awesome'), 70 | onTap: _confirmOptimismChange, 71 | trailing: Checkbox( 72 | value: AppStocks.stockMode == StockMode.optimistic, 73 | onChanged: (bool value) => _confirmOptimismChange(), 74 | ), 75 | ), 76 | ListTile( 77 | leading: const Icon(Icons.backup), 78 | title: const Text('Back up stock list to the cloud'), 79 | onTap: () { 80 | _handleBackupChanged( 81 | !(AppStocks.backupMode == BackupMode.enabled)); 82 | }, 83 | trailing: Switch( 84 | value: AppStocks.backupMode == BackupMode.enabled, 85 | onChanged: _handleBackupChanged, 86 | ), 87 | ), 88 | ListTile( 89 | leading: const Icon(Icons.picture_in_picture), 90 | title: const Text('Show rendering performance overlay'), 91 | onTap: () { 92 | _handleShowPerformanceOverlayChanged( 93 | !AppStocks.showPerformanceOverlay); 94 | }, 95 | trailing: Switch( 96 | value: AppStocks.showPerformanceOverlay, 97 | onChanged: _handleShowPerformanceOverlayChanged, 98 | ), 99 | ), 100 | ListTile( 101 | leading: const Icon(Icons.accessibility), 102 | title: const Text('Show semantics overlay'), 103 | onTap: () { 104 | _handleShowSemanticsDebuggerChanged( 105 | !AppStocks.showSemanticsDebugger); 106 | }, 107 | trailing: Switch( 108 | value: AppStocks.showSemanticsDebugger, 109 | onChanged: _handleShowSemanticsDebuggerChanged, 110 | ), 111 | ), 112 | ]; 113 | assert(() { 114 | // material grid and size construction lines are only available in checked mode 115 | rows.addAll([ 116 | ListTile( 117 | leading: const Icon(Icons.border_clear), 118 | title: const Text('Show material grid (for debugging)'), 119 | onTap: () { 120 | _handleShowGridChanged(!AppStocks.debugShowGrid); 121 | }, 122 | trailing: Switch( 123 | value: AppStocks.debugShowGrid, 124 | onChanged: _handleShowGridChanged, 125 | ), 126 | ), 127 | ListTile( 128 | leading: const Icon(Icons.border_all), 129 | title: const Text('Show construction lines (for debugging)'), 130 | onTap: () { 131 | _handleShowSizesChanged(!AppStocks.debugShowSizes); 132 | }, 133 | trailing: Switch( 134 | value: AppStocks.debugShowSizes, 135 | onChanged: _handleShowSizesChanged, 136 | ), 137 | ), 138 | ListTile( 139 | leading: const Icon(Icons.format_color_text), 140 | title: const Text('Show baselines (for debugging)'), 141 | onTap: () { 142 | _handleShowBaselinesChanged( 143 | !AppStocks.debugShowBaselines); 144 | }, 145 | trailing: Switch( 146 | value: AppStocks.debugShowBaselines, 147 | onChanged: _handleShowBaselinesChanged, 148 | ), 149 | ), 150 | ListTile( 151 | leading: const Icon(Icons.filter_none), 152 | title: const Text('Show layer boundaries (for debugging)'), 153 | onTap: () { 154 | _handleShowLayersChanged(!AppStocks.debugShowLayers); 155 | }, 156 | trailing: Switch( 157 | value: AppStocks.debugShowLayers, 158 | onChanged: _handleShowLayersChanged, 159 | ), 160 | ), 161 | ListTile( 162 | leading: const Icon(Icons.mouse), 163 | title: const Text('Show pointer hit-testing (for debugging)'), 164 | onTap: () { 165 | _handleShowPointersChanged( 166 | !AppStocks.debugShowPointers); 167 | }, 168 | trailing: Switch( 169 | value: AppStocks.debugShowPointers, 170 | onChanged: _handleShowPointersChanged, 171 | ), 172 | ), 173 | ListTile( 174 | leading: const Icon(Icons.gradient), 175 | title: const Text('Show repaint rainbow (for debugging)'), 176 | onTap: () { 177 | _handleShowRainbowChanged( 178 | !AppStocks.debugShowRainbow); 179 | }, 180 | trailing: Switch( 181 | value: AppStocks.debugShowRainbow, 182 | onChanged: _handleShowRainbowChanged, 183 | ), 184 | ), 185 | ]); 186 | return true; 187 | }()); 188 | return ListView( 189 | padding: const EdgeInsets.symmetric(vertical: 20.0), 190 | children: rows, 191 | ); 192 | } 193 | 194 | 195 | void _confirmOptimismChange() { 196 | switch (AppStocks.stockMode) { 197 | case StockMode.optimistic: 198 | _handleOptimismChanged(false); 199 | break; 200 | case StockMode.pessimistic: 201 | showDialog( 202 | context: context, 203 | builder: (BuildContext context) { 204 | return AlertDialog( 205 | title: const Text('Change mode?'), 206 | content: const Text( 207 | 'Optimistic mode means everything is awesome. Are you sure you can handle that?'), 208 | actions: [ 209 | FlatButton( 210 | child: const Text('NO THANKS'), 211 | onPressed: () { 212 | Navigator.pop(context, false); 213 | }, 214 | ), 215 | FlatButton( 216 | child: const Text('AGREE'), 217 | onPressed: () { 218 | Navigator.pop(context, true); 219 | }, 220 | ), 221 | ], 222 | ); 223 | }, 224 | ).then(_handleOptimismChanged); 225 | break; 226 | } 227 | } 228 | 229 | @override 230 | Widget build(BuildContext context) { 231 | return Scaffold( 232 | appBar: buildAppBar(context), 233 | body: buildSettingsPane(context), 234 | ); 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /lib/src/app/view/stock_types.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | //import 'package:flutter/foundation.dart'; 6 | 7 | enum StockMode { optimistic, pessimistic } 8 | enum BackupMode { enabled, disabled } 9 | 10 | //class StockConfiguration { 11 | // StockConfiguration({ 12 | // @required this.stockMode, 13 | // @required this.backupMode, 14 | // @required this.debugShowGrid, 15 | // @required this.debugShowSizes, 16 | // @required this.debugShowBaselines, 17 | // @required this.debugShowLayers, 18 | // @required this.debugShowPointers, 19 | // @required this.debugShowRainbow, 20 | // @required this.showPerformanceOverlay, 21 | // @required this.showSemanticsDebugger, 22 | // }) : assert(stockMode != null), 23 | // assert(backupMode != null), 24 | // assert(debugShowGrid != null), 25 | // assert(debugShowSizes != null), 26 | // assert(debugShowBaselines != null), 27 | // assert(debugShowLayers != null), 28 | // assert(debugShowPointers != null), 29 | // assert(debugShowRainbow != null), 30 | // assert(showPerformanceOverlay != null), 31 | // assert(showSemanticsDebugger != null); 32 | // 33 | // final StockMode stockMode; 34 | // final BackupMode backupMode; 35 | // final bool debugShowGrid; 36 | // final bool debugShowSizes; 37 | // final bool debugShowBaselines; 38 | // final bool debugShowLayers; 39 | // final bool debugShowPointers; 40 | // final bool debugShowRainbow; 41 | // final bool showPerformanceOverlay; 42 | // final bool showSemanticsDebugger; 43 | // 44 | // StockConfiguration copyWith({ 45 | // StockMode stockMode, 46 | // BackupMode backupMode, 47 | // bool debugShowGrid, 48 | // bool debugShowSizes, 49 | // bool debugShowBaselines, 50 | // bool debugShowLayers, 51 | // bool debugShowPointers, 52 | // bool debugShowRainbow, 53 | // bool showPerformanceOverlay, 54 | // bool showSemanticsDebugger, 55 | // }) { 56 | // return StockConfiguration( 57 | // stockMode: stockMode ?? this.stockMode, 58 | // backupMode: backupMode ?? this.backupMode, 59 | // debugShowGrid: debugShowGrid ?? this.debugShowGrid, 60 | // debugShowSizes: debugShowSizes ?? this.debugShowSizes, 61 | // debugShowBaselines: debugShowBaselines ?? this.debugShowBaselines, 62 | // debugShowLayers: debugShowLayers ?? this.debugShowLayers, 63 | // debugShowPointers: debugShowPointers ?? this.debugShowPointers, 64 | // debugShowRainbow: debugShowRainbow ?? this.debugShowRainbow, 65 | // showPerformanceOverlay: 66 | // showPerformanceOverlay ?? this.showPerformanceOverlay, 67 | // showSemanticsDebugger: 68 | // showSemanticsDebugger ?? this.showSemanticsDebugger, 69 | // ); 70 | // } 71 | //} 72 | -------------------------------------------------------------------------------- /lib/src/app/view/stocks.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (C) 2019 Andrious Solutions 3 | /// 4 | /// This program is free software; you can redistribute it and/or 5 | /// modify it under the terms of the GNU General Public License 6 | /// as published by the Free Software Foundation; either version 3 7 | /// of the License, or any later version. 8 | /// 9 | /// You may obtain a copy of the License at 10 | /// 11 | /// http://www.apache.org/licenses/LICENSE-2.0 12 | /// 13 | /// 14 | /// Unless required by applicable law or agreed to in writing, software 15 | /// distributed under the License is distributed on an "AS IS" BASIS, 16 | /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | /// See the License for the specific language governing permissions and 18 | /// limitations under the License. 19 | /// 20 | /// Created 09 Jul 2019 21 | /// 22 | /// 23 | 24 | import 'package:flutter/material.dart'; 25 | 26 | import 'package:flutter_localizations/flutter_localizations.dart'; 27 | 28 | import 'package:stocks_mvc/src/view.dart'; 29 | 30 | import 'package:stocks_mvc/src/controller.dart' as con; 31 | 32 | class StocksApp extends AppView { 33 | StocksApp() 34 | : super( 35 | con: con.AppStocks(), 36 | title: 'Stocks', 37 | theme: con.AppStocks.theme, 38 | localizationsDelegates: >[ 39 | con.AppStocks.localizationsDelegate, 40 | GlobalMaterialLocalizations.delegate, 41 | GlobalWidgetsLocalizations.delegate, 42 | ], 43 | supportedLocales: const [ 44 | Locale('en', 'US'), 45 | Locale('es', 'ES'), 46 | ], 47 | ); 48 | 49 | onRoutes() => { 50 | '/': (BuildContext context) => StockHome(), 51 | '/settings': (BuildContext context) => StockSettings(), 52 | }; 53 | 54 | onOnGenerateRoute() => (RouteSettings settings) { 55 | if (settings.name == '/stock') { 56 | final String symbol = settings.arguments; 57 | return MaterialPageRoute( 58 | settings: settings, 59 | builder: (BuildContext context) => 60 | con.AppStocks.symbolPage(symbol: symbol), 61 | ); 62 | } 63 | // The other paths we support are in the routes table. 64 | return null; 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /lib/src/controller.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (C) 2019 Andrious Solutions 3 | /// 4 | /// This program is free software; you can redistribute it and/or 5 | /// modify it under the terms of the GNU General Public License 6 | /// as published by the Free Software Foundation; either version 3 7 | /// of the License, or any later version. 8 | /// 9 | /// You may obtain a copy of the License at 10 | /// 11 | /// http://www.apache.org/licenses/LICENSE-2.0 12 | /// 13 | /// 14 | /// Unless required by applicable law or agreed to in writing, software 15 | /// distributed under the License is distributed on an "AS IS" BASIS, 16 | /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | /// See the License for the specific language governing permissions and 18 | /// limitations under the License. 19 | /// 20 | /// Created 09 Jul 2019 21 | /// 22 | /// 23 | 24 | export 'package:mvc_application/controller.dart'; 25 | 26 | export 'package:stocks_mvc/src/app/controller/stocks.dart'; 27 | 28 | export 'package:stocks_mvc/src/home/controller/stock_home.dart'; 29 | -------------------------------------------------------------------------------- /lib/src/home/controller/stock_home.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (C) 2019 Andrious Solutions 3 | /// 4 | /// This program is free software; you can redistribute it and/or 5 | /// modify it under the terms of the GNU General Public License 6 | /// as published by the Free Software Foundation; either version 3 7 | /// of the License, or any later version. 8 | /// 9 | /// You may obtain a copy of the License at 10 | /// 11 | /// http://www.apache.org/licenses/LICENSE-2.0 12 | /// 13 | /// 14 | /// Unless required by applicable law or agreed to in writing, software 15 | /// distributed under the License is distributed on an "AS IS" BASIS, 16 | /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | /// See the License for the specific language governing permissions and 18 | /// limitations under the License. 19 | /// 20 | /// Created 12 Jul 2019 21 | /// 22 | /// 23 | /// 24 | import 'package:flutter/material.dart'; 25 | import 'package:flutter/gestures.dart' show DragStartBehavior; 26 | 27 | import 'package:flutter/rendering.dart'; 28 | import 'package:flutter/scheduler.dart' show timeDilation; 29 | 30 | import 'package:stocks_mvc/src/model.dart'; 31 | import 'package:stocks_mvc/src/view.dart'; 32 | import 'package:stocks_mvc/src/controller.dart'; 33 | 34 | class StockHome extends ControllerMVC { 35 | factory StockHome() { 36 | _this ??= StockHome._(); 37 | return _this; 38 | } 39 | StockHome._(); 40 | static StockHome _this; 41 | 42 | @override 43 | void initState() { 44 | _widget = _Widgets(this); 45 | _title = _Titles(this); 46 | _onTaps = OnTaps(this); 47 | } 48 | 49 | @override 50 | void dispose() { 51 | _this = null; 52 | super.dispose(); 53 | } 54 | 55 | @override 56 | void didChangeDependencies() { 57 | _widget.didChangeDependencies(); 58 | } 59 | 60 | List portfolioSymbols = [ 61 | 'AAPL', 62 | 'FIZZ', 63 | 'FIVE', 64 | 'FLAT', 65 | 'ZINC', 66 | 'ZNGA' 67 | ]; 68 | 69 | final GlobalKey scaffoldKey = GlobalKey(); 70 | 71 | _Titles get title => _title; 72 | _Titles _title; 73 | 74 | _Widgets get widget => _widget; 75 | _Widgets _widget; 76 | 77 | OnTaps get onTap => _onTaps; 78 | OnTaps _onTaps; 79 | 80 | BuildContext get context => this.stateMVC.context; 81 | 82 | Widget buildAppBar() { 83 | return AppBar( 84 | elevation: 0.0, 85 | title: _widget.appBarTitle, 86 | actions: [ 87 | _widget.search, 88 | _widget.popMenu, 89 | ], 90 | bottom: TabBar( 91 | tabs: [ 92 | _widget.market, 93 | _widget.portfolio, 94 | ], 95 | ), 96 | ); 97 | } 98 | 99 | Widget buildSearchBar() { 100 | return AppBar( 101 | leading: BackButton( 102 | color: Theme.of(context).accentColor, 103 | ), 104 | title: TextField( 105 | controller: _searchQuery, 106 | autofocus: true, 107 | decoration: const InputDecoration( 108 | hintText: 'Search stocks', 109 | ), 110 | ), 111 | backgroundColor: Theme.of(context).canvasColor, 112 | ); 113 | } 114 | 115 | Widget buildStockTab( 116 | BuildContext context, StockHomeTab tab, List stockSymbols) { 117 | return AnimatedBuilder( 118 | key: ValueKey(tab), 119 | animation: Listenable.merge([_searchQuery, AppStocks.stocks]), 120 | builder: (BuildContext context, Widget child) { 121 | return _buildStockList( 122 | context, 123 | _filterBySearchQuery(_getStockList(AppStocks.stocks, stockSymbols)) 124 | .toList(), 125 | tab); 126 | }, 127 | ); 128 | } 129 | 130 | Widget _buildStockList( 131 | BuildContext context, Iterable stocks, StockHomeTab tab) { 132 | return StockList( 133 | stocks: stocks.toList(), 134 | onAction: _buyStock, 135 | onOpen: (Stock stock) { 136 | Navigator.pushNamed(context, '/stock', arguments: stock.symbol); 137 | }, 138 | onShow: (Stock stock) { 139 | this.scaffoldKey.currentState.showBottomSheet( 140 | (BuildContext context) => StockSymbolBottomSheet(stock: stock)); 141 | }, 142 | ); 143 | } 144 | 145 | void _handleStockMenu(BuildContext context, _StockMenuItem value) { 146 | switch (value) { 147 | case _StockMenuItem.autorefresh: 148 | setState(() { 149 | _autorefresh = !_autorefresh; 150 | }); 151 | break; 152 | case _StockMenuItem.refresh: 153 | showDialog( 154 | context: context, 155 | builder: (BuildContext context) => _NotImplementedDialog(), 156 | ); 157 | break; 158 | case _StockMenuItem.speedUp: 159 | timeDilation /= 5.0; 160 | break; 161 | case _StockMenuItem.speedDown: 162 | timeDilation *= 5.0; 163 | break; 164 | } 165 | } 166 | 167 | void _handleSearchBegin() { 168 | ModalRoute.of(context).addLocalHistoryEntry(LocalHistoryEntry( 169 | onRemove: () { 170 | setState(() { 171 | _isSearching = false; 172 | _searchQuery.clear(); 173 | }); 174 | }, 175 | )); 176 | setState(() { 177 | _isSearching = true; 178 | }); 179 | } 180 | 181 | void _buyStock(Stock stock) { 182 | setState(() { 183 | stock.percentChange = 100.0 * (1.0 / stock.lastSale); 184 | stock.lastSale += 1.0; 185 | }); 186 | this.scaffoldKey.currentState.showSnackBar(SnackBar( 187 | content: Text('Purchased ${stock.symbol} for ${stock.lastSale}'), 188 | action: SnackBarAction( 189 | label: 'BUY MORE', 190 | onPressed: () { 191 | _buyStock(stock); 192 | }, 193 | ), 194 | )); 195 | } 196 | 197 | Iterable _getStockList(StockData stocks, Iterable symbols) { 198 | return symbols 199 | .map((String symbol) => stocks[symbol]) 200 | .where((Stock stock) => stock != null); 201 | } 202 | 203 | Iterable _filterBySearchQuery(Iterable stocks) { 204 | if (_searchQuery.text.isEmpty) return stocks; 205 | final RegExp regexp = RegExp(_searchQuery.text, caseSensitive: false); 206 | return stocks.where((Stock stock) => stock.symbol.contains(regexp)); 207 | } 208 | 209 | void handleStockModeChange(StockMode value) { 210 | AppStocks.stockMode = value; 211 | } 212 | } 213 | 214 | class _Titles { 215 | _Titles(this.con); 216 | StockHome con; 217 | 218 | Widget stockList = const Text('Stock List'); 219 | 220 | Widget balance = const Text('Account Balance'); 221 | 222 | Widget dumpConsole = const Text('Dump App to Console'); 223 | 224 | Widget optimistic = const Text('Optimistic'); 225 | 226 | Widget pessimistic = const Text('Pessimistic'); 227 | 228 | Widget settings = const Text('Settings'); 229 | 230 | Widget about = const Text('About'); 231 | } 232 | 233 | class _Widgets { 234 | _Widgets(this.con) { 235 | listTiles = _ListTiles(con); 236 | _appBar = _AppBar(con); 237 | _drawer = _Drawer(con); 238 | _body = _Body(con); 239 | } 240 | StockHome con; 241 | _ListTiles listTiles; 242 | _AppBar _appBar; 243 | _Drawer _drawer; 244 | _Body _body; 245 | _FloatingActionButton _floatingButton; 246 | 247 | Widget get drawer => _drawer.drawer; 248 | 249 | Widget get body => _body.body; 250 | 251 | Widget get appBar => _isSearching ? con.buildSearchBar() : con.buildAppBar(); 252 | 253 | Widget get stockList => listTiles.stockList; 254 | 255 | Widget get accountBalance => listTiles.accountBalance; 256 | 257 | Widget get dumpConsole => listTiles.dumpConsole; 258 | 259 | Widget get optimistic => listTiles.optimistic; 260 | 261 | Widget get pessimistic => listTiles.pessimistic; 262 | 263 | Widget get settings => listTiles.settings; 264 | 265 | Widget get about => listTiles.about; 266 | 267 | Widget get appBarTitle => _appBar.appBarTitle; 268 | 269 | Widget get search => _appBar.search; 270 | 271 | Widget get popMenu => _appBar.popMenu; 272 | 273 | Widget get market => _appBar.market; 274 | 275 | Widget get portfolio => _appBar.portfolio; 276 | 277 | void didChangeDependencies() { 278 | _floatingButton = _FloatingActionButton(con); 279 | _appBar.stockStrings(); 280 | } 281 | 282 | Widget get marketTab => con.buildStockTab( 283 | con.context, StockHomeTab.market, AppStocks.stocks.allSymbols); 284 | 285 | Widget get portfolioTab => con.buildStockTab( 286 | con.context, StockHomeTab.portfolio, con.portfolioSymbols); 287 | 288 | _FloatingActionButton get floatingButton => _floatingButton; 289 | } 290 | 291 | class _AppBar { 292 | _AppBar(this.con) { 293 | popMenu = PopupMenuButton( 294 | onSelected: (_StockMenuItem value) { 295 | con._handleStockMenu(con.context, value); 296 | }, 297 | itemBuilder: (BuildContext context) => >[ 298 | autoRefresh, 299 | refresh, 300 | increase, 301 | decrease, 302 | ], 303 | ); 304 | 305 | search = IconButton( 306 | icon: const Icon(Icons.search), 307 | onPressed: con._handleSearchBegin, 308 | tooltip: 'Search', 309 | ); 310 | } 311 | StockHome con; 312 | PopupMenuButton<_StockMenuItem> popMenu; 313 | IconButton search; 314 | Tab market; 315 | Tab portfolio; 316 | Text appBarTitle; 317 | 318 | void stockStrings() { 319 | market = Tab(text: StockStrings.of(con.context).market()); 320 | 321 | portfolio = Tab(text: StockStrings.of(con.context).portfolio()); 322 | 323 | appBarTitle = Text(StockStrings.of(con.context).title()); 324 | } 325 | 326 | CheckedPopupMenuItem<_StockMenuItem> autoRefresh = CheckedPopupMenuItem( 327 | value: _StockMenuItem.autorefresh, 328 | checked: _autorefresh, 329 | child: const Text('Autorefresh'), 330 | ); 331 | 332 | PopupMenuItem<_StockMenuItem> refresh = const PopupMenuItem( 333 | value: _StockMenuItem.refresh, 334 | child: Text('Refresh'), 335 | ); 336 | 337 | PopupMenuItem<_StockMenuItem> increase = const PopupMenuItem( 338 | value: _StockMenuItem.speedUp, 339 | child: Text('Increase animation speed'), 340 | ); 341 | 342 | PopupMenuItem<_StockMenuItem> decrease = const PopupMenuItem( 343 | value: _StockMenuItem.speedDown, 344 | child: Text('Decrease animation speed'), 345 | ); 346 | } 347 | 348 | class _FloatingActionButton { 349 | _FloatingActionButton(StockHome con) { 350 | _createCompany = FloatingActionButton( 351 | tooltip: 'Create company', 352 | child: const Icon(Icons.add), 353 | backgroundColor: Theme.of(con.context).accentColor, 354 | onPressed: () { 355 | showModalBottomSheet( 356 | context: con.context, 357 | builder: (BuildContext context) => _CreateCompanySheet(), 358 | ); 359 | }, 360 | ); 361 | } 362 | FloatingActionButton get createCompany => _createCompany; 363 | FloatingActionButton _createCompany; 364 | } 365 | 366 | class _CreateCompanySheet extends StatelessWidget { 367 | @override 368 | Widget build(BuildContext context) { 369 | return Column( 370 | children: const [ 371 | TextField( 372 | autofocus: true, 373 | decoration: InputDecoration( 374 | hintText: 'Company Name', 375 | ), 376 | ), 377 | Text('(This demo is not yet complete.)'), 378 | // For example, we could add a button that actually updates the list 379 | // and then contacts the server, etc. 380 | ], 381 | ); 382 | } 383 | } 384 | 385 | class _ListTiles { 386 | _ListTiles(this.con); 387 | StockHome con; 388 | 389 | ListTile stockList = const ListTile( 390 | leading: Icon(Icons.assessment), 391 | title: Text('Stock List'), 392 | selected: true, 393 | ); 394 | 395 | ListTile accountBalance = const ListTile( 396 | leading: Icon(Icons.account_balance), 397 | title: Text('Account Balance'), 398 | enabled: false, 399 | ); 400 | 401 | ListTile dumpConsole = ListTile( 402 | leading: const Icon(Icons.dvr), 403 | title: const Text('Dump App to Console'), 404 | onTap: () { 405 | try { 406 | debugDumpApp(); 407 | debugDumpRenderTree(); 408 | debugDumpLayerTree(); 409 | debugDumpSemanticsTree(DebugSemanticsDumpOrder.traversalOrder); 410 | } catch (e, stack) { 411 | debugPrint('Exception while dumping app:\n$e\n$stack'); 412 | } 413 | }, 414 | ); 415 | 416 | ListTile get optimistic => ListTile( 417 | leading: const Icon(Icons.thumb_up), 418 | title: const Text('Optimistic'), 419 | trailing: Radio( 420 | value: StockMode.optimistic, 421 | groupValue: AppStocks.stockMode, 422 | onChanged: con.handleStockModeChange, 423 | ), 424 | onTap: () { 425 | con.handleStockModeChange(StockMode.optimistic); 426 | con.stateMVC.refresh(); 427 | }, 428 | ); 429 | 430 | ListTile get pessimistic => ListTile( 431 | leading: const Icon(Icons.thumb_down), 432 | title: const Text('Pessimistic'), 433 | trailing: Radio( 434 | value: StockMode.pessimistic, 435 | groupValue: AppStocks.stockMode, 436 | onChanged: con.handleStockModeChange, 437 | ), 438 | onTap: () { 439 | con.handleStockModeChange(StockMode.pessimistic); 440 | con.stateMVC.refresh(); 441 | }, 442 | ); 443 | 444 | ListTile get settings => ListTile( 445 | leading: const Icon(Icons.settings), 446 | title: const Text('Settings'), 447 | onTap: () => con.onTap.settings(con.context), 448 | ); 449 | 450 | ListTile get about => ListTile( 451 | leading: const Icon(Icons.help), 452 | title: const Text('About'), 453 | onTap: () => con.onTap.about(con.context), 454 | ); 455 | } 456 | 457 | class OnTaps { 458 | OnTaps(this.con); 459 | StockHome con; 460 | 461 | GestureTapCallback get dumpConsole => () { 462 | try { 463 | debugDumpApp(); 464 | debugDumpRenderTree(); 465 | debugDumpLayerTree(); 466 | debugDumpSemanticsTree(DebugSemanticsDumpOrder.traversalOrder); 467 | } catch (e, stack) { 468 | debugPrint('Exception while dumping app:\n$e\n$stack'); 469 | } 470 | }; 471 | 472 | GestureTapCallback get optimistic => () { 473 | con.handleStockModeChange(StockMode.optimistic); 474 | con.stateMVC.refresh(); 475 | }; 476 | 477 | GestureTapCallback get pessimistic => () { 478 | con.handleStockModeChange(StockMode.pessimistic); 479 | con.stateMVC.refresh(); 480 | }; 481 | 482 | settings(BuildContext context) { 483 | Navigator.popAndPushNamed(context, '/settings'); 484 | } 485 | 486 | about(BuildContext context) { 487 | showAboutDialog(context: context); 488 | } 489 | } 490 | 491 | class _Drawer { 492 | _Drawer(this.con); 493 | StockHome con; 494 | 495 | Widget get drawer => Drawer( 496 | child: ListView( 497 | dragStartBehavior: DragStartBehavior.down, 498 | children: [ 499 | const DrawerHeader(child: Center(child: Text('Stocks'))), 500 | con.widget.stockList, 501 | con.widget.accountBalance, 502 | con.widget.dumpConsole, 503 | const Divider(), 504 | con.widget.optimistic, 505 | con.widget.pessimistic, 506 | const Divider(), 507 | con.widget.settings, 508 | con.widget.about, 509 | ], 510 | ), 511 | ); 512 | } 513 | 514 | class _Body{ 515 | _Body(this.con); 516 | StockHome con; 517 | 518 | Widget get body => TabBarView( 519 | dragStartBehavior: DragStartBehavior.down, 520 | children: [ 521 | con.widget.marketTab, 522 | con.widget.portfolioTab, 523 | ], 524 | ); 525 | } 526 | 527 | class _NotImplementedDialog extends StatelessWidget { 528 | @override 529 | Widget build(BuildContext context) { 530 | return AlertDialog( 531 | title: const Text('Not Implemented'), 532 | content: const Text('This feature has not yet been implemented.'), 533 | actions: [ 534 | FlatButton( 535 | onPressed: debugDumpApp, 536 | child: Row( 537 | children: [ 538 | const Icon( 539 | Icons.dvr, 540 | size: 18.0, 541 | ), 542 | Container( 543 | width: 8.0, 544 | ), 545 | const Text('DUMP APP TO CONSOLE'), 546 | ], 547 | ), 548 | ), 549 | FlatButton( 550 | onPressed: () { 551 | Navigator.pop(context, false); 552 | }, 553 | child: const Text('OH WELL'), 554 | ), 555 | ], 556 | ); 557 | } 558 | } 559 | 560 | final TextEditingController _searchQuery = TextEditingController(); 561 | bool _isSearching = false; 562 | bool _autorefresh = false; 563 | 564 | enum _StockMenuItem { autorefresh, refresh, speedUp, speedDown } 565 | -------------------------------------------------------------------------------- /lib/src/home/view/stock_home.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:flutter/material.dart'; 6 | 7 | import 'package:flutter/gestures.dart' show DragStartBehavior; 8 | 9 | import 'package:stocks_mvc/src/view.dart'; 10 | 11 | import 'package:stocks_mvc/src/controller.dart' as cont; 12 | 13 | typedef ModeUpdater = void Function(StockMode mode); 14 | 15 | enum StockHomeTab { market, portfolio } 16 | 17 | class StockHome extends StatefulWidget { 18 | @override 19 | StockHomeState createState() => StockHomeState(); 20 | } 21 | 22 | class StockHomeState extends StateMVC { 23 | StockHomeState() : super(cont.StockHome()) { 24 | con = this.controller; 25 | } 26 | cont.StockHome con; 27 | 28 | // _degree01, _degree02 & _degree03 are also available. 29 | @override 30 | Widget build(BuildContext context) { 31 | return DefaultTabController( 32 | length: 2, 33 | child: Scaffold( 34 | drawerDragStartBehavior: DragStartBehavior.down, 35 | key: con.scaffoldKey, 36 | appBar: con.widget.appBar, 37 | floatingActionButton: con.widget.floatingButton.createCompany, 38 | drawer: Drawer( 39 | child: ListView( 40 | dragStartBehavior: DragStartBehavior.down, 41 | children: [ 42 | const DrawerHeader(child: Center(child: Text('Stocks'))), 43 | con.widget.stockList, 44 | con.widget.accountBalance, 45 | con.widget.dumpConsole, 46 | const Divider(), 47 | con.widget.optimistic, 48 | con.widget.pessimistic, 49 | const Divider(), 50 | con.widget.settings, 51 | con.widget.about, 52 | ], 53 | ), 54 | ), 55 | body: TabBarView( 56 | dragStartBehavior: DragStartBehavior.down, 57 | children: [ 58 | con.widget.marketTab, 59 | con.widget.portfolioTab, 60 | ], 61 | ), 62 | ), 63 | ); 64 | } 65 | 66 | Widget get _degree01 => DefaultTabController( 67 | length: 2, 68 | child: Scaffold( 69 | drawerDragStartBehavior: DragStartBehavior.down, 70 | key: con.scaffoldKey, 71 | appBar: con.widget.appBar, 72 | floatingActionButton: con.widget.floatingButton.createCompany, 73 | drawer: Drawer( 74 | child: ListView( 75 | dragStartBehavior: DragStartBehavior.down, 76 | children: [ 77 | const DrawerHeader(child: Center(child: Text('Stocks'))), 78 | ListTile( 79 | leading: Icon(Icons.assessment), 80 | title: con.title.stockList, 81 | selected: true, 82 | ), 83 | ListTile( 84 | leading: Icon(Icons.account_balance), 85 | title: con.title.balance, 86 | enabled: false, 87 | ), 88 | ListTile( 89 | leading: const Icon(Icons.dvr), 90 | title: con.title.dumpConsole, 91 | onTap: con.onTap.dumpConsole, 92 | ), 93 | const Divider(), 94 | ListTile( 95 | leading: const Icon(Icons.thumb_up), 96 | title: con.title.optimistic, 97 | trailing: Radio( 98 | value: StockMode.optimistic, 99 | groupValue: cont.AppStocks.stockMode, 100 | onChanged: con.handleStockModeChange, 101 | ), 102 | onTap: con.onTap.optimistic, 103 | ), 104 | ListTile( 105 | leading: const Icon(Icons.thumb_down), 106 | title: con.title.pessimistic, 107 | trailing: Radio( 108 | value: StockMode.pessimistic, 109 | groupValue: cont.AppStocks.stockMode, 110 | onChanged: con.handleStockModeChange, 111 | ), 112 | onTap: con.onTap.pessimistic, 113 | ), 114 | const Divider(), 115 | ListTile( 116 | leading: const Icon(Icons.settings), 117 | title: con.title.settings, 118 | onTap: () { 119 | con.onTap.settings(con.context); 120 | }, 121 | ), 122 | ListTile( 123 | leading: const Icon(Icons.help), 124 | title: con.title.about, 125 | onTap: () { 126 | con.onTap.about(con.context); 127 | }, 128 | ), 129 | ], 130 | ), 131 | ), 132 | body: TabBarView( 133 | dragStartBehavior: DragStartBehavior.down, 134 | children: [ 135 | con.widget.marketTab, 136 | con.widget.portfolioTab, 137 | ], 138 | ), 139 | ), 140 | ); 141 | 142 | Widget get _degree02 => DefaultTabController( 143 | length: 2, 144 | child: Scaffold( 145 | drawerDragStartBehavior: DragStartBehavior.down, 146 | key: con.scaffoldKey, 147 | appBar: con.widget.appBar, 148 | floatingActionButton: con.widget.floatingButton.createCompany, 149 | drawer: Drawer( 150 | child: ListView( 151 | dragStartBehavior: DragStartBehavior.down, 152 | children: [ 153 | const DrawerHeader(child: Center(child: Text('Stocks'))), 154 | con.widget.stockList, 155 | con.widget.accountBalance, 156 | con.widget.dumpConsole, 157 | const Divider(), 158 | con.widget.optimistic, 159 | con.widget.pessimistic, 160 | const Divider(), 161 | con.widget.settings, 162 | con.widget.about, 163 | ], 164 | ), 165 | ), 166 | body: TabBarView( 167 | dragStartBehavior: DragStartBehavior.down, 168 | children: [ 169 | con.widget.marketTab, 170 | con.widget.portfolioTab, 171 | ], 172 | ), 173 | ), 174 | ); 175 | 176 | Widget get _degree03 => DefaultTabController( 177 | length: 2, 178 | child: Scaffold( 179 | drawerDragStartBehavior: DragStartBehavior.down, 180 | key: con.scaffoldKey, 181 | appBar: con.widget.appBar, 182 | floatingActionButton: con.widget.floatingButton.createCompany, 183 | drawer: con.widget.drawer, 184 | body: con.widget.body, 185 | ), 186 | ); 187 | } 188 | -------------------------------------------------------------------------------- /lib/src/i18n/.dartignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andrious/stocks_mvc/24e73ca3f55b642aa5f6cab34b85ba0c46b2fb83/lib/src/i18n/.dartignore -------------------------------------------------------------------------------- /lib/src/i18n/regenerate.md: -------------------------------------------------------------------------------- 1 | ## Regenerating the i18n files 2 | 3 | The files in this directory are based on ../lib/stock_strings.dart 4 | which defines all of the localizable strings used by the stocks 5 | app. The stocks app uses 6 | the [Dart `intl` package](https://github.com/dart-lang/intl). 7 | 8 | Rebuilding everything requires two steps. 9 | 10 | With the `examples/stocks` as the current directory, generate 11 | `intl_messages.arb` from `lib/stock_strings.dart`: 12 | ``` 13 | flutter pub pub run intl_translation:extract_to_arb --output-dir=lib/i18n lib/stock_strings.dart 14 | ``` 15 | The `intl_messages.arb` file is a JSON format map with one entry for 16 | each `Intl.message()` function defined in `stock_strings.dart`. This 17 | file was used to create the English and Spanish localizations, 18 | `stocks_en.arb` and `stocks_es.arb`. The `intl_messages.arb` wasn't 19 | checked into the repository, since it only serves as a template for 20 | the other `.arb` files. 21 | 22 | 23 | With the `examples/stocks` as the current directory, generate a 24 | `stock_messages_.dart` for each `stocks_.arb` file and 25 | `stock_messages_all.dart`, which imports all of the messages files: 26 | ``` 27 | flutter pub pub run intl_translation:generate_from_arb --output-dir=lib/i18n \ 28 | --generated-file-prefix=stock_ --no-use-deferred-loading lib/*.dart lib/i18n/stocks_*.arb 29 | ``` 30 | 31 | The `StockStrings` class uses the generated `initializeMessages()` 32 | function (`stock_messages_all.dart`) to load the localized messages 33 | and `Intl.message()` to look them up. 34 | -------------------------------------------------------------------------------- /lib/src/i18n/stock_messages_all.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that looks up messages for specific locales by 3 | // delegating to the appropriate library. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:intl/intl.dart'; 8 | import 'package:intl/message_lookup_by_library.dart'; 9 | // ignore: implementation_imports 10 | import 'package:intl/src/intl_helpers.dart'; 11 | 12 | import 'stock_messages_en.dart' as messages_en; 13 | import 'stock_messages_es.dart' as messages_es; 14 | 15 | typedef Future LibraryLoader(); 16 | Map _deferredLibraries = { 17 | 'en': () => new Future.value(null), 18 | 'es': () => new Future.value(null), 19 | }; 20 | 21 | MessageLookupByLibrary _findExact(localeName) { 22 | switch (localeName) { 23 | case 'en': 24 | return messages_en.messages; 25 | case 'es': 26 | return messages_es.messages; 27 | default: 28 | return null; 29 | } 30 | } 31 | 32 | /// User programs should call this before using [localeName] for messages. 33 | Future initializeMessages(String localeName) async { 34 | var availableLocale = Intl.verifiedLocale( 35 | localeName, (locale) => _deferredLibraries[locale] != null, 36 | onFailure: (_) => null); 37 | if (availableLocale == null) { 38 | return new Future.value(false); 39 | } 40 | var lib = _deferredLibraries[availableLocale]; 41 | await (lib == null ? new Future.value(false) : lib()); 42 | initializeInternalMessageLookup(() => new CompositeMessageLookup()); 43 | messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); 44 | return new Future.value(true); 45 | } 46 | 47 | bool _messagesExistFor(String locale) { 48 | try { 49 | return _findExact(locale) != null; 50 | } catch (e) { 51 | return false; 52 | } 53 | } 54 | 55 | MessageLookupByLibrary _findGeneratedMessagesFor(locale) { 56 | var actualLocale = 57 | Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null); 58 | if (actualLocale == null) return null; 59 | return _findExact(actualLocale); 60 | } 61 | -------------------------------------------------------------------------------- /lib/src/i18n/stock_messages_en.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that provides messages for a en locale. All the 3 | // messages from the main program should be duplicated here with the same 4 | // function name. 5 | 6 | import 'package:intl/intl.dart'; 7 | import 'package:intl/message_lookup_by_library.dart'; 8 | 9 | final messages = new MessageLookup(); 10 | 11 | // ignore: unused_element 12 | final _keepAnalysisHappy = Intl.defaultLocale; 13 | 14 | // ignore: non_constant_identifier_names 15 | typedef MessageIfAbsent(String message_str, List args); 16 | 17 | class MessageLookup extends MessageLookupByLibrary { 18 | get localeName => 'en'; 19 | 20 | final messages = _notInlinedMessages(_notInlinedMessages); 21 | static _notInlinedMessages(_) => { 22 | "market": MessageLookupByLibrary.simpleMessage("MARKET"), 23 | "portfolio": MessageLookupByLibrary.simpleMessage("PORTFOLIO"), 24 | "title": MessageLookupByLibrary.simpleMessage("Stocks"), 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/i18n/stock_messages_es.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that provides messages for a es locale. All the 3 | // messages from the main program should be duplicated here with the same 4 | // function name. 5 | 6 | import 'package:intl/intl.dart'; 7 | import 'package:intl/message_lookup_by_library.dart'; 8 | 9 | final messages = new MessageLookup(); 10 | 11 | // ignore: unused_element 12 | final _keepAnalysisHappy = Intl.defaultLocale; 13 | 14 | // ignore: non_constant_identifier_names 15 | typedef MessageIfAbsent(String message_str, List args); 16 | 17 | class MessageLookup extends MessageLookupByLibrary { 18 | get localeName => 'es'; 19 | 20 | final messages = _notInlinedMessages(_notInlinedMessages); 21 | static _notInlinedMessages(_) => { 22 | "market": MessageLookupByLibrary.simpleMessage("MERCADO"), 23 | "portfolio": MessageLookupByLibrary.simpleMessage("CARTERA"), 24 | "title": MessageLookupByLibrary.simpleMessage("Acciones"), 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/i18n/stocks_en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Stocks", 3 | "@title": { 4 | "description": "Title for the Stocks application", 5 | "type": "text", 6 | "placeholders": {} 7 | }, 8 | 9 | "market": "MARKET", 10 | "@market": { 11 | "description": "Label for the Market tab", 12 | "type": "text", 13 | "placeholders": {} 14 | }, 15 | 16 | "portfolio": "PORTFOLIO", 17 | "@portfolio": { 18 | "description": "Label for the Portfolio tab", 19 | "type": "text", 20 | "placeholders": {} 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/i18n/stocks_es.arb: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Acciones", 3 | "@title": { 4 | "description": "Title for the Stocks application", 5 | "type": "text", 6 | "placeholders": {} 7 | }, 8 | 9 | "market": "MERCADO", 10 | "@market": { 11 | "description": "Label for the Market tab", 12 | "type": "text", 13 | "placeholders": {} 14 | }, 15 | 16 | "portfolio": "CARTERA", 17 | "@portfolio": { 18 | "description": "Label for the Portfolio tab", 19 | "type": "text", 20 | "placeholders": {} 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/model.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (C) 2019 Andrious Solutions 3 | /// 4 | /// This program is free software; you can redistribute it and/or 5 | /// modify it under the terms of the GNU General Public License 6 | /// as published by the Free Software Foundation; either version 3 7 | /// of the License, or any later version. 8 | /// 9 | /// You may obtain a copy of the License at 10 | /// 11 | /// http://www.apache.org/licenses/LICENSE-2.0 12 | /// 13 | /// 14 | /// Unless required by applicable law or agreed to in writing, software 15 | /// distributed under the License is distributed on an "AS IS" BASIS, 16 | /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | /// See the License for the specific language governing permissions and 18 | /// limitations under the License. 19 | /// 20 | /// Created 09 Jul 2019 21 | /// 22 | /// 23 | 24 | export 'package:stocks_mvc/src/app/model/stock_strings.dart'; 25 | 26 | export 'package:stocks_mvc/src/app/model/stock_data.dart'; 27 | 28 | export 'package:stocks_mvc/src/app/model/stock_symbol_viewer.dart'; 29 | 30 | export 'package:stocks_mvc/src/app/model/stock_list.dart'; 31 | -------------------------------------------------------------------------------- /lib/src/view.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (C) 2019 Andrious Solutions 3 | /// 4 | /// This program is free software; you can redistribute it and/or 5 | /// modify it under the terms of the GNU General Public License 6 | /// as published by the Free Software Foundation; either version 3 7 | /// of the License, or any later version. 8 | /// 9 | /// You may obtain a copy of the License at 10 | /// 11 | /// http://www.apache.org/licenses/LICENSE-2.0 12 | /// 13 | /// 14 | /// Unless required by applicable law or agreed to in writing, software 15 | /// distributed under the License is distributed on an "AS IS" BASIS, 16 | /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | /// See the License for the specific language governing permissions and 18 | /// limitations under the License. 19 | /// 20 | /// Created 09 Jul 2019 21 | /// 22 | /// 23 | 24 | export 'package:mvc_application/view.dart'; 25 | 26 | export 'package:stocks_mvc/src/app/view/stocks.dart'; 27 | 28 | export 'package:stocks_mvc/src/app/view/stock_types.dart'; 29 | 30 | export 'package:stocks_mvc/src/app/view/stock_settings.dart'; 31 | 32 | export 'package:stocks_mvc/src/home/view/stock_home.dart'; 33 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | analyzer: 5 | dependency: "direct main" 6 | description: 7 | name: analyzer 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "0.36.4" 11 | archive: 12 | dependency: transitive 13 | description: 14 | name: archive 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.0.11" 18 | args: 19 | dependency: "direct main" 20 | description: 21 | name: args 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.5.2" 25 | async: 26 | dependency: "direct main" 27 | description: 28 | name: async 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.4.0" 32 | boolean_selector: 33 | dependency: "direct dev" 34 | description: 35 | name: boolean_selector 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.0.5" 39 | charcode: 40 | dependency: "direct main" 41 | description: 42 | name: charcode 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.1.2" 46 | collection: 47 | dependency: "direct main" 48 | description: 49 | name: collection 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.14.11" 53 | connectivity: 54 | dependency: transitive 55 | description: 56 | name: connectivity 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "0.4.3+6" 60 | convert: 61 | dependency: "direct main" 62 | description: 63 | name: convert 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "2.1.1" 67 | crypto: 68 | dependency: "direct main" 69 | description: 70 | name: crypto 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "2.1.3" 74 | csslib: 75 | dependency: "direct main" 76 | description: 77 | name: csslib 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "0.16.1" 81 | dart_style: 82 | dependency: "direct main" 83 | description: 84 | name: dart_style 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "1.2.9" 88 | dbutils: 89 | dependency: transitive 90 | description: 91 | name: dbutils 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "0.3.4" 95 | device_info: 96 | dependency: transitive 97 | description: 98 | name: device_info 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "0.4.0+2" 102 | file: 103 | dependency: "direct dev" 104 | description: 105 | name: file 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "5.1.0" 109 | flutter: 110 | dependency: "direct main" 111 | description: flutter 112 | source: sdk 113 | version: "0.0.0" 114 | flutter_driver: 115 | dependency: "direct dev" 116 | description: flutter 117 | source: sdk 118 | version: "0.0.0" 119 | flutter_localizations: 120 | dependency: "direct main" 121 | description: flutter 122 | source: sdk 123 | version: "0.0.0" 124 | flutter_material_color_picker: 125 | dependency: transitive 126 | description: 127 | name: flutter_material_color_picker 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "1.0.2" 131 | flutter_test: 132 | dependency: "direct dev" 133 | description: flutter 134 | source: sdk 135 | version: "0.0.0" 136 | front_end: 137 | dependency: "direct main" 138 | description: 139 | name: front_end 140 | url: "https://pub.dartlang.org" 141 | source: hosted 142 | version: "0.1.19" 143 | fuchsia_remote_debug_protocol: 144 | dependency: transitive 145 | description: flutter 146 | source: sdk 147 | version: "0.0.0" 148 | glob: 149 | dependency: "direct main" 150 | description: 151 | name: glob 152 | url: "https://pub.dartlang.org" 153 | source: hosted 154 | version: "1.1.7" 155 | html: 156 | dependency: "direct main" 157 | description: 158 | name: html 159 | url: "https://pub.dartlang.org" 160 | source: hosted 161 | version: "0.14.0+2" 162 | http: 163 | dependency: "direct main" 164 | description: 165 | name: http 166 | url: "https://pub.dartlang.org" 167 | source: hosted 168 | version: "0.12.0+2" 169 | http_parser: 170 | dependency: "direct main" 171 | description: 172 | name: http_parser 173 | url: "https://pub.dartlang.org" 174 | source: hosted 175 | version: "3.1.3" 176 | image: 177 | dependency: transitive 178 | description: 179 | name: image 180 | url: "https://pub.dartlang.org" 181 | source: hosted 182 | version: "2.1.4" 183 | intl: 184 | dependency: "direct main" 185 | description: 186 | name: intl 187 | url: "https://pub.dartlang.org" 188 | source: hosted 189 | version: "0.16.0" 190 | intl_translation: 191 | dependency: "direct main" 192 | description: 193 | name: intl_translation 194 | url: "https://pub.dartlang.org" 195 | source: hosted 196 | version: "0.17.7" 197 | isolate: 198 | dependency: "direct main" 199 | description: 200 | name: isolate 201 | url: "https://pub.dartlang.org" 202 | source: hosted 203 | version: "2.0.2" 204 | json_rpc_2: 205 | dependency: "direct dev" 206 | description: 207 | name: json_rpc_2 208 | url: "https://pub.dartlang.org" 209 | source: hosted 210 | version: "2.1.0" 211 | kernel: 212 | dependency: "direct main" 213 | description: 214 | name: kernel 215 | url: "https://pub.dartlang.org" 216 | source: hosted 217 | version: "0.3.19" 218 | matcher: 219 | dependency: "direct dev" 220 | description: 221 | name: matcher 222 | url: "https://pub.dartlang.org" 223 | source: hosted 224 | version: "0.12.6" 225 | meta: 226 | dependency: "direct main" 227 | description: 228 | name: meta 229 | url: "https://pub.dartlang.org" 230 | source: hosted 231 | version: "1.1.8" 232 | mvc_application: 233 | dependency: "direct main" 234 | description: 235 | name: mvc_application 236 | url: "https://pub.dartlang.org" 237 | source: hosted 238 | version: "1.0.4" 239 | mvc_pattern: 240 | dependency: transitive 241 | description: 242 | name: mvc_pattern 243 | url: "https://pub.dartlang.org" 244 | source: hosted 245 | version: "3.4.1" 246 | package_config: 247 | dependency: "direct main" 248 | description: 249 | name: package_config 250 | url: "https://pub.dartlang.org" 251 | source: hosted 252 | version: "1.0.5" 253 | package_info: 254 | dependency: transitive 255 | description: 256 | name: package_info 257 | url: "https://pub.dartlang.org" 258 | source: hosted 259 | version: "0.4.0+5" 260 | path: 261 | dependency: "direct main" 262 | description: 263 | name: path 264 | url: "https://pub.dartlang.org" 265 | source: hosted 266 | version: "1.6.4" 267 | path_provider: 268 | dependency: transitive 269 | description: 270 | name: path_provider 271 | url: "https://pub.dartlang.org" 272 | source: hosted 273 | version: "1.1.2" 274 | pedantic: 275 | dependency: "direct main" 276 | description: 277 | name: pedantic 278 | url: "https://pub.dartlang.org" 279 | source: hosted 280 | version: "1.8.0+1" 281 | petitparser: 282 | dependency: "direct main" 283 | description: 284 | name: petitparser 285 | url: "https://pub.dartlang.org" 286 | source: hosted 287 | version: "2.4.0" 288 | platform: 289 | dependency: transitive 290 | description: 291 | name: platform 292 | url: "https://pub.dartlang.org" 293 | source: hosted 294 | version: "2.2.1" 295 | prefs: 296 | dependency: transitive 297 | description: 298 | name: prefs 299 | url: "https://pub.dartlang.org" 300 | source: hosted 301 | version: "3.0.0" 302 | process: 303 | dependency: transitive 304 | description: 305 | name: process 306 | url: "https://pub.dartlang.org" 307 | source: hosted 308 | version: "3.0.12" 309 | pub_semver: 310 | dependency: "direct main" 311 | description: 312 | name: pub_semver 313 | url: "https://pub.dartlang.org" 314 | source: hosted 315 | version: "1.4.2" 316 | quiver: 317 | dependency: "direct dev" 318 | description: 319 | name: quiver 320 | url: "https://pub.dartlang.org" 321 | source: hosted 322 | version: "2.0.5" 323 | shared_preferences: 324 | dependency: transitive 325 | description: 326 | name: shared_preferences 327 | url: "https://pub.dartlang.org" 328 | source: hosted 329 | version: "0.5.3+4" 330 | sky_engine: 331 | dependency: transitive 332 | description: flutter 333 | source: sdk 334 | version: "0.0.99" 335 | source_span: 336 | dependency: "direct main" 337 | description: 338 | name: source_span 339 | url: "https://pub.dartlang.org" 340 | source: hosted 341 | version: "1.5.5" 342 | sqflite: 343 | dependency: transitive 344 | description: 345 | name: sqflite 346 | url: "https://pub.dartlang.org" 347 | source: hosted 348 | version: "1.1.6+2" 349 | stack_trace: 350 | dependency: "direct dev" 351 | description: 352 | name: stack_trace 353 | url: "https://pub.dartlang.org" 354 | source: hosted 355 | version: "1.9.3" 356 | stream_channel: 357 | dependency: "direct dev" 358 | description: 359 | name: stream_channel 360 | url: "https://pub.dartlang.org" 361 | source: hosted 362 | version: "2.0.0" 363 | string_scanner: 364 | dependency: "direct main" 365 | description: 366 | name: string_scanner 367 | url: "https://pub.dartlang.org" 368 | source: hosted 369 | version: "1.0.5" 370 | synchronized: 371 | dependency: transitive 372 | description: 373 | name: synchronized 374 | url: "https://pub.dartlang.org" 375 | source: hosted 376 | version: "2.1.0+1" 377 | term_glyph: 378 | dependency: "direct main" 379 | description: 380 | name: term_glyph 381 | url: "https://pub.dartlang.org" 382 | source: hosted 383 | version: "1.1.0" 384 | test_api: 385 | dependency: "direct dev" 386 | description: 387 | name: test_api 388 | url: "https://pub.dartlang.org" 389 | source: hosted 390 | version: "0.2.11" 391 | typed_data: 392 | dependency: "direct main" 393 | description: 394 | name: typed_data 395 | url: "https://pub.dartlang.org" 396 | source: hosted 397 | version: "1.1.6" 398 | url_launcher: 399 | dependency: transitive 400 | description: 401 | name: url_launcher 402 | url: "https://pub.dartlang.org" 403 | source: hosted 404 | version: "5.1.0" 405 | uuid: 406 | dependency: transitive 407 | description: 408 | name: uuid 409 | url: "https://pub.dartlang.org" 410 | source: hosted 411 | version: "2.0.2" 412 | vector_math: 413 | dependency: "direct main" 414 | description: 415 | name: vector_math 416 | url: "https://pub.dartlang.org" 417 | source: hosted 418 | version: "2.0.8" 419 | vm_service_client: 420 | dependency: "direct dev" 421 | description: 422 | name: vm_service_client 423 | url: "https://pub.dartlang.org" 424 | source: hosted 425 | version: "0.2.6+2" 426 | watcher: 427 | dependency: "direct main" 428 | description: 429 | name: watcher 430 | url: "https://pub.dartlang.org" 431 | source: hosted 432 | version: "0.9.7+12" 433 | web_socket_channel: 434 | dependency: "direct dev" 435 | description: 436 | name: web_socket_channel 437 | url: "https://pub.dartlang.org" 438 | source: hosted 439 | version: "1.1.0" 440 | xml: 441 | dependency: transitive 442 | description: 443 | name: xml 444 | url: "https://pub.dartlang.org" 445 | source: hosted 446 | version: "3.5.0" 447 | yaml: 448 | dependency: "direct main" 449 | description: 450 | name: yaml 451 | url: "https://pub.dartlang.org" 452 | source: hosted 453 | version: "2.1.16" 454 | sdks: 455 | dart: ">=2.4.0 <3.0.0" 456 | flutter: ">=1.5.0 <2.0.0" 457 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: stocks_mvc 2 | description: The Flutter Stocks Example using MVC 3 | 4 | version: 1.2.0 5 | 6 | environment: 7 | sdk: ">=2.1.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | flutter_localizations: 13 | sdk: flutter 14 | intl: # 0.15.8 15 | intl_translation: # 0.17.5 16 | http: # 0.12.0+2 17 | isolate: # 2.0.2 18 | 19 | mvc_application: ^1.0.3 20 | # path: ../../packages/mvc_application 21 | # git: 22 | # url: git://github.com/AndriousSolutions/mvc_application.git 23 | 24 | analyzer: # 0.36.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 25 | args: # 1.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 26 | async: # 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 27 | charcode: # 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 28 | collection: # 1.14.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 29 | convert: # 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 30 | crypto: # 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 31 | csslib: # 0.16.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 32 | dart_style: # 1.2.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 33 | front_end: # 0.1.19 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 34 | glob: # 1.1.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 35 | html: # 0.14.0+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 36 | http_parser: # 3.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 37 | kernel: # 0.3.19 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 38 | meta: # 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 39 | package_config: # 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 40 | path: # 1.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 41 | pedantic: # 1.8.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 42 | petitparser: # 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 43 | pub_semver: # 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 44 | source_span: # 1.5.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 45 | string_scanner: # 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 46 | term_glyph: # 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 47 | typed_data: # 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 48 | vector_math: # 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 49 | watcher: # 0.9.7+10 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 50 | yaml: # 2.1.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 51 | 52 | dev_dependencies: 53 | flutter_test: 54 | sdk: flutter 55 | flutter_driver: 56 | sdk: flutter 57 | 58 | boolean_selector: # 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 59 | file: # 5.0.8+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 60 | json_rpc_2: # 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 61 | matcher: # 0.12.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 62 | quiver: # 2.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 63 | stack_trace: # 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 64 | stream_channel: # 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 65 | test_api: # 0.2.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 66 | vm_service_client: # 0.2.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 67 | web_socket_channel: # 1.0.14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" 68 | 69 | flutter: 70 | uses-material-design: true 71 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:stocks_mvc/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------