├── .metadata ├── lib ├── AppError.dart ├── App.dart ├── StatedWidget.dart ├── src │ ├── LoadingScreen.dart │ ├── FireBase.dart │ └── App.dart ├── Flux.dart ├── AppSettings.dart └── MVC.dart ├── .gitignore ├── test └── widget_test.dart ├── pubspec.yaml ├── README.md ├── LICENSE └── pubspec.lock /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 3ea4d06340a97a1e9d7cae97567c64e0569dcaa2 8 | channel: beta 9 | -------------------------------------------------------------------------------- /lib/AppError.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/foundation.dart'; 3 | 4 | class AppError extends FlutterError { 5 | 6 | AppError(String message) : super(message); 7 | 8 | // Called in FlutterError.reportError(details); 9 | static FlutterExceptionHandler onError = dumpErrorToServer; 10 | 11 | 12 | static void dumpErrorToServer(FlutterErrorDetails details, { bool forceReport: false }) { 13 | 14 | FlutterError.dumpErrorToConsole(details, forceReport: forceReport); 15 | } 16 | 17 | 18 | static void reportError(FlutterErrorDetails details) { 19 | 20 | FlutterError.reportError(details); 21 | } 22 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #.gitignore 2 | .DS_Store 3 | .atom/ 4 | .idea/ 5 | .vscode/ 6 | .packages 7 | .pub/ 8 | build/ 9 | 10 | packages 11 | .flutter-plugins 12 | doc/api/ 13 | .dart_tool/ 14 | 15 | # Don't ignore however for application packages 16 | pubspec.lock 17 | 18 | # Android Studio 19 | *.iml 20 | 21 | # Ignore App signing files 22 | *.jks 23 | 24 | # Don't need the Android stuff 25 | android/ 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 -------------------------------------------------------------------------------- /lib/App.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (C) 2018 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 21 Jun 2018 21 | /// 22 | 23 | library app_flutter; 24 | 25 | export 'MVC.dart'; 26 | export 'src/App.dart'; 27 | export 'src/FireBase.dart'; 28 | export 'src/LoadingScreen.dart'; -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter 3 | // provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to 4 | // find child widgets in the widget tree, read text, and verify that the values of widget properties 5 | // are correct. 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | import 'package:mvc/src/MyApp.dart'; 11 | 12 | void main() { 13 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 14 | // Build our app and trigger a frame. 15 | await tester.pumpWidget(new MyApp()); 16 | 17 | // Verify that our counter starts at 0. 18 | expect(find.text('0'), findsOneWidget); 19 | expect(find.text('1'), findsNothing); 20 | 21 | // Tap the '+' icon and trigger a frame. 22 | await tester.tap(find.byIcon(Icons.add)); 23 | await tester.pump(); 24 | 25 | // Verify that our counter has incremented. 26 | expect(find.text('0'), findsNothing); 27 | expect(find.text('1'), findsOneWidget); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /lib/StatedWidget.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// Classes for generic Flutter MVC approach. 5 | /// 6 | /// Free to redistribute and/or modify. 7 | /// http://www.apache.org/licenses/LICENSE-2.0 8 | /// Andrious Solutions Ltd. 2018-04-19 9 | /// 10 | 11 | import 'package:flutter/widgets.dart' show AppLifecycleState, BuildContext, StatefulWidget, VoidCallback, Widget; 12 | 13 | import 'MVC.dart' show MCView, MVController; 14 | 15 | abstract class StatedWidget extends MCView{ 16 | StatedWidget({this.data}) : super(data._con); 17 | 18 | final StatedData data; 19 | 20 | /// Where the magic happens. 21 | Widget build(BuildContext context); 22 | 23 | 24 | void onPressed(){ 25 | 26 | try { 27 | 28 | data?.onPressed(); 29 | 30 | } catch (ex){ 31 | 32 | } 33 | } 34 | 35 | void onTap(){ 36 | 37 | try { 38 | 39 | data?.onTap(); 40 | 41 | } catch (ex){ 42 | 43 | } 44 | } 45 | } 46 | 47 | 48 | 49 | class StatedData{ 50 | 51 | MVController _con; 52 | 53 | get con => _con; 54 | set con(MVController con){ 55 | /// Can only do it once. 56 | if(_con == null) _con = con; 57 | } 58 | /// A getter with a more descriptive name. 59 | get controller => _con; 60 | 61 | get widget => _con.widget; 62 | 63 | get context => _con.context; 64 | 65 | get mounted => _con.mounted; 66 | 67 | void initState() { 68 | /// called only when the [State] object is first created. 69 | } 70 | 71 | void deactivate() { 72 | /// called when this [State] object is removed from the tree. 73 | } 74 | 75 | void dispose() { 76 | /// called when this [State] object will never build again. 77 | } 78 | 79 | void didUpdateWidget(StatefulWidget oldWidget) { 80 | /// Override this method to respond when the [widget] changes. 81 | } 82 | 83 | void didChangeAppLifecycleState(AppLifecycleState state) { 84 | /// called when the app's lifecycle state changes. 85 | } 86 | 87 | void onPressed(){ 88 | /// Override to answer a call to widget.onPressed. 89 | } 90 | 91 | void onTap(){ 92 | /// Override to answer a call to widget.onTap. 93 | } 94 | 95 | setState(VoidCallback fn){ 96 | _con.setState(fn); 97 | } 98 | 99 | refresh() { 100 | _con.refresh(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/src/LoadingScreen.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (C) 2018 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 21 Jun 2018 21 | /// 22 | 23 | import 'package:flutter/material.dart'; 24 | 25 | /// 26 | /// This is just a basic `Scaffold` with a centered `CircularProgressIndicator` 27 | /// class right in the middle of the screen. 28 | /// 29 | /// It's copied from the `flutter_gallery` example project in flutter/flutter 30 | /// 31 | class LoadingScreen extends StatefulWidget { 32 | @override 33 | _LoadingScreenState createState() => new _LoadingScreenState(); 34 | } 35 | 36 | class _LoadingScreenState extends State 37 | with SingleTickerProviderStateMixin { 38 | AnimationController _controller; 39 | Animation _animation; 40 | 41 | @override 42 | void initState() { 43 | super.initState(); 44 | _controller = new AnimationController( 45 | duration: const Duration(milliseconds: 1500), 46 | vsync: this 47 | ) 48 | ..forward(); 49 | _animation = new CurvedAnimation( 50 | parent: _controller, 51 | curve: new Interval(0.0, 0.9, curve: Curves.fastOutSlowIn), 52 | reverseCurve: Curves.fastOutSlowIn 53 | ) 54 | ..addStatusListener((AnimationStatus status) { 55 | if (status == AnimationStatus.dismissed) { 56 | _controller.forward(); 57 | } else if (status == AnimationStatus.completed) { 58 | _controller.reverse(); 59 | } 60 | }); 61 | } 62 | 63 | @override 64 | void dispose() { 65 | _controller.stop(); 66 | super.dispose(); 67 | } 68 | 69 | @override 70 | Widget build(BuildContext context) { 71 | return new MaterialApp( 72 | home: Scaffold( 73 | appBar: new AppBar( 74 | title: new Text('Loading...') 75 | ), 76 | body: new Container( 77 | child: new AnimatedBuilder( 78 | animation: _animation, 79 | builder: (BuildContext context, Widget child) { 80 | return new Center(child: new CircularProgressIndicator()); 81 | } 82 | ) 83 | ) 84 | )); 85 | } 86 | } -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: mvc 2 | description: A Flutter MVC Framework 3 | 4 | environment: 5 | sdk: '>=2.0.0 <3.0.0' 6 | 7 | dependencies: 8 | flutter: 9 | sdk: flutter 10 | 11 | # The following adds the Cupertino Icons font to your application. 12 | # Use with the CupertinoIcons class for iOS style icons. 13 | cupertino_icons: ^0.1.0 14 | 15 | # https://pub.dartlang.org/packages/url_launcher 16 | # AppSettings.dart 17 | url_launcher: 3.0.3 18 | 19 | 20 | # mvc: 21 | # path: ../../mvc/ 22 | # git: 23 | # url: git://github.com/AndriousSolutions/mvc.git 24 | 25 | auth: 26 | path: ../../libs/auth 27 | # git: 28 | # url: git://github.com/AndriousSolutions/auth.git 29 | 30 | assets: 31 | path: ../../libs/assets 32 | # git: 33 | # url: git://github.com/AndriousSolutions/assets.git 34 | 35 | prefs: 36 | path: ../../libs/prefs 37 | # git: 38 | # url: git://github.com/AndriousSolutions/prefs.git 39 | 40 | # Files utilities from my library 41 | file_utils: 42 | path: ../../libs/files/file_utils 43 | # git: 44 | # url: git://github.com/AndriousSolutions/files.git 45 | 46 | # https://pub.dartlang.org/packages/firebase_database 47 | firebase_database: ^1.0.3 48 | 49 | # https://pub.dartlang.org/packages/connectivity#-changelog-tab- 50 | connectivity: "^0.3.0" 51 | 52 | dev_dependencies: 53 | flutter_test: 54 | sdk: flutter 55 | 56 | 57 | # For information on the generic Dart part of this file, see the 58 | # following page: https://www.dartlang.org/tools/pub/pubspec 59 | 60 | # The following section is specific to Flutter. 61 | flutter: 62 | 63 | # The following line ensures that the Material Icons font is 64 | # included with your application, so that you can use the icons in 65 | # the material Icons class. 66 | uses-material-design: true 67 | 68 | # To add assets to your application, add an assets section, like this: 69 | # assets: 70 | # - images/a_dot_burr.jpeg 71 | # - images/a_dot_ham.jpeg 72 | 73 | # An image asset can refer to one or more resolution-specific "variants", see 74 | # https://flutter.io/assets-and-images/#resolution-aware. 75 | 76 | # For details regarding adding assets from package dependencies, see 77 | # https://flutter.io/assets-and-images/#from-packages 78 | 79 | # To add custom fonts to your application, add a fonts section here, 80 | # in this "flutter" section. Each entry in this list should have a 81 | # "family" key with the font family name, and a "fonts" key with a 82 | # list giving the asset and other descriptors for the font. For 83 | # example: 84 | # fonts: 85 | # - family: Schyler 86 | # fonts: 87 | # - asset: fonts/Schyler-Regular.ttf 88 | # - asset: fonts/Schyler-Italic.ttf 89 | # style: italic 90 | # - family: Trajan Pro 91 | # fonts: 92 | # - asset: fonts/TrajanPro.ttf 93 | # - asset: fonts/TrajanPro_Bold.ttf 94 | # weight: 700 95 | # 96 | # For details regarding fonts from package dependencies, 97 | # see https://flutter.io/custom-fonts/#from-packages 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mvc 2 | ![Flutter + MVC](https://i.imgur.com/MdZJpMi.png) 3 | ###### The "Kiss" of Flutter Frameworks 4 | 5 | In keeping with the ["KISS Principle"](https://en.wikipedia.org/wiki/KISS_principle), this was an attempt 6 | to offer the MVC design pattern to Flutter in a simple 7 | and intrinsic fashion. Simply extend three classes, and you've then 8 | the means to implement the Model-View-Controller Design Pattern. 9 | 10 | **MVC** aims to decouple its three major components allowing 11 | for efficient code reuse and parallel development, each responsible 12 | for separate tasks: 13 | 14 | * Model - the Controller’s source for data 15 | * View - concerned with how data is displayed. 16 | * Controller - controls what data is displayed. ("the brains of the operation.") 17 | 18 | Ideally, this design pattern would have the code in the 'View' not directly 19 | affect or interfere with the code involved in the 'Model' or vice versa. 20 | While the 'Contoller', in this arrangement, controls the 21 | 'state' of the application during its lifecycle. The View can 'talk to' the Controller; 22 | The Controller can 'talk to' the Model. 23 | 24 | ![MVC Diagram](https://i.imgur.com/r4C1y28.png) 25 | 26 | Placed in your **main.dart** file, only four simple Classes are involved with the intent here 27 | to make the implementation of **MVC** in Flutter as intuitive as possible. 28 | Further notice how Dependency Injection is introduced in its purest form: 29 | Each **MVC** component is appropriately passed to another as a parameter. 30 | ![4 Classes](https://i.imgur.com/BqxMSeP.png) 31 | 32 | 33 | Simply inherit from the three "KISS" MVC Classes: 34 | AppModel, AppView and AppController 35 | 36 | Below, for example, are subclasses of those three MVC Classes. 37 | Now you fill them up with what will make up your Flutter applcation 38 | (with the appropriate code in the appropriate Class), and you're done! 39 | 40 | Model Class 41 | ![Model](https://i.imgur.com/mUIo8sq.png) 42 | View Class 43 | ![View](https://i.imgur.com/3N73L5D.png) 44 | Controller Class 45 | ![Controller](https://i.imgur.com/FVX3YHx.png) 46 | 47 | 48 | **Think of it this way...** 49 | > The 'View' is the **build()** function. 50 | 51 | > The 'Controller' is everything else. 52 | 53 | ## 'View' is the `build()` function 54 | You override the **build()** function found in the 'View' Class. 55 | Of course, this being a MVC design pattern, most of the 'data' the View needs to display 56 | or what have you comes from the Controller (e.g. _con.telephoneNumber()). 57 | The Controller is passed as a parameter to your 'View'. You are to let the Controller 58 | worry about what data to display, and the View worry about how to display it. 59 | Finally, like the traditional build function, you do the **setState()** function if you 60 | need it. Although most calls, by MVC design, will now instead come from the passed 61 | Controller reference (e.g _con.invoiceTotal()). 62 | 63 | ## 'Controller' is `StatefulWidget` + `State` 64 | And like the State object, you have access to the 65 | usual properties: **widget**, **context** and **mounted**. Further, 66 | the Controller (and it's data info.) will persist between **build()** function calls like 67 | the State object. 68 | 69 | ## Dart Programming Language 70 | Further, with Flutter written in the Dart programming language, you can have your 'View' 71 | and 'Controller' Class in a separate files (e.g. View.dart). And, unlike .java files, a file 72 | name need not correspond to a particular Class name inside. With your 'View' file, for example, 73 | you can have all the code responsible for the 'look and feel' of your app all in one place. 74 | 75 | In Dart, you can define code outside of classes. Variables, functions, getters, and 76 | setters can all live outside of classes. So not only the Class that overrides the build() function 77 | , but as many top-level functions, top-level variables and as many other Classes you like 78 | can make of the 'View', the 'Controller' or the 'Model' 79 | ---all in one file. Or in multiple files, and you simply import them. 80 | 81 | ## Try it out now 82 | In Flutter, its possible to 'try before you fork' this repo by inserting this git url 83 | into your metadata file, **pubspec.yaml**: `git://github.com/AndriousSolutions/mvc.git` 84 | ![mvc git url](https://i.imgur.com/gIc1ejh.png) 85 | 86 | ![Exclaimation Point](https://i.imgur.com/KfdDFVK.png) 87 | This repo won't last forever(Nothing ever does). Download it. Make it better. Then Share. 88 | 89 | ## Read the Article... 90 | [An MVC approach to Flutter](https://proandroiddev.com/mvc-in-flutter-ebfba2a78842) - for 91 | more insight of how this was intended to work. 92 | 93 | ## Getting Started with Flutter 94 | 95 | [Online Documentation](https://flutter.io/) -------------------------------------------------------------------------------- /lib/src/FireBase.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (C) 2018 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 23 Jun 2018 21 | 22 | import 'dart:async'; 23 | 24 | import 'dart:io' show Platform; 25 | 26 | import 'package:flutter/material.dart'; 27 | 28 | import 'package:connectivity/connectivity.dart'; 29 | 30 | import 'package:mvc/MVC.dart'; 31 | 32 | import 'package:file_utils/files.dart'; 33 | 34 | import 'package:file_utils/InstallFile.dart'; 35 | 36 | import 'package:prefs/prefs.dart'; 37 | 38 | import 'package:auth/Auth.dart'; 39 | 40 | import 'package:assets/Assets.dart'; 41 | 42 | import 'package:firebase_database/firebase_database.dart'; 43 | 44 | import 'package:firebase_core/firebase_core.dart'; 45 | 46 | import 'package:flutter/widgets.dart' show AppLifecycleState; 47 | 48 | import 'package:firebase_database/ui/firebase_animated_list.dart'; 49 | 50 | import 'package:firebase_auth/firebase_auth.dart' show FirebaseUser; 51 | 52 | class FireBase{ 53 | 54 | static FirebaseDatabase _database; 55 | 56 | static DatabaseReference _reference; 57 | 58 | static FirebaseApp _app; 59 | 60 | 61 | static init() async { 62 | 63 | _app = await FirebaseApp.configure( 64 | name: 'working-memory-823375', 65 | options: Platform.isIOS 66 | ? const FirebaseOptions( 67 | googleAppID: '1:297855924061:ios:c6de2b69b03a5be8', 68 | gcmSenderID: '297855924061', 69 | databaseURL: 'https://flutterfire-cd2f7.firebaseio.com', 70 | ) 71 | : const FirebaseOptions( 72 | googleAppID: '1:761477102089:android:ecbfc1109769ee45', 73 | apiKey: 'AIzaSyBpSv3EJpHCvQj4Qkur1X-Y3ooZWFo5i1E', 74 | databaseURL: 'https://working-memory-823375.firebaseio.com', 75 | storageBucket: 'working-memory-823375.appspot.com', 76 | ), 77 | ); 78 | 79 | FireBase.setPersistenceEnabled(true); 80 | } 81 | 82 | 83 | 84 | static dispose(){ 85 | FireBase.goOffline(); 86 | _database = null; 87 | _reference = null; 88 | } 89 | 90 | 91 | 92 | static void didChangeAppLifecycleState(AppLifecycleState state) { 93 | if(state == AppLifecycleState.paused){ 94 | FireBase.goOffline(); 95 | }else{ 96 | FireBase.goOnline(); 97 | } 98 | } 99 | 100 | 101 | 102 | static DatabaseReference dataRef(String name) { 103 | return FireBase.reference()?.child(name); 104 | } 105 | 106 | 107 | 108 | static FirebaseApp get app => FireBase._getDB()?.app; 109 | 110 | 111 | 112 | static String get databaseURL => FireBase._getDB()?.databaseURL ?? 'unknown'; 113 | 114 | 115 | 116 | static FirebaseDatabase get instance => FireBase._getDB(); 117 | static FirebaseDatabase _getDB(){ 118 | if(_database == null){ 119 | 120 | _database = FirebaseDatabase(app: _app, databaseURL: 'https://working-memory-823375.firebaseio.com'); 121 | 122 | // _app.options.then((options){ 123 | // var settings = options; 124 | // var test = settings.databaseURL; 125 | // }); 126 | 127 | } 128 | return _database; 129 | } 130 | 131 | 132 | FireBase(); 133 | 134 | static DatabaseReference reference(){ 135 | if(_reference == null) _reference = FireBase._getDB()?.reference(); 136 | return _reference; 137 | } 138 | 139 | 140 | 141 | static bool get isPersistenceEnabled => _persistenceEnabled; 142 | static bool _persistenceEnabled; 143 | 144 | 145 | 146 | static Future setPersistenceEnabled(bool enabled) async { 147 | 148 | FirebaseDatabase db = FireBase._getDB(); 149 | 150 | _persistenceEnabled = await db?.setPersistenceEnabled(enabled); 151 | 152 | _persistenceEnabled = _persistenceEnabled ?? false; 153 | 154 | return Future.value(_persistenceEnabled); 155 | } 156 | 157 | 158 | static Future setPersistenceCacheSizeBytes(int cacheSize) => FireBase._getDB()?.setPersistenceCacheSizeBytes(cacheSize) ?? Future.value(false); 159 | 160 | 161 | 162 | static Future goOnline() => FireBase._getDB()?.goOnline(); 163 | 164 | 165 | 166 | static Future goOffline() => _database != null ? FireBase._getDB()?.goOffline() : Future.value(); 167 | 168 | 169 | 170 | static Future purgeOutstandingWrites() => FireBase._getDB()?.purgeOutstandingWrites(); 171 | } 172 | 173 | 174 | -------------------------------------------------------------------------------- /lib/Flux.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/foundation.dart'; 3 | 4 | import 'package:flutter/widgets.dart'; 5 | 6 | abstract class DispatchWidget extends StatefulWidget{ 7 | const DispatchWidget({ 8 | this.stores, 9 | Key key}) : super(key: key); 10 | 11 | 12 | final List stores; 13 | 14 | /// Provide the 'tree of views' 15 | Widget build(BuildContext context); 16 | 17 | 18 | @override 19 | createState(){ 20 | 21 | var state = new _FluxState(this, stores); 22 | 23 | for (var store in stores) { 24 | /// Get a reference of the State object for each Store. 25 | store.state = state; 26 | } 27 | 28 | return state; 29 | } 30 | 31 | 32 | 33 | void onPressed(Widget widget){ 34 | 35 | for (var store in stores){ 36 | 37 | try { 38 | 39 | store.onPressed(widget); 40 | 41 | } on Exception catch (ex){ 42 | 43 | } 44 | } 45 | } 46 | 47 | 48 | void onTap(Widget widget){ 49 | 50 | for (var store in stores){ 51 | 52 | try { 53 | 54 | store.onTap(widget); 55 | 56 | } on Exception catch (ex){ 57 | 58 | } 59 | } 60 | } 61 | } 62 | 63 | 64 | /// The State Object 65 | class _FluxState extends State with WidgetsBindingObserver { 66 | _FluxState( 67 | this.widget, 68 | this._stores, 69 | ): assert(widget != null), assert(_stores != null); 70 | 71 | final DispatchWidget widget; 72 | 73 | final List _stores; 74 | 75 | @override 76 | void initState(){ 77 | /// called when the [State] object is first created. 78 | super.initState(); 79 | 80 | for (var store in _stores) { 81 | 82 | store.initState(); 83 | } 84 | WidgetsBinding.instance.addObserver(this); 85 | } 86 | 87 | @override 88 | void deactivate(){ 89 | /// called when this [State] object is removed from the tree. 90 | 91 | for (var store in _stores) { 92 | 93 | store.deactivate(); 94 | } 95 | super.deactivate(); 96 | } 97 | 98 | @override 99 | void dispose(){ 100 | /// called when this [State] object will never build again. 101 | WidgetsBinding.instance.removeObserver(this); 102 | 103 | for (var store in _stores) { 104 | 105 | store.dispose(); 106 | } 107 | super.dispose(); 108 | } 109 | 110 | @override 111 | void didUpdateWidget(DispatchWidget oldWidget) { 112 | /// Override this method to respond when the [widget] changes (e.g., to start 113 | /// implicit animations). 114 | /// The framework always calls [build] after calling [didUpdateWidget], which 115 | /// means any calls to [setState] in [didUpdateWidget] are redundant. 116 | super.didUpdateWidget(oldWidget); 117 | 118 | for (var store in _stores) { 119 | 120 | store.didUpdateWidget(oldWidget); 121 | } 122 | } 123 | 124 | @override 125 | void didChangeAppLifecycleState(AppLifecycleState state) { 126 | /// Passing either the values AppLifecycleState.paused or AppLifecycleState.resumed. 127 | for (var store in _stores) { 128 | 129 | store.didChangeAppLifecycleState(state); 130 | } 131 | } 132 | 133 | void reState(VoidCallback fn) { 134 | /// Calls the 'real' setState() 135 | /// setState() can only be called within this class. 136 | setState(fn); 137 | } 138 | 139 | /// The View 140 | Widget build(BuildContext context){ 141 | /// Here's where the magic happens. 142 | return widget.build(context); 143 | } 144 | } 145 | 146 | 147 | /// 148 | /// Extend this class and instantiate with a factory constructor. 149 | //class newStore extends Store{ 150 | // factory newStore() { 151 | // 152 | // if(_store == null){ 153 | // 154 | // _store = new newStore._getInstance(); 155 | // 156 | // return _store; 157 | // }else{ 158 | // 159 | // return _store; 160 | // } 161 | // } 162 | // static newStore _store; 163 | // newStore._getInstance(); 164 | //} 165 | class Store{ 166 | 167 | /// A reference to the State object. 168 | _FluxState state; 169 | 170 | /// Allow for the widget getter in the build() function. 171 | get widget => state?.widget; 172 | 173 | /// BuildContext is always useful in the build() function. 174 | get context => state?.context; 175 | 176 | /// Ensure the State Object is 'mounted' and not being terminated. 177 | get mounted => state?.mounted ?? false; 178 | 179 | 180 | void onPressed(Widget widget){ 181 | 182 | } 183 | 184 | 185 | void onTap(Widget widget){ 186 | 187 | } 188 | 189 | void initState(){ 190 | 191 | } 192 | 193 | void deactivate(){ 194 | 195 | } 196 | 197 | @mustCallSuper 198 | void dispose(){ 199 | 200 | state = null; 201 | } 202 | 203 | void didUpdateWidget(DispatchWidget oldWidget) { 204 | 205 | } 206 | 207 | void didChangeAppLifecycleState(AppLifecycleState state) { 208 | 209 | } 210 | 211 | 212 | void setState(VoidCallback fn){ 213 | /// Call the State object's setState() function. 214 | state?.reState(fn); 215 | } 216 | 217 | void refresh(){ 218 | /// Refresh the Widget Tree Interface 219 | state?.reState(() {}); 220 | } 221 | } 222 | 223 | 224 | 225 | 226 | 227 | -------------------------------------------------------------------------------- /lib/AppSettings.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (C) 2018 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 11 Sep 2018 21 | /// 22 | 23 | import 'dart:async'; 24 | 25 | import 'package:flutter/material.dart' show AlignmentDirectional, BoxConstraints, BuildContext, Container, DefaultTextStyle, EdgeInsets, EdgeInsetsDirectional, FlatButton, IconTheme, Key, MediaQuery, MergeSemantics, StatelessWidget, Text, TextOverflow, TextSpan, TextStyle, Theme, ThemeData, VoidCallback, Widget, required, showAboutDialog; 26 | 27 | import 'package:flutter/gestures.dart'; 28 | 29 | import 'package:url_launcher/url_launcher.dart'; 30 | 31 | import 'package:flutter/foundation.dart' as Plat show defaultTargetPlatform ; 32 | 33 | 34 | import 'package:mvc/App.dart'; 35 | 36 | class AppSettings{ 37 | 38 | static get defaultTargetPlatform => Plat.defaultTargetPlatform; 39 | 40 | static StatelessWidget tapText(String text, VoidCallback onTap, {TextStyle style}){ 41 | return _TapText(text, onTap, style: style); 42 | } 43 | 44 | static _LinkTextSpan linkTextSpan({ TextStyle style, String url, String text }){ 45 | return _LinkTextSpan(style: style, url: url, text: text); 46 | } 47 | 48 | static void showAbout({ 49 | @required BuildContext context, 50 | String applicationName, 51 | String applicationVersion, 52 | Widget applicationIcon, 53 | String applicationLegalese, 54 | List children, 55 | }) { 56 | showAboutDialog( 57 | context: context, 58 | applicationName: applicationName, 59 | applicationVersion: applicationVersion, 60 | applicationIcon: applicationIcon, 61 | applicationLegalese: applicationLegalese, 62 | children: children, 63 | ); 64 | } 65 | } 66 | 67 | 68 | class _TapText extends StatelessWidget { 69 | const _TapText(this.text, this.onTap, {this.style}); 70 | 71 | final String text; 72 | final VoidCallback onTap; 73 | final TextStyle style; 74 | 75 | @override 76 | Widget build(BuildContext context) { 77 | return _OptionsItem( 78 | child: _FlatButton( 79 | onPressed: onTap, 80 | child: Text(text), 81 | style: style, 82 | ), 83 | ); 84 | } 85 | } 86 | 87 | 88 | class _OptionsItem extends StatelessWidget { 89 | const _OptionsItem({ Key key, this.child }) : super(key: key); 90 | 91 | final Widget child; 92 | 93 | @override 94 | Widget build(BuildContext context) { 95 | 96 | return MergeSemantics( 97 | child: Container( 98 | constraints: BoxConstraints(minHeight: 48.0 * MediaQuery.textScaleFactorOf(context)), 99 | padding: EdgeInsetsDirectional.only(start: 56.0), 100 | alignment: AlignmentDirectional.centerStart, 101 | child: DefaultTextStyle( 102 | style: DefaultTextStyle.of(context).style, 103 | maxLines: 2, 104 | overflow: TextOverflow.fade, 105 | child: IconTheme( 106 | data: Theme.of(context).primaryIconTheme, 107 | child: child, 108 | ), 109 | ), 110 | ), 111 | ); 112 | } 113 | } 114 | 115 | class _FlatButton extends StatelessWidget { 116 | const _FlatButton({ Key key, 117 | this.onPressed, 118 | this.child, 119 | this.style,}) : super(key: key); 120 | 121 | final VoidCallback onPressed; 122 | final Widget child; 123 | final TextStyle style; 124 | 125 | @override 126 | Widget build(BuildContext context) { 127 | return FlatButton( 128 | padding: EdgeInsets.zero, 129 | onPressed: onPressed, 130 | child: DefaultTextStyle( 131 | style: style ?? Theme.of(context).primaryTextTheme.subhead, 132 | child: child, 133 | ), 134 | ); 135 | } 136 | } 137 | 138 | class _LinkTextSpan extends TextSpan { 139 | 140 | // Beware! 141 | // 142 | // This class is only safe because the TapGestureRecognizer is not 143 | // given a deadline and therefore never allocates any resources. 144 | // 145 | // In any other situation -- setting a deadline, using any of the less trivial 146 | // recognizers, etc -- you would have to manage the gesture recognizer's 147 | // lifetime and call dispose() when the TextSpan was no longer being rendered. 148 | // 149 | // Since TextSpan itself is @immutable, this means that you would have to 150 | // manage the recognizer from outside the TextSpan, e.g. in the State of a 151 | // stateful widget that then hands the recognizer to the TextSpan. 152 | 153 | _LinkTextSpan({ TextStyle style, String url, String text }) : super( 154 | style: style, 155 | text: text ?? url, 156 | recognizer: TapGestureRecognizer()..onTap = () { 157 | launch(url, forceSafariVC: false); 158 | } 159 | ); 160 | } 161 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/MVC.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// Classes for generic Flutter MVC approach. 5 | /// 6 | /// Free to redistribute and/or modify. 7 | /// http://www.apache.org/licenses/LICENSE-2.0 8 | /// Andrious Solutions Ltd. 2018-04-19 9 | /// 10 | 11 | library mvc; 12 | 13 | import 'package:flutter/material.dart' show AppLifecycleState, BuildContext, GlobalKey, Image, Key, RenderObject, State, StatefulWidget, VoidCallback, Widget, WidgetsBinding, WidgetsBindingObserver, mustCallSuper; 14 | 15 | import 'package:flutter/widgets.dart' show AppLifecycleState, BuildContext, Key, RenderObject, State, StatefulWidget, VoidCallback, Widget, WidgetsBinding, WidgetsBindingObserver; 16 | 17 | import 'package:flutter/material.dart' show AppLifecycleState, BuildContext, Key, RenderObject, State, StatefulWidget, VoidCallback, Widget, WidgetsBinding, WidgetsBindingObserver, mustCallSuper; 18 | 19 | import 'package:flutter/foundation.dart' show BindingBase, DiagnosticPropertiesBuilder, Key, VoidCallback, mustCallSuper; 20 | 21 | 22 | /// The State Object for this MVC Design Pattern 23 | class MVController extends State with WidgetsBindingObserver{ 24 | 25 | /// Allow for Mixins 26 | MVController(): super(); 27 | 28 | 29 | /// The View 30 | @override 31 | Widget build(BuildContext context){ 32 | /// Here's where the magic happens. 33 | _build = _view.build(context); 34 | return _build; 35 | } 36 | 37 | 38 | get view => _view; 39 | MCView _view; 40 | 41 | 42 | get buildWidget => _build; 43 | Widget _build; 44 | 45 | /// The framework will call this method exactly once. 46 | /// Only when the [State] object is first created. 47 | /// 48 | /// Override this method to perform initialization that depends on the 49 | /// location at which this object was inserted into the tree. 50 | /// (i.e. Subscribe to another object it depends on during [initState], 51 | /// unsubscribe object and subscribe to a new object when it changes in 52 | /// [didUpdateWidget], and then unsubscribe from the object in [dispose]. 53 | @override 54 | void initState(){ 55 | /// called when the [State] object is first created. 56 | super.initState(); 57 | _view?.widget = widget; 58 | WidgetsBinding.instance.addObserver(this); 59 | } 60 | 61 | /// The framework calls this method whenever it removes this [State] object 62 | /// from the tree. It might reinsert it into another part of the tree. 63 | /// Subclasses should override this method to clean up any links between 64 | /// this object and other elements in the tree (e.g. if you have provided an 65 | /// ancestor with a pointer to a descendant's [RenderObject]). 66 | @override 67 | void deactivate(){ 68 | super.deactivate(); 69 | } 70 | 71 | /// The framework calls this method when this [State] object will never 72 | /// build again. The [State] object's lifecycle is terminated. 73 | /// Subclasses should override this method to release any resources retained 74 | /// by this object (e.g., stop any active animations). 75 | @override 76 | @mustCallSuper 77 | void dispose(){ 78 | /// called when this [State] object will never build again. 79 | _build = null; 80 | WidgetsBinding.instance.removeObserver(this); 81 | super.dispose(); 82 | } 83 | 84 | 85 | @override 86 | void didUpdateWidget(StatefulWidget oldWidget) { 87 | /// Override this method to respond when the [widget] changes (e.g., to start 88 | /// implicit animations). 89 | /// The framework always calls [build] after calling [didUpdateWidget], which 90 | /// means any calls to [setState] in [didUpdateWidget] are redundant. 91 | super.didUpdateWidget(oldWidget); 92 | } 93 | 94 | 95 | @override 96 | void didChangeAppLifecycleState(AppLifecycleState state) { 97 | /// Passing either the values AppLifecycleState.paused or AppLifecycleState.resumed. 98 | } 99 | 100 | 101 | void setState(fn){ 102 | /// Call the State object's setState() function. 103 | super.setState(fn); 104 | } 105 | 106 | void refresh(){ 107 | /// Refresh the Widget Tree Interface 108 | setState(() {}); 109 | } 110 | } 111 | 112 | 113 | 114 | /// The View of MVC 115 | abstract class MCView{ 116 | 117 | MCView([this._con]){ 118 | _con?._view = this; 119 | } 120 | 121 | MVController _con; 122 | 123 | get con => controller; 124 | set con(MVController c) => controller = c; 125 | /// A getter with a more descriptive name. 126 | get controller => _con; 127 | set controller(MVController c){ 128 | assert(_con == null, "A Controller already assigned!"); 129 | _con = c; 130 | } 131 | 132 | /// Allow for the widget getter in the build() function. 133 | StatefulWidget _widget; 134 | get widget => _widget ?? this; 135 | set widget(StatefulWidget w) => _widget = w; 136 | 137 | /// BuildContext is always useful in the build() function. 138 | get context => _con?.context; 139 | 140 | /// Ensure the State Object is 'mounted' and not being terminated. 141 | get mounted => _con?.mounted; 142 | 143 | /// Provide the setState() function to the build() function. 144 | /// Although it's the Controller that should do all the calling of setState() function. 145 | setState(fn){ 146 | _con?.setState(fn); 147 | } 148 | 149 | /// Provide 'the view' 150 | Widget build(BuildContext context); 151 | } 152 | 153 | 154 | 155 | 156 | abstract class StatedWidget { 157 | 158 | 159 | StatedWidget(){ 160 | _widget._state._statedWidget = this; 161 | } 162 | 163 | final WidgetStated _widget = WidgetStated(key: GlobalKey()); 164 | 165 | /// This is where the magic happens. 166 | Widget build(BuildContext context); 167 | 168 | 169 | void initState(){ 170 | 171 | } 172 | 173 | void deactivate(){ 174 | 175 | } 176 | 177 | void dispose(){ 178 | 179 | } 180 | 181 | void reassemble() { 182 | 183 | } 184 | 185 | void didUpdateWidget(StatefulWidget oldWidget) { 186 | 187 | } 188 | 189 | void didChangeAppLifecycleState(AppLifecycleState state) { 190 | 191 | } 192 | 193 | void didChangeDependencies() { 194 | 195 | } 196 | 197 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 198 | _widget._state.debugFillProperties(properties); 199 | } 200 | 201 | get state => _widget._state; 202 | 203 | get widget => _widget; 204 | 205 | get key => _widget.key; 206 | 207 | get context => _widget._state.context; 208 | 209 | get mounted => _widget._state.mounted; 210 | 211 | void setState(fn){ 212 | /// Call the State object's setState() function. 213 | _widget._state._reState(fn); 214 | } 215 | 216 | void refresh(){ 217 | /// Refresh the Widget Tree Interface 218 | setState(() {}); 219 | } 220 | } 221 | 222 | 223 | class WidgetStated extends StatefulWidget { 224 | WidgetStated({ Key key }) : super(key: key); 225 | 226 | final StatedWidgetState _state = StatedWidgetState(); 227 | 228 | @override 229 | State createState(){ 230 | return _state; 231 | } 232 | } 233 | 234 | 235 | class StatedWidgetState extends State with WidgetsBindingObserver{ 236 | 237 | StatedWidget _statedWidget; 238 | 239 | @override 240 | Widget build(BuildContext context){ 241 | /// Here's where the magic happens. 242 | return _statedWidget.build(context); 243 | } 244 | 245 | 246 | /// The framework will call this method exactly once. 247 | /// Only when the [State] object is first created. 248 | /// 249 | /// Override this method to perform initialization that depends on the 250 | /// location at which this object was inserted into the tree. 251 | /// (i.e. Subscribe to another object it depends on during [initState], 252 | /// unsubscribe object and subscribe to a new object when it changes in 253 | /// [didUpdateWidget], and then unsubscribe from the object in [dispose]. 254 | @override 255 | void initState(){ 256 | super.initState(); 257 | _statedWidget.initState(); 258 | } 259 | 260 | 261 | /// The framework calls this method whenever it removes this [State] object 262 | /// from the tree. It might reinsert it into another part of the tree. 263 | /// Subclasses should override this method to clean up any links between 264 | /// this object and other elements in the tree (e.g. if you have provided an 265 | /// ancestor with a pointer to a descendant's [RenderObject]). 266 | @override 267 | void deactivate(){ 268 | _statedWidget.deactivate(); 269 | super.deactivate(); 270 | } 271 | 272 | 273 | /// The framework calls this method when this [State] object will never 274 | /// build again. The [State] object's lifecycle is terminated. 275 | /// Subclasses should override this method to release any resources retained 276 | /// by this object (e.g., stop any active animations). 277 | @override 278 | void dispose(){ 279 | _statedWidget.dispose(); 280 | /// called when this [State] object will never build again. 281 | super.dispose(); 282 | } 283 | 284 | 285 | /// Notify the framework that the internal state of this object has changed. 286 | void _reState(fn){ 287 | if(mounted){ 288 | /// Call the State object's setState() function. 289 | super.setState(fn); 290 | } 291 | } 292 | 293 | 294 | /// Called whenever the application is reassembled during debugging, for 295 | /// example during hot reload. 296 | /// See also: 297 | /// 298 | /// * [BindingBase.reassembleApplication]. 299 | /// * [Image], which uses this to reload images. 300 | @mustCallSuper 301 | void reassemble() { 302 | super.reassemble(); 303 | _statedWidget.reassemble(); 304 | } 305 | 306 | @override 307 | void didUpdateWidget(StatefulWidget oldWidget) { 308 | /// Override this method to respond when the [widget] changes (e.g., to start 309 | /// implicit animations). 310 | /// The framework always calls [build] after calling [didUpdateWidget], which 311 | /// means any calls to [setState] in [didUpdateWidget] are redundant. 312 | super.didUpdateWidget(oldWidget); 313 | _statedWidget.didUpdateWidget(oldWidget); 314 | } 315 | 316 | 317 | @override 318 | void didChangeAppLifecycleState(AppLifecycleState state) { 319 | /// Passing either the values AppLifecycleState.paused or AppLifecycleState.resumed. 320 | _statedWidget.didChangeAppLifecycleState(state); 321 | } 322 | 323 | /// Called when a dependency of this [State] object changes. 324 | void didChangeDependencies() { 325 | super.didChangeDependencies(); 326 | _statedWidget.didChangeDependencies(); 327 | } 328 | } 329 | 330 | 331 | 332 | 333 | /* Copy n' paste this code to then implement these Classes: 334 | 335 | import 'package:mvc/MVC.dart'; 336 | 337 | import 'Controller.dart'; 338 | 339 | class View extends MCView{ 340 | View(this._con): super(con: _con); 341 | 342 | final Controller _con; 343 | 344 | } 345 | 346 | or If you don't want to use a parameter: 347 | 348 | import 'package:mvc/MVC.dart'; 349 | 350 | import 'Controller.dart'; 351 | 352 | class View extends MCView{ 353 | View(): _con = Controller() { 354 | this.con = _con; 355 | } 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | import 'MVC.dart'; 365 | 366 | class Controller extends MVController { 367 | Controller(this._model); 368 | 369 | final Model _model; 370 | } 371 | 372 | */ -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://www.dartlang.org/tools/pub/glossary#lockfile 3 | packages: 4 | analyzer: 5 | dependency: transitive 6 | description: 7 | name: analyzer 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "0.31.2-alpha.2" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.4.3" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.0.7" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.0.3" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.1" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.14.6" 46 | connectivity: 47 | dependency: "direct main" 48 | description: 49 | name: connectivity 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "0.3.1" 53 | convert: 54 | dependency: transitive 55 | description: 56 | name: convert 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.0.1" 60 | crypto: 61 | dependency: transitive 62 | description: 63 | name: crypto 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "2.0.3" 67 | csslib: 68 | dependency: transitive 69 | description: 70 | name: csslib 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "0.14.4" 74 | cupertino_icons: 75 | dependency: "direct main" 76 | description: 77 | name: cupertino_icons 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "0.1.2" 81 | file_utils: 82 | dependency: "direct main" 83 | description: 84 | path: "." 85 | ref: HEAD 86 | resolved-ref: "25ab06bf7ff9453c905f562680444f3b0df5a0a5" 87 | url: "git://github.com/AndriousSolutions/files.git" 88 | source: git 89 | version: "0.0.1" 90 | flutter: 91 | dependency: "direct main" 92 | description: flutter 93 | source: sdk 94 | version: "0.0.0" 95 | flutter_test: 96 | dependency: "direct dev" 97 | description: flutter 98 | source: sdk 99 | version: "0.0.0" 100 | front_end: 101 | dependency: transitive 102 | description: 103 | name: front_end 104 | url: "https://pub.dartlang.org" 105 | source: hosted 106 | version: "0.1.0-alpha.12" 107 | glob: 108 | dependency: transitive 109 | description: 110 | name: glob 111 | url: "https://pub.dartlang.org" 112 | source: hosted 113 | version: "1.1.5" 114 | html: 115 | dependency: transitive 116 | description: 117 | name: html 118 | url: "https://pub.dartlang.org" 119 | source: hosted 120 | version: "0.13.3" 121 | http: 122 | dependency: transitive 123 | description: 124 | name: http 125 | url: "https://pub.dartlang.org" 126 | source: hosted 127 | version: "0.11.3+16" 128 | http_multi_server: 129 | dependency: transitive 130 | description: 131 | name: http_multi_server 132 | url: "https://pub.dartlang.org" 133 | source: hosted 134 | version: "2.0.4" 135 | http_parser: 136 | dependency: transitive 137 | description: 138 | name: http_parser 139 | url: "https://pub.dartlang.org" 140 | source: hosted 141 | version: "3.1.2" 142 | io: 143 | dependency: transitive 144 | description: 145 | name: io 146 | url: "https://pub.dartlang.org" 147 | source: hosted 148 | version: "0.3.2+1" 149 | js: 150 | dependency: transitive 151 | description: 152 | name: js 153 | url: "https://pub.dartlang.org" 154 | source: hosted 155 | version: "0.6.1" 156 | kernel: 157 | dependency: transitive 158 | description: 159 | name: kernel 160 | url: "https://pub.dartlang.org" 161 | source: hosted 162 | version: "0.3.0-alpha.12" 163 | logging: 164 | dependency: transitive 165 | description: 166 | name: logging 167 | url: "https://pub.dartlang.org" 168 | source: hosted 169 | version: "0.11.3+1" 170 | matcher: 171 | dependency: transitive 172 | description: 173 | name: matcher 174 | url: "https://pub.dartlang.org" 175 | source: hosted 176 | version: "0.12.2+1" 177 | meta: 178 | dependency: transitive 179 | description: 180 | name: meta 181 | url: "https://pub.dartlang.org" 182 | source: hosted 183 | version: "1.1.5" 184 | mime: 185 | dependency: transitive 186 | description: 187 | name: mime 188 | url: "https://pub.dartlang.org" 189 | source: hosted 190 | version: "0.9.6" 191 | multi_server_socket: 192 | dependency: transitive 193 | description: 194 | name: multi_server_socket 195 | url: "https://pub.dartlang.org" 196 | source: hosted 197 | version: "1.0.1" 198 | node_preamble: 199 | dependency: transitive 200 | description: 201 | name: node_preamble 202 | url: "https://pub.dartlang.org" 203 | source: hosted 204 | version: "1.4.1" 205 | package_config: 206 | dependency: transitive 207 | description: 208 | name: package_config 209 | url: "https://pub.dartlang.org" 210 | source: hosted 211 | version: "1.0.3" 212 | package_resolver: 213 | dependency: transitive 214 | description: 215 | name: package_resolver 216 | url: "https://pub.dartlang.org" 217 | source: hosted 218 | version: "1.0.2" 219 | path: 220 | dependency: transitive 221 | description: 222 | name: path 223 | url: "https://pub.dartlang.org" 224 | source: hosted 225 | version: "1.5.1" 226 | path_provider: 227 | dependency: transitive 228 | description: 229 | name: path_provider 230 | url: "https://pub.dartlang.org" 231 | source: hosted 232 | version: "0.4.1" 233 | plugin: 234 | dependency: transitive 235 | description: 236 | name: plugin 237 | url: "https://pub.dartlang.org" 238 | source: hosted 239 | version: "0.2.0+2" 240 | pool: 241 | dependency: transitive 242 | description: 243 | name: pool 244 | url: "https://pub.dartlang.org" 245 | source: hosted 246 | version: "1.3.4" 247 | prefs: 248 | dependency: "direct main" 249 | description: 250 | path: "..\\prefs" 251 | relative: true 252 | source: path 253 | version: "0.0.1" 254 | pub_semver: 255 | dependency: transitive 256 | description: 257 | name: pub_semver 258 | url: "https://pub.dartlang.org" 259 | source: hosted 260 | version: "1.4.1" 261 | quiver: 262 | dependency: transitive 263 | description: 264 | name: quiver 265 | url: "https://pub.dartlang.org" 266 | source: hosted 267 | version: "0.29.0+1" 268 | shared_preferences: 269 | dependency: transitive 270 | description: 271 | name: shared_preferences 272 | url: "https://pub.dartlang.org" 273 | source: hosted 274 | version: "0.4.2" 275 | shelf: 276 | dependency: transitive 277 | description: 278 | name: shelf 279 | url: "https://pub.dartlang.org" 280 | source: hosted 281 | version: "0.7.3" 282 | shelf_packages_handler: 283 | dependency: transitive 284 | description: 285 | name: shelf_packages_handler 286 | url: "https://pub.dartlang.org" 287 | source: hosted 288 | version: "1.0.3" 289 | shelf_static: 290 | dependency: transitive 291 | description: 292 | name: shelf_static 293 | url: "https://pub.dartlang.org" 294 | source: hosted 295 | version: "0.2.7" 296 | shelf_web_socket: 297 | dependency: transitive 298 | description: 299 | name: shelf_web_socket 300 | url: "https://pub.dartlang.org" 301 | source: hosted 302 | version: "0.2.2" 303 | sky_engine: 304 | dependency: transitive 305 | description: flutter 306 | source: sdk 307 | version: "0.0.99" 308 | source_map_stack_trace: 309 | dependency: transitive 310 | description: 311 | name: source_map_stack_trace 312 | url: "https://pub.dartlang.org" 313 | source: hosted 314 | version: "1.1.4" 315 | source_maps: 316 | dependency: transitive 317 | description: 318 | name: source_maps 319 | url: "https://pub.dartlang.org" 320 | source: hosted 321 | version: "0.10.5" 322 | source_span: 323 | dependency: transitive 324 | description: 325 | name: source_span 326 | url: "https://pub.dartlang.org" 327 | source: hosted 328 | version: "1.4.0" 329 | stack_trace: 330 | dependency: transitive 331 | description: 332 | name: stack_trace 333 | url: "https://pub.dartlang.org" 334 | source: hosted 335 | version: "1.9.2" 336 | stream_channel: 337 | dependency: transitive 338 | description: 339 | name: stream_channel 340 | url: "https://pub.dartlang.org" 341 | source: hosted 342 | version: "1.6.6" 343 | string_scanner: 344 | dependency: transitive 345 | description: 346 | name: string_scanner 347 | url: "https://pub.dartlang.org" 348 | source: hosted 349 | version: "1.0.2" 350 | term_glyph: 351 | dependency: transitive 352 | description: 353 | name: term_glyph 354 | url: "https://pub.dartlang.org" 355 | source: hosted 356 | version: "1.0.0" 357 | test: 358 | dependency: transitive 359 | description: 360 | name: test 361 | url: "https://pub.dartlang.org" 362 | source: hosted 363 | version: "0.12.37" 364 | typed_data: 365 | dependency: transitive 366 | description: 367 | name: typed_data 368 | url: "https://pub.dartlang.org" 369 | source: hosted 370 | version: "1.1.5" 371 | utf: 372 | dependency: transitive 373 | description: 374 | name: utf 375 | url: "https://pub.dartlang.org" 376 | source: hosted 377 | version: "0.9.0+4" 378 | uuid: 379 | dependency: transitive 380 | description: 381 | name: uuid 382 | url: "https://pub.dartlang.org" 383 | source: hosted 384 | version: "1.0.0" 385 | vector_math: 386 | dependency: transitive 387 | description: 388 | name: vector_math 389 | url: "https://pub.dartlang.org" 390 | source: hosted 391 | version: "2.0.6" 392 | watcher: 393 | dependency: transitive 394 | description: 395 | name: watcher 396 | url: "https://pub.dartlang.org" 397 | source: hosted 398 | version: "0.9.7+7" 399 | web_socket_channel: 400 | dependency: transitive 401 | description: 402 | name: web_socket_channel 403 | url: "https://pub.dartlang.org" 404 | source: hosted 405 | version: "1.0.7" 406 | yaml: 407 | dependency: transitive 408 | description: 409 | name: yaml 410 | url: "https://pub.dartlang.org" 411 | source: hosted 412 | version: "2.1.13" 413 | sdks: 414 | dart: ">=2.0.0-dev.52.0 <=2.0.0-dev.58.0.flutter-f981f09760" 415 | flutter: ">=0.1.4 <2.0.0" 416 | -------------------------------------------------------------------------------- /lib/src/App.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (C) 2018 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 21 Jun 2018 21 | /// 22 | 23 | 24 | 25 | import 'dart:async' show Future, StreamSubscription; 26 | 27 | import 'package:flutter/material.dart' show StatefulWidget, AppLifecycleState, BoxDecoration, BuildContext, Color, Drawer, DrawerHeader, FlutterError, FlutterErrorDetails, FutureBuilder, GenerateAppTitle, GlobalKey, Key, ListTile, ListView, Locale, LocaleResolutionCallback, LocalizationsDelegate, MaterialApp, Navigator, NavigatorObserver, NavigatorState, RouteFactory, Scaffold, ScaffoldState, State, StatelessWidget, Text, Theme, ThemeData, TransitionBuilder, Widget, WidgetBuilder, mustCallSuper; 28 | 29 | import 'package:connectivity/connectivity.dart' show Connectivity, ConnectivityResult; 30 | 31 | import 'package:mvc/MVC.dart' show MCView, MVController, StatedWidget; 32 | 33 | import 'package:file_utils/files.dart' show Files; 34 | 35 | import 'package:file_utils/InstallFile.dart' show InstallFile; 36 | 37 | import 'package:prefs/prefs.dart' show Prefs; 38 | 39 | import 'package:auth/Auth.dart' show Auth; 40 | 41 | import 'package:assets/Assets.dart' show Assets; 42 | 43 | import 'package:flutter/widgets.dart' show AppLifecycleState; 44 | 45 | import 'package:firebase_auth/firebase_auth.dart' show FirebaseUser; 46 | 47 | import 'package:mvc/src/LoadingScreen.dart' show LoadingScreen; 48 | 49 | import 'package:mvc/src/FireBase.dart' show FireBase; 50 | 51 | 52 | class App extends StatelessWidget { 53 | 54 | factory App(AppView view,{Key key, 55 | }){ 56 | if(_this == null){ 57 | /// The default is to dump the error to the console. 58 | /// Instead, a custom function is called. 59 | FlutterError.onError = (FlutterErrorDetails details) async { 60 | await _reportError(details); 61 | }; 62 | _this = App._getInstance(view, key); 63 | } 64 | return _this; 65 | } 66 | 67 | /// Make only one instance of this class. 68 | static App _this; 69 | 70 | App._getInstance(AppView view, Key key) : 71 | _vw = view, 72 | key = key, 73 | super(key: key){ 74 | _app = MyApp(_vw); 75 | } 76 | 77 | final AppView _vw; 78 | final Key key; 79 | static MyApp _app; 80 | 81 | static BuildContext _context; 82 | static ThemeData _theme; 83 | static ScaffoldState _scaffold; 84 | 85 | @override 86 | Widget build(BuildContext context) { 87 | Assets.init(context); 88 | _context = context; 89 | return MaterialApp( 90 | key: key, 91 | navigatorKey: _vw.navigatorKey, 92 | routes: _vw.routes, 93 | initialRoute: _vw.initialRoute, 94 | onGenerateRoute: _vw.onGenerateRoute, 95 | onUnknownRoute: _vw.onUnknownRoute, 96 | navigatorObservers: _vw.navigatorObservers, 97 | builder: _vw.builder, 98 | title: _vw.title, 99 | onGenerateTitle: _vw.onGenerateTitle, 100 | color: _vw.color, 101 | theme: _vw.theme ?? MyApp.getThemeData(), 102 | locale: _vw.locale, 103 | localizationsDelegates: _vw.localizationsDelegates, 104 | localeResolutionCallback: _vw.localeResolutionCallback, 105 | supportedLocales: _vw.supportedLocales, 106 | debugShowMaterialGrid: _vw.debugShowMaterialGrid, 107 | showPerformanceOverlay: _vw.showPerformanceOverlay, 108 | checkerboardRasterCacheImages: _vw.checkerboardRasterCacheImages, 109 | checkerboardOffscreenLayers: _vw.checkerboardOffscreenLayers, 110 | showSemanticsDebugger: _vw.showSemanticsDebugger, 111 | debugShowCheckedModeBanner: _vw.debugShowCheckedModeBanner, 112 | home: FutureBuilder( 113 | future: _app.init(), 114 | builder: (_, snapshot) { 115 | return snapshot.hasData ? _app : LoadingScreen(); 116 | }, 117 | ), 118 | ); 119 | } 120 | 121 | /// Called in the MyApp dispose() function. 122 | static void dispose(){ 123 | _app.dispose(); 124 | _context = null; 125 | _theme = null; 126 | _scaffold = null; 127 | } 128 | 129 | static Future getInstallNum() => MyApp.getInstallNum(); 130 | 131 | static addConnectivityListener(ConnectivityListener listener) => MyApp.addConnectivityListener(listener); 132 | 133 | static removeConnectivityListener(ConnectivityListener listener) => MyApp.removeConnectivityListener(listener); 134 | 135 | static clearConnectivityListener() => MyApp.clearConnectivityListener(); 136 | 137 | static bool get inDebugger => MyApp.inDebugger; 138 | 139 | static ThemeData get theme => App._getTheme(); 140 | static ThemeData _getTheme(){ 141 | if(_theme == null) _theme = Theme.of(_context); 142 | return _theme; 143 | } 144 | 145 | static ScaffoldState get scaffold => App._getScaffold(); 146 | static ScaffoldState _getScaffold(){ 147 | if(_scaffold == null) _scaffold = Scaffold.of(_context, nullOk: true); 148 | return _scaffold; 149 | } 150 | } 151 | 152 | 153 | 154 | 155 | 156 | 157 | class MyApp extends StatefulWidget { 158 | 159 | factory MyApp(MCView view,{Key key}){ 160 | if(_this == null) _this = MyApp._getInstance(view, key: key); 161 | return _this; 162 | } 163 | /// Make only one instance of this class. 164 | static MyApp _this; 165 | 166 | MyApp._getInstance(AppView view,{Key key}) : 167 | _vw = view, 168 | _state = view.con, 169 | super(key: key); 170 | 171 | final AppView _vw; 172 | final AppController _state; 173 | 174 | 175 | @override 176 | State createState(){ 177 | /// Pass this 'view' to the State object. 178 | return _state; 179 | } 180 | 181 | 182 | 183 | /// Called in the State object's dispose() function. 184 | void dispose(){ 185 | 186 | _vw.dispose(); 187 | 188 | _connectivitySubscription.cancel(); 189 | _connectivitySubscription = null; 190 | } 191 | 192 | 193 | 194 | Future init() async{ 195 | 196 | _initInternal(); 197 | 198 | return _vw.init(); 199 | } 200 | 201 | 202 | 203 | static getThemeData() { 204 | 205 | Prefs.getStringF('theme').then((value){ 206 | 207 | var theme = value ?? 'light'; 208 | 209 | ThemeData themeData; 210 | 211 | switch (theme) { 212 | case 'light': 213 | themeData = ThemeData.light(); 214 | break; 215 | case 'dark': 216 | themeData = ThemeData.dark(); 217 | break; 218 | default: 219 | themeData = ThemeData.fallback(); 220 | } 221 | return themeData; 222 | }); 223 | } 224 | 225 | 226 | 227 | static setThemeData(String theme) { 228 | switch (theme) { 229 | case 'light': 230 | break; 231 | case 'dark': 232 | break; 233 | default: 234 | theme = 'fallback'; 235 | } 236 | Prefs.setString('theme', theme); 237 | } 238 | 239 | 240 | static final Connectivity _connectivity = Connectivity(); 241 | 242 | static StreamSubscription _connectivitySubscription; 243 | 244 | static String _installNum; 245 | static get installNum => _installNum; 246 | 247 | static String _path; 248 | static get filesDir => _path; 249 | 250 | static String _connectivityStatus; 251 | static get connectivity => _connectivityStatus; 252 | 253 | static get isOnline => _connectivityStatus != 'none'; 254 | 255 | static Set _listeners = new Set(); 256 | 257 | 258 | 259 | /// Internal Initialization routines. 260 | static void _initInternal(){ 261 | 262 | /// Get the installation number 263 | InstallFile.id() 264 | .then((id){_installNum = id;}) 265 | .catchError((e){}); 266 | 267 | /// Determine the location to the files directory. 268 | Files.localPath 269 | .then((path){_path = path;}) 270 | .catchError((e){}); 271 | 272 | _connectivitySubscription = 273 | _connectivity.onConnectivityChanged.listen((ConnectivityResult result) { 274 | _listeners.forEach((listener){listener.onConnectivityChanged(result);}); 275 | }); 276 | 277 | _initConnectivity() 278 | .then((status){_connectivityStatus = status;}) 279 | .catchError((e){_connectivityStatus = 'none';}); 280 | } 281 | 282 | 283 | void didChangeAppLifecycleState(AppLifecycleState state) { 284 | FireBase.didChangeAppLifecycleState(state); 285 | } 286 | 287 | 288 | static Future getInstallNum() => InstallFile.id(); 289 | 290 | 291 | static Future _initConnectivity() async { 292 | String connectionStatus; 293 | // Platform messages may fail, so we use a try/catch PlatformException. 294 | try { 295 | connectionStatus = (await _connectivity.checkConnectivity()).toString(); 296 | } catch (ex) { 297 | connectionStatus = 'Failed to get connectivity.'; 298 | } 299 | return connectionStatus; 300 | } 301 | 302 | 303 | static addConnectivityListener(ConnectivityListener listener) => _listeners.add(listener); 304 | 305 | 306 | static removeConnectivityListener(ConnectivityListener listener) => _listeners.remove(listener); 307 | 308 | 309 | static clearConnectivityListener() => _listeners.clear(); 310 | 311 | 312 | static bool get inDebugger { 313 | var inDebugMode = false; 314 | assert(inDebugMode = true); 315 | return inDebugMode; 316 | } 317 | } 318 | 319 | 320 | 321 | abstract class ConnectivityListener{ 322 | onConnectivityChanged(ConnectivityResult result); 323 | } 324 | 325 | 326 | 327 | /// Reports [error] along with its [stackTrace] 328 | Future _reportError(FlutterErrorDetails details) async { 329 | // details.exception, details.stack 330 | FlutterError.dumpErrorToConsole(details); 331 | } 332 | 333 | 334 | 335 | abstract class AppView extends MCView{ 336 | 337 | AppView({ 338 | this.con, 339 | this.navigatorKey, 340 | this.routes: const {}, 341 | this.initialRoute, 342 | this.onGenerateRoute, 343 | this.onUnknownRoute, 344 | this.navigatorObservers: const [], 345 | this.builder, 346 | this.title: '', 347 | this.onGenerateTitle, 348 | this.color, 349 | this.theme, 350 | this.locale, 351 | this.localizationsDelegates, 352 | this.localeResolutionCallback, 353 | this.supportedLocales: const [const Locale('en', 'US')], 354 | this.debugShowMaterialGrid: false, 355 | this.showPerformanceOverlay: false, 356 | this.checkerboardRasterCacheImages: false, 357 | this.checkerboardOffscreenLayers: false, 358 | this.showSemanticsDebugger: false, 359 | this.debugShowCheckedModeBanner: true, 360 | }): super(con); 361 | 362 | final AppController con; 363 | 364 | final GlobalKey navigatorKey; 365 | final Map routes; 366 | final String initialRoute; 367 | final RouteFactory onGenerateRoute; 368 | final RouteFactory onUnknownRoute; 369 | final List navigatorObservers; 370 | final TransitionBuilder builder; 371 | final String title; 372 | final GenerateAppTitle onGenerateTitle; 373 | final ThemeData theme; 374 | final Color color; 375 | final Locale locale; 376 | final Iterable> localizationsDelegates; 377 | final LocaleResolutionCallback localeResolutionCallback; 378 | final Iterable supportedLocales; 379 | final bool debugShowMaterialGrid; 380 | final bool showPerformanceOverlay; 381 | final bool checkerboardRasterCacheImages; 382 | final bool checkerboardOffscreenLayers; 383 | final bool showSemanticsDebugger; 384 | final bool debugShowCheckedModeBanner; 385 | 386 | @mustCallSuper 387 | Future init() { 388 | return con.init(); 389 | } 390 | 391 | /// Override to dispose anything initialized in your init() function. 392 | @mustCallSuper 393 | void dispose(){ 394 | con.dispose(); 395 | } 396 | 397 | /// Provide 'the view' 398 | Widget build(BuildContext context); 399 | } 400 | 401 | 402 | 403 | 404 | 405 | class AppController extends MVController{ 406 | 407 | 408 | /// Initialize any 'time-consuming' operations at the beginning. 409 | /// Initialize items essential to the Mobile Applications. 410 | /// Called by the MyApp.init() function. 411 | Future init() async { 412 | Auth.init(listener: listener); 413 | Prefs.init(); 414 | return Future.value(true); 415 | } 416 | 417 | 418 | /// Ensure certain objects are 'disposed.' 419 | /// Callec by the AppState.dispose() function. 420 | @override 421 | @mustCallSuper 422 | void dispose() { 423 | Auth.dispose(); 424 | Prefs.dispose(); 425 | App.dispose(); 426 | Assets.dispose(); 427 | super.dispose(); 428 | } 429 | 430 | 431 | 432 | @override 433 | void didChangeAppLifecycleState(AppLifecycleState state) { 434 | super.didChangeAppLifecycleState(state); 435 | // MyApp.didChangeAppLifecycleState(state); 436 | } 437 | 438 | 439 | /// Authentication listener 440 | listener(FirebaseUser user){ 441 | 442 | } 443 | } 444 | 445 | 446 | 447 | 448 | 449 | 450 | class AppDrawer extends StatelessWidget { 451 | @override 452 | Widget build (BuildContext context) { 453 | return new Drawer( 454 | child: new ListView( 455 | children: [ 456 | new DrawerHeader( 457 | child: new Text("DRAWER HEADER.."), 458 | decoration: new BoxDecoration( 459 | 460 | ), 461 | ), 462 | new ListTile( 463 | title: new Text("Item => 1"), 464 | onTap: () { 465 | Navigator.pop(context); 466 | // Navigator.push(context, 467 | // new MaterialPageRoute(builder: (context) => new FirstPage())); 468 | }, 469 | ), 470 | new ListTile( 471 | title: new Text("Item => 2"), 472 | onTap: () { 473 | Navigator.pop(context); 474 | // Navigator.push(context, 475 | // new MaterialPageRoute(builder: (context) => new SecondPage())); 476 | }, 477 | ), 478 | ], 479 | ) 480 | ); 481 | } 482 | } 483 | 484 | 485 | 486 | 487 | --------------------------------------------------------------------------------