From 5c718fc14b9a5186136d3e1c1e884118df1fb9b2 Mon Sep 17 00:00:00 2001 From: Finn Date: Wed, 8 May 2019 19:54:14 +0100 Subject: [PATCH] Initial framework in place, non-functional POST & env vars atm --- .idea/codeStyles/Project.xml | 29 -- .idea/runConfigurations/main_dart.xml | 4 +- README.md | 8 +- .../apifunctions/request_login_api.dart | 50 +++ .../apifunctions/request_logout_api.dart | 31 ++ lib/common/functions/get_token.dart | 8 + lib/common/functions/save_current_login.dart | 30 ++ lib/common/functions/save_logout.dart | 10 + .../functions/show_dialog_single_button.dart | 25 ++ lib/common/platform/platform_scaffold.dart | 72 ++++ lib/common/widgets/basic_drawer.dart | 54 +++ lib/config.dart | 46 +++ lib/config.g.dart | 14 + lib/env/dev.dart | 6 + lib/env/dev.g.dart | 13 + lib/env/dev.json | 5 + lib/env/prod.dart | 6 + lib/env/prod.g.dart | 13 + lib/env/prod.json | 5 + lib/login_page.dart | 125 ------- lib/main.dart | 29 +- lib/main_dev.dart | 7 + lib/model/json/login_model.dart | 21 ++ lib/pages/home_page.dart | 49 +++ lib/pages/login_page.dart | 188 ++++++++++ lib/pages/receipt_page.dart | 187 ++++++++++ lib/pages/spash_screen.dart | 61 ++++ pubspec.lock | 331 +++++++++++++++++- pubspec.yaml | 6 + 29 files changed, 1263 insertions(+), 170 deletions(-) delete mode 100644 .idea/codeStyles/Project.xml create mode 100644 lib/common/apifunctions/request_login_api.dart create mode 100644 lib/common/apifunctions/request_logout_api.dart create mode 100644 lib/common/functions/get_token.dart create mode 100644 lib/common/functions/save_current_login.dart create mode 100644 lib/common/functions/save_logout.dart create mode 100644 lib/common/functions/show_dialog_single_button.dart create mode 100644 lib/common/platform/platform_scaffold.dart create mode 100644 lib/common/widgets/basic_drawer.dart create mode 100644 lib/config.dart create mode 100644 lib/config.g.dart create mode 100644 lib/env/dev.dart create mode 100644 lib/env/dev.g.dart create mode 100644 lib/env/dev.json create mode 100644 lib/env/prod.dart create mode 100644 lib/env/prod.g.dart create mode 100644 lib/env/prod.json delete mode 100644 lib/login_page.dart create mode 100644 lib/main_dev.dart create mode 100644 lib/model/json/login_model.dart create mode 100644 lib/pages/home_page.dart create mode 100644 lib/pages/login_page.dart create mode 100644 lib/pages/receipt_page.dart create mode 100644 lib/pages/spash_screen.dart diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 30aa626..0000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/main_dart.xml b/.idea/runConfigurations/main_dart.xml index aab7b5c..934fdfc 100644 --- a/.idea/runConfigurations/main_dart.xml +++ b/.idea/runConfigurations/main_dart.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/README.md b/README.md index 80f5397..f56480d 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,12 @@ For help getting started with Flutter, view our [online documentation](https://flutter.io/docs), which offers tutorials, samples, guidance on mobile development, and a full API reference. +## Environments + +To build an apk from dev, use: +flutter build apk -t lib/main_dev.dart + ## Links and Things -https://github.com/putraxor/flutter-login-ui \ No newline at end of file +https://github.com/putraxor/flutter-login-ui +https://github.com/pbirdsall/medium_splash_tokenauth \ No newline at end of file diff --git a/lib/common/apifunctions/request_login_api.dart b/lib/common/apifunctions/request_login_api.dart new file mode 100644 index 0000000..2c3e566 --- /dev/null +++ b/lib/common/apifunctions/request_login_api.dart @@ -0,0 +1,50 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:local_spend/common/functions/save_current_login.dart'; +import 'package:local_spend/common/functions/show_dialog_single_button.dart'; +import 'package:local_spend/model/json/login_model.dart'; +import 'package:local_spend/config.dart'; +import 'package:flutter/foundation.dart'; + +Future requestLoginAPI( + BuildContext context, String email, String password) async { + //var apiUrl = ConfigWrapper.of(context).apiKey; + final url = "https://dev.peartrade.org/api/login"; + + Map body = { + 'email': email, + 'password': password, + }; + + debugPrint('$body'); + + final response = await http.post( + url, + body: body, + ); + + debugPrint(response.body); + + if (response.statusCode == 200) { + final responseJson = json.decode(response.body); + var user = new LoginModel.fromJson(responseJson); + + saveCurrentLogin(responseJson); + Navigator.of(context).pushReplacementNamed('/HomePage'); + + return LoginModel.fromJson(responseJson); + } else { + final responseJson = json.decode(response.body); + + saveCurrentLogin(responseJson); + showDialogSingleButton( + context, + "Unable to Login", + "You may have supplied an invalid 'Email' / 'Password' combination. Please try again or email an administrator.", + "OK"); + return null; + } +} diff --git a/lib/common/apifunctions/request_logout_api.dart b/lib/common/apifunctions/request_logout_api.dart new file mode 100644 index 0000000..2d5a141 --- /dev/null +++ b/lib/common/apifunctions/request_logout_api.dart @@ -0,0 +1,31 @@ +import 'dart:io'; +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:local_spend/common/functions/get_token.dart'; +import 'package:local_spend/common/functions/save_logout.dart'; +import 'package:local_spend/model/json/login_model.dart'; + +Future requestLogoutAPI(BuildContext context) async { + final url = "https://www.yoururl.com/logout"; + + var token; + + await getToken().then((result) { + token = result; + }); + + final response = await http.post( + url, + headers: {HttpHeaders.authorizationHeader: "Token $token"}, + ); + + if (response.statusCode == 200) { + saveLogout(); + return null; + } else { + saveLogout(); + return null; + } +} diff --git a/lib/common/functions/get_token.dart b/lib/common/functions/get_token.dart new file mode 100644 index 0000000..c502ade --- /dev/null +++ b/lib/common/functions/get_token.dart @@ -0,0 +1,8 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +getToken() async { + SharedPreferences preferences = await SharedPreferences.getInstance(); + + String getToken = await preferences.getString("LastToken"); + return getToken; +} diff --git a/lib/common/functions/save_current_login.dart b/lib/common/functions/save_current_login.dart new file mode 100644 index 0000000..91c6989 --- /dev/null +++ b/lib/common/functions/save_current_login.dart @@ -0,0 +1,30 @@ +import 'package:local_spend/model/json/login_model.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +saveCurrentLogin(Map responseJson) async { + SharedPreferences preferences = await SharedPreferences.getInstance(); + + var user; + if ((responseJson != null && responseJson.isNotEmpty)) { + user = LoginModel.fromJson(responseJson).userName; + } else { + user = ""; + } + var token = (responseJson != null && responseJson.isNotEmpty) + ? LoginModel.fromJson(responseJson).token + : ""; + var email = (responseJson != null && responseJson.isNotEmpty) + ? LoginModel.fromJson(responseJson).email + : ""; + var pk = (responseJson != null && responseJson.isNotEmpty) + ? LoginModel.fromJson(responseJson).userId + : 0; + + await preferences.setString( + 'LastUser', (user != null && user.length > 0) ? user : ""); + await preferences.setString( + 'LastToken', (token != null && token.length > 0) ? token : ""); + await preferences.setString( + 'LastEmail', (email != null && email.length > 0) ? email : ""); + await preferences.setInt('LastUserId', (pk != null && pk > 0) ? pk : 0); +} diff --git a/lib/common/functions/save_logout.dart b/lib/common/functions/save_logout.dart new file mode 100644 index 0000000..73779ea --- /dev/null +++ b/lib/common/functions/save_logout.dart @@ -0,0 +1,10 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +saveLogout() async { + SharedPreferences preferences = await SharedPreferences.getInstance(); + + await preferences.setString('LastUser', ""); + await preferences.setString('LastToken', ""); + await preferences.setString('LastEmail', ""); + await preferences.setInt('LastUserId', 0); +} diff --git a/lib/common/functions/show_dialog_single_button.dart b/lib/common/functions/show_dialog_single_button.dart new file mode 100644 index 0000000..b9d64e8 --- /dev/null +++ b/lib/common/functions/show_dialog_single_button.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +void showDialogSingleButton( + BuildContext context, String title, String message, String buttonLabel) { + // flutter defined function + showDialog( + context: context, + builder: (BuildContext context) { + // return object of type Dialog + return AlertDialog( + title: new Text(title), + content: new Text(message), + actions: [ + // usually buttons at the bottom of the dialog + new FlatButton( + child: new Text(buttonLabel), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); +} diff --git a/lib/common/platform/platform_scaffold.dart b/lib/common/platform/platform_scaffold.dart new file mode 100644 index 0000000..0209aa6 --- /dev/null +++ b/lib/common/platform/platform_scaffold.dart @@ -0,0 +1,72 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class PlatformScaffold extends StatelessWidget { + final Key key; + final PreferredSizeWidget appBar; + final Widget body; + final Widget floatingActionButton; + final FloatingActionButtonLocation floatingActionButtonLocation; + final FloatingActionButtonAnimator floatingActionButtonAnimator; + final List persistentFooterButtons; + final Widget drawer; + final Widget endDrawer; + final Widget bottomNavigationBar; + final Color backgroundColor; + final bool resizeToAvoidBottomPadding; + final bool primary; + + PlatformScaffold( + {this.key, + this.appBar, + this.body, + this.floatingActionButton, + this.floatingActionButtonLocation, + this.floatingActionButtonAnimator, + this.persistentFooterButtons, + this.drawer, + this.endDrawer, + this.bottomNavigationBar, + this.backgroundColor, + this.resizeToAvoidBottomPadding: true, + this.primary: true}) + : assert(primary != null), + super(key: key); + + @override + Widget build(BuildContext context) { + return Platform.isIOS + ? Scaffold( + key: key, + appBar: appBar, + body: body, + floatingActionButton: floatingActionButton, + persistentFooterButtons: persistentFooterButtons, + floatingActionButtonLocation: floatingActionButtonLocation, + floatingActionButtonAnimator: floatingActionButtonAnimator, + drawer: endDrawer, + endDrawer: drawer, + bottomNavigationBar: bottomNavigationBar, + backgroundColor: backgroundColor, + resizeToAvoidBottomPadding: resizeToAvoidBottomPadding, + primary: primary, + ) + : Scaffold( + key: key, + appBar: appBar, + body: body, + floatingActionButton: floatingActionButton, + persistentFooterButtons: persistentFooterButtons, + floatingActionButtonLocation: floatingActionButtonLocation, + floatingActionButtonAnimator: floatingActionButtonAnimator, + drawer: drawer, + endDrawer: endDrawer, + bottomNavigationBar: bottomNavigationBar, + backgroundColor: backgroundColor, + resizeToAvoidBottomPadding: resizeToAvoidBottomPadding, + primary: primary, + ); + } +} diff --git a/lib/common/widgets/basic_drawer.dart b/lib/common/widgets/basic_drawer.dart new file mode 100644 index 0000000..384f693 --- /dev/null +++ b/lib/common/widgets/basic_drawer.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:local_spend/common/apifunctions/request_logout_api.dart'; + +class BasicDrawer extends StatefulWidget { + @override + _BasicDrawerState createState() => _BasicDrawerState(); +} + +class _BasicDrawerState extends State { + @override + Widget build(BuildContext context) { + return Drawer( + child: Container( + padding: new EdgeInsets.all(32.0), + child: ListView( + children: [ + ListTile( + title: Text( + "Submit Receipt", + style: TextStyle(color: Colors.black, fontSize: 20.0), + ), + onTap: () { + requestLogoutAPI(context); + Navigator.of(context).pushNamed('/ReceiptPage'); + }, + ), + ListTile( + title: Text( + "About", + style: TextStyle(color: Colors.black, fontSize: 20.0), + ), + onTap: () { + SystemChannels.textInput.invokeMethod('TextInput.hide'); +// Here I have not implemented an actual about screen, but if you did you would navigate to it's route +// Navigator.of(context).pushReplacementNamed('/AboutScreen'); + }, + ), + ListTile( + title: Text( + "Logout", + style: TextStyle(color: Colors.black, fontSize: 20.0), + ), + onTap: () { + requestLogoutAPI(context); + Navigator.of(context).pushReplacementNamed('/LoginPage'); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/config.dart b/lib/config.dart new file mode 100644 index 0000000..4e86245 --- /dev/null +++ b/lib/config.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'config.g.dart'; + +@JsonSerializable(createToJson: false) +class Config { + final String env; + final bool production; + final String apiKey; + + Config({this.env, this.production, this.apiKey}); + + factory Config.fromJson(Map json) => _$ConfigFromJson(json); +} + +class ConfigWrapper extends StatelessWidget { + ConfigWrapper({Key key, this.config, this.child}); + + @override + Widget build(BuildContext context) { + return new _InheritedConfig(config: this.config, child: this.child); + } + + static Config of(BuildContext context) { + final _InheritedConfig inheritedConfig = + context.inheritFromWidgetOfExactType(_InheritedConfig); + return inheritedConfig.config; + } + + final Config config; + final Widget child; +} + +class _InheritedConfig extends InheritedWidget { + const _InheritedConfig( + {Key key, @required this.config, @required Widget child}) + : assert(config != null), + assert(child != null), + super(key: key, child: child); + final Config config; + + @override + bool updateShouldNotify(_InheritedConfig oldWidget) => + config != oldWidget.config; +} \ No newline at end of file diff --git a/lib/config.g.dart b/lib/config.g.dart new file mode 100644 index 0000000..445b22e --- /dev/null +++ b/lib/config.g.dart @@ -0,0 +1,14 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Config _$ConfigFromJson(Map json) { + return Config( + env: json['env'] as String, + production: json['production'] as bool, + apiKey: json['apiKey'] as String); +} diff --git a/lib/env/dev.dart b/lib/env/dev.dart new file mode 100644 index 0000000..4a7ee00 --- /dev/null +++ b/lib/env/dev.dart @@ -0,0 +1,6 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'dev.g.dart'; + +@JsonLiteral('dev.json', asConst: true) +Map get config => _$configJsonLiteral; \ No newline at end of file diff --git a/lib/env/dev.g.dart b/lib/env/dev.g.dart new file mode 100644 index 0000000..ad6c7c4 --- /dev/null +++ b/lib/env/dev.g.dart @@ -0,0 +1,13 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'dev.dart'; + +// ************************************************************************** +// JsonLiteralGenerator +// ************************************************************************** + +const _$configJsonLiteral = { + 'env': 'DEV', + 'production': false, + 'apiUrl': 'https://dev.peartrade.org/api' +}; diff --git a/lib/env/dev.json b/lib/env/dev.json new file mode 100644 index 0000000..403630b --- /dev/null +++ b/lib/env/dev.json @@ -0,0 +1,5 @@ +{ + "env": "DEV", + "production": false, + "apiUrl": "https://dev.peartrade.org/api" +} \ No newline at end of file diff --git a/lib/env/prod.dart b/lib/env/prod.dart new file mode 100644 index 0000000..ac3dc8d --- /dev/null +++ b/lib/env/prod.dart @@ -0,0 +1,6 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'prod.g.dart'; + +@JsonLiteral('prod.json', asConst: true) +Map get config => _$configJsonLiteral; \ No newline at end of file diff --git a/lib/env/prod.g.dart b/lib/env/prod.g.dart new file mode 100644 index 0000000..46e18c7 --- /dev/null +++ b/lib/env/prod.g.dart @@ -0,0 +1,13 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'prod.dart'; + +// ************************************************************************** +// JsonLiteralGenerator +// ************************************************************************** + +const _$configJsonLiteral = { + 'env': 'PROD', + 'production': true, + 'apiUrl': 'https://www.peartrade.org/api' +}; diff --git a/lib/env/prod.json b/lib/env/prod.json new file mode 100644 index 0000000..ccdabad --- /dev/null +++ b/lib/env/prod.json @@ -0,0 +1,5 @@ +{ + "env": "PROD", + "production": true, + "apiUrl": "https://www.peartrade.org/api" +} \ No newline at end of file diff --git a/lib/login_page.dart b/lib/login_page.dart deleted file mode 100644 index 2518385..0000000 --- a/lib/login_page.dart +++ /dev/null @@ -1,125 +0,0 @@ -import 'package:flutter/material.dart'; - -class LoginPage extends StatefulWidget { - - @override - _LoginPageState createState() => _LoginPageState(); -} - -class _LoginPageState extends State { - @override - Widget build(BuildContext context) { - - final email = TextFormField( - keyboardType: TextInputType.emailAddress, - autofocus: true, - decoration: InputDecoration( - hintText: 'Email', - ) - ); - - final password = TextFormField( - autofocus: false, - obscureText: true, - decoration: InputDecoration( - hintText: 'Password', - ) - ); - - return Scaffold( - body: Center( - child: ListView( - children: [ - email, - password - ] - ) - ) - ); - } -} - -class MyHomePage extends StatefulWidget { - MyHomePage({Key key, this.title}) : super(key: key); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - _MyHomePageState createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title + 'derpaderp'), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Invoke "debug painting" (press "p" in the console, choose the - // "Toggle Debug Paint" action from the Flutter Inspector in Android - // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) - // to see the wireframe for each widget. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.display1, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. - ); - } -} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 2afc941..1bf6840 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,18 +1,29 @@ import 'package:flutter/material.dart'; -import 'package:local_spend/login_page.dart'; +import 'package:local_spend/pages/home_page.dart'; +import 'package:local_spend/pages/login_page.dart'; +import 'package:local_spend/pages/receipt_page.dart'; +import 'package:local_spend/pages/spash_screen.dart'; +import 'package:local_spend/config.dart'; -void main() => runApp(MyApp()); +void main() { + runApp(MyApp()); +} class MyApp extends StatelessWidget { - // This widget is the root of your application. @override Widget build(BuildContext context) { - return MaterialApp( - title: 'LocalSpend Tracker', - theme: ThemeData( - primarySwatch: Colors.blueGrey, - ), - home: LoginPage(), + //var config = ConfigWrapper.of(context); + return new MaterialApp( + title: "Splash and Token Authentication", +// theme: new ThemeData( +// primarySwatch: config.production ? Colors.green : Colors.yellow, +// ), + routes: { + "/HomePage": (BuildContext context) => HomePage(), + "/LoginPage": (BuildContext context) => LoginPage(), + "/ReceiptPage": (BuildContext context) => ReceiptPage(), + }, + home: SplashScreen(), ); } } diff --git a/lib/main_dev.dart b/lib/main_dev.dart new file mode 100644 index 0000000..289940e --- /dev/null +++ b/lib/main_dev.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; +import 'package:local_spend/config.dart'; +import 'package:local_spend/env/dev.dart'; +import 'package:local_spend/main.dart'; + +void main() => runApp( + new ConfigWrapper(config: Config.fromJson(config), child: new MyApp())); diff --git a/lib/model/json/login_model.dart b/lib/model/json/login_model.dart new file mode 100644 index 0000000..68ccc1f --- /dev/null +++ b/lib/model/json/login_model.dart @@ -0,0 +1,21 @@ +class LoginModel { + final String userName; + final String token; + final String email; + final int userId; + + LoginModel(this.userName, this.token, this.email, this.userId); + + LoginModel.fromJson(Map json) + : userName = json['name'], + token = json['token'], + email = json['email'], + userId = json['pk']; + + Map toJson() => { + 'name': userName, + 'token': token, + 'email': email, + 'pk': userId, + }; +} diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart new file mode 100644 index 0000000..a678e2a --- /dev/null +++ b/lib/pages/home_page.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:local_spend/common/platform/platform_scaffold.dart'; +import 'package:local_spend/common/widgets/basic_drawer.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class HomePage extends StatefulWidget { + @override + _HomePageState createState() => _HomePageState(); +} + +class _HomePageState extends State { + @override + void initState() { + super.initState(); + _saveCurrentRoute("/HomePage"); + } + + _saveCurrentRoute(String lastRoute) async { + SharedPreferences preferences = await SharedPreferences.getInstance(); + await preferences.setString('LastScreenRoute', lastRoute); + } + + @override + Widget build(BuildContext context) { + return PlatformScaffold( + appBar: AppBar( + title: Text( + "Home Page", + style: TextStyle(color: Colors.black), + ), + backgroundColor: Colors.white, + iconTheme: IconThemeData(color: Colors.black), + elevation: Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 6.0, + ), + drawer: BasicDrawer(), + backgroundColor: Colors.white, + body: Container( + padding: EdgeInsets.all(32.0), + child: Center( + child: Column( + children: [ + Text('This is the Home page'), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/login_page.dart b/lib/pages/login_page.dart new file mode 100644 index 0000000..f3f848e --- /dev/null +++ b/lib/pages/login_page.dart @@ -0,0 +1,188 @@ +import 'dart:async'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:local_spend/common/apifunctions/request_login_api.dart'; +import 'package:local_spend/common/functions/show_dialog_single_button.dart'; +import 'package:local_spend/common/platform/platform_scaffold.dart'; +import 'package:local_spend/common/widgets/basic_drawer.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:url_launcher/url_launcher.dart'; + +const URL = "https://flutter.io/"; + +class LoginPage extends StatefulWidget { + @override + State createState() { + return new LoginPageState(); + } +} + +class LoginPageState extends State { + final TextEditingController _emailController = TextEditingController(); + final TextEditingController _passwordController = TextEditingController(); + String _welcomeString = ""; + + Future launchURL(String url) async { + if (await canLaunch(url)) { + await launch(url, forceSafariVC: true, forceWebView: true); + } else { + showDialogSingleButton( + context, + "Unable to reach your website.", + "Currently unable to reach the website $URL. Please try again at a later time.", + "OK"); + } + } + + @override + void initState() { + super.initState(); + _saveCurrentRoute("/LoginPage"); + } + + _saveCurrentRoute(String lastRoute) async { + SharedPreferences preferences = await SharedPreferences.getInstance(); + await preferences.setString('LastPageRoute', lastRoute); + } + + @override + Widget build(BuildContext context) { + var drawer = Drawer(); + return WillPopScope( + onWillPop: () { + if (Navigator.canPop(context)) { + Navigator.of(context).pushNamedAndRemoveUntil( + '/HomePage', (Route route) => false); + } else { + Navigator.of(context).pushReplacementNamed('/HomePage'); + } + }, + child: PlatformScaffold( + drawer: BasicDrawer(), + appBar: AppBar( + title: Text( + "LOGIN", + style: TextStyle( + fontSize: 30.0, + color: Colors.black, + ), + ), + centerTitle: true, + backgroundColor: Colors.white, + iconTheme: IconThemeData(color: Colors.black), + ), + backgroundColor: Colors.white, + body: Container( + child: Padding( + padding: EdgeInsets.fromLTRB(30.0, 0.0, 30.0, 0.0), + child: ListView( + children: [ + Container( + alignment: Alignment.topCenter, + child: Padding( + padding: EdgeInsets.fromLTRB(0.0, 40.0, 0.0, 15.0), + child: Text( + "Local Loop", + style: TextStyle(fontSize: 40.0, color: Colors.black), + ), + )), + Padding( + padding: EdgeInsets.fromLTRB(0.0, 5.0, 0.0, 78.0), + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: + 'This is the logon page.', + style: new TextStyle( + fontSize: 20.0, + color: Colors.black, + ), + ), + TextSpan( + text: + ' It is currently in development.', + style: new TextStyle( + fontSize: 20.0, + color: Colors.black, + ), + ), + ], + ), + ), + ), + Text( + "Email", + style: TextStyle( + fontSize: 18.0, + color: Colors.black, + fontWeight: FontWeight.bold, + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(0.0, 5.0, 0.0, 0.0), + child: TextField( + controller: _emailController, + decoration: InputDecoration( + hintText: "Use your login email", + ), + style: TextStyle( + fontSize: 18.0, + color: Colors.grey, + fontWeight: FontWeight.bold, + ), + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(0.0, 35.0, 0.0, 0.0), + child: Text( + "Password", + style: TextStyle( + fontSize: 18.0, + color: Colors.black, + fontWeight: FontWeight.bold, + ), + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(0.0, 5.0, 0.0, 0.0), + child: TextField( + controller: _passwordController, + decoration: InputDecoration( + hintText: 'Your password, keep it secret, keep it safe.', + ), + obscureText: true, + style: TextStyle( + fontSize: 18.0, + color: Colors.grey, + fontWeight: FontWeight.bold, + ), + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(0.0, 70.0, 0.0, 0.0), + child: Container( + height: 65.0, + child: RaisedButton( + onPressed: () { + SystemChannels.textInput.invokeMethod('TextInput.hide'); + requestLoginAPI(context, _emailController.text, + _passwordController.text); + }, + child: Text("LOGIN", + style: + TextStyle(color: Colors.white, fontSize: 22.0)), + color: Colors.blue, + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/receipt_page.dart b/lib/pages/receipt_page.dart new file mode 100644 index 0000000..24df346 --- /dev/null +++ b/lib/pages/receipt_page.dart @@ -0,0 +1,187 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:local_spend/common/apifunctions/request_login_api.dart'; +import 'package:local_spend/common/functions/show_dialog_single_button.dart'; +import 'package:local_spend/common/platform/platform_scaffold.dart'; +import 'package:local_spend/common/widgets/basic_drawer.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:url_launcher/url_launcher.dart'; + +const URL = "https://flutter.io/"; + +class ReceiptPage extends StatefulWidget { + @override + State createState() { + return new ReceiptPageState(); + } +} + +class ReceiptPageState extends State { + final TextEditingController _emailController = TextEditingController(); + final TextEditingController _passwordController = TextEditingController(); + String _welcomeString = ""; + + Future launchURL(String url) async { + if (await canLaunch(url)) { + await launch(url, forceSafariVC: true, forceWebView: true); + } else { + showDialogSingleButton( + context, + "Unable to reach your website.", + "Currently unable to reach the website $URL. Please try again at a later time.", + "OK"); + } + } + + @override + void initState() { + super.initState(); + _saveCurrentRoute("/LoginPage"); + } + + _saveCurrentRoute(String lastRoute) async { + SharedPreferences preferences = await SharedPreferences.getInstance(); + await preferences.setString('LastPageRoute', lastRoute); + } + + @override + Widget build(BuildContext context) { + var drawer = Drawer(); + return WillPopScope( + onWillPop: () { + if (Navigator.canPop(context)) { + Navigator.of(context).pushNamedAndRemoveUntil( + '/HomePage', (Route route) => false); + } else { + Navigator.of(context).pushReplacementNamed('/HomePage'); + } + }, + child: PlatformScaffold( + drawer: BasicDrawer(), + appBar: AppBar( + title: Text( + "LOGIN", + style: TextStyle( + fontSize: 30.0, + color: Colors.black, + ), + ), + centerTitle: true, + backgroundColor: Colors.white, + iconTheme: IconThemeData(color: Colors.black), + ), + backgroundColor: Colors.white, + body: Container( + child: Padding( + padding: EdgeInsets.fromLTRB(30.0, 0.0, 30.0, 0.0), + child: ListView( + children: [ + Container( + alignment: Alignment.topCenter, + child: Padding( + padding: EdgeInsets.fromLTRB(0.0, 40.0, 0.0, 15.0), + child: Text( + "Local Loop", + style: TextStyle(fontSize: 40.0, color: Colors.black), + ), + )), + Padding( + padding: EdgeInsets.fromLTRB(0.0, 5.0, 0.0, 78.0), + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: + 'This is the logon page.', + style: new TextStyle( + fontSize: 20.0, + color: Colors.black, + ), + ), + TextSpan( + text: + ' It is currently in development.', + style: new TextStyle( + fontSize: 20.0, + color: Colors.black, + ), + ), + ], + ), + ), + ), + Text( + "Email", + style: TextStyle( + fontSize: 18.0, + color: Colors.black, + fontWeight: FontWeight.bold, + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(0.0, 5.0, 0.0, 0.0), + child: TextField( + controller: _emailController, + decoration: InputDecoration( + hintText: "Use your login email", + ), + style: TextStyle( + fontSize: 18.0, + color: Colors.grey, + fontWeight: FontWeight.bold, + ), + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(0.0, 35.0, 0.0, 0.0), + child: Text( + "Password", + style: TextStyle( + fontSize: 18.0, + color: Colors.black, + fontWeight: FontWeight.bold, + ), + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(0.0, 5.0, 0.0, 0.0), + child: TextField( + controller: _passwordController, + decoration: InputDecoration( + hintText: 'Your password, keep it secret, keep it safe.', + ), + obscureText: true, + style: TextStyle( + fontSize: 18.0, + color: Colors.grey, + fontWeight: FontWeight.bold, + ), + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(0.0, 70.0, 0.0, 0.0), + child: Container( + height: 65.0, + child: RaisedButton( + onPressed: () { + SystemChannels.textInput.invokeMethod('TextInput.hide'); + requestLoginAPI(context, _emailController.text, + _passwordController.text); + }, + child: Text("LOGIN", + style: + TextStyle(color: Colors.white, fontSize: 22.0)), + color: Colors.blue, + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/spash_screen.dart b/lib/pages/spash_screen.dart new file mode 100644 index 0000000..8a27131 --- /dev/null +++ b/lib/pages/spash_screen.dart @@ -0,0 +1,61 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:local_spend/common/platform/platform_scaffold.dart'; + +class SplashScreen extends StatefulWidget { + @override + _SplashScreenState createState() => _SplashScreenState(); +} + +class _SplashScreenState extends State { + final int splashDuration = 3; + + startTime() async { + return Timer(Duration(seconds: splashDuration), () { + SystemChannels.textInput.invokeMethod('TextInput.hide'); + Navigator.of(context).pushReplacementNamed('/HomePage'); + }); + } + + @override + void initState() { + super.initState(); + startTime(); + } + + @override + Widget build(BuildContext context) { + var drawer = Drawer(); + + return PlatformScaffold( + drawer: drawer, + body: Container( + decoration: BoxDecoration(color: Colors.black), + child: Column( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration(color: Colors.black), + alignment: FractionalOffset(0.5, 0.3), + child: Text( + "Local Loop", + style: TextStyle(fontSize: 40.0, color: Colors.white), + ), + ), + ), + Container( + margin: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 30.0), + child: Text( + "© Copyright Statement 2018", + style: TextStyle( + fontSize: 16.0, + color: Colors.white, + ), + ), + ), + ], + ))); + } +} diff --git a/pubspec.lock b/pubspec.lock index 7eebb6b..ebf82ae 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,20 @@ # Generated by pub # See https://www.dartlang.org/tools/pub/glossary#lockfile packages: + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "0.36.2" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.1" async: dependency: transitive description: @@ -15,6 +29,62 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.4" + build: + dependency: transitive + description: + name: build + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.4" + build_config: + dependency: transitive + description: + name: build_config + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.2" + build_daemon: + dependency: transitive + description: + name: build_daemon + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.0" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + build_runner: + dependency: "direct dev" + description: + name: build_runner + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.3" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.3" + built_collection: + dependency: transitive + description: + name: built_collection + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.0" + built_value: + dependency: transitive + description: + name: built_value + url: "https://pub.dartlang.org" + source: hosted + version: "6.4.0" charcode: dependency: transitive description: @@ -22,6 +92,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.2" + code_builder: + dependency: transitive + description: + name: code_builder + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.0" collection: dependency: transitive description: @@ -29,6 +106,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.14.11" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.0" cupertino_icons: dependency: "direct main" description: @@ -36,6 +134,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.2" + dart_style: + dependency: transitive + description: + name: dart_style + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.7" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.9" flutter: dependency: "direct main" description: flutter @@ -46,6 +158,97 @@ packages: description: flutter source: sdk version: "0.0.0" + front_end: + dependency: transitive + description: + name: front_end + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.17" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.7" + graphs: + dependency: transitive + description: + name: graphs + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.0+2" + http: + dependency: "direct main" + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.0+2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.3" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.3" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.1+1" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" + kernel: + dependency: transitive + description: + name: kernel + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.17" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "0.11.3+2" matcher: dependency: transitive description: @@ -60,6 +263,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.6" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.6+2" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + package_resolver: + dependency: transitive + description: + name: package_resolver + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.10" path: dependency: transitive description: @@ -67,6 +291,34 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.6.2" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.2" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.4" quiver: dependency: transitive description: @@ -74,18 +326,46 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.3" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.5" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.3" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.4+2" source_span: dependency: transitive description: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.4.1" + version: "1.5.4" stack_trace: dependency: transitive description: @@ -100,6 +380,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.6.8" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.16+1" string_scanner: dependency: transitive description: @@ -113,14 +400,21 @@ packages: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.1.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.1" + version: "0.2.2" + timing: + dependency: transitive + description: + name: timing + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.1+1" typed_data: dependency: transitive description: @@ -128,6 +422,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.6" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.3" vector_math: dependency: transitive description: @@ -135,5 +436,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.7+10" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.12" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.15" sdks: - dart: ">=2.0.0 <3.0.0" + dart: ">=2.1.1 <3.0.0" + flutter: ">=0.1.4 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index f9339a5..eb5480f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,6 +15,10 @@ environment: dependencies: flutter: sdk: flutter + shared_preferences: ^0.4.2 + url_launcher: ^3.0.3 + json_annotation : ^2.2.0 + http: ^0.12.0+2 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. @@ -23,6 +27,8 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + build_runner: ^1.1.3 + json_serializable: ^2.1.2 # For information on the generic Dart part of this file, see the