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