Compare commits

..

3 commits

Author SHA1 Message Date
Felix
248d5f6983
names appropriated, pages updated 2019-07-17 16:45:37 +01:00
Felix
157d485e3c
drawer added, trying to fix text alignment 2019-07-17 14:15:10 +01:00
Felix
9e42671488
side bar added, ran into weird problem 2019-07-17 12:54:31 +01:00
74 changed files with 1610 additions and 3077 deletions

4
.gitignore vendored
View file

@ -234,6 +234,4 @@ fabric.properties
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
# End of https://www.gitignore.io/api/android,flutter,androidstudio
lib/common/felixApiCreds.dart
android/app/src/main/AndroidManifest.xml
# End of https://www.gitignore.io/api/android,flutter,androidstudio

View file

@ -1,116 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
</code_scheme>
</component>

106
README.md
View file

@ -1,99 +1,31 @@
# LocalSpend (Mobile App.)
# local_spend
Looking to discover if the value of spending local can be measured, understood and shown.
Local Spend Tracker
This repository contains the mobile application for the LocalSpend system. See also:
## Getting Started
* the [Web application](https://github.com/Pear-Trading/Foodloop-Web); and
* the [server](https://github.com/Pear-Trading/Foodloop-Server).
This project is a starting point for a Flutter application.
## Table of Contents
A few resources to get you started if this is your first Flutter project:
* [Tech Stack](#tech-stack)
* [Features](#features)
* [Installation](#installation)
* [Configuration](#configuration)
* [Usage](#usage)
* [Testing](#testing)
* [Code Formatting](#code-formatting)
* [Documentation](#documentation)
* [Acknowledgments](#acknowledgements)
* [License](#license)
* [Contact](#contact)
- [Lab: Write your first Flutter app](https://flutter.io/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.io/docs/cookbook)
## Technology Stack
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.
The mobile app. is written in [Dart](https://dart.dev/).
## Environments
| Technology | Description | Link |
|-------------|---------------------------------|---------------------|
| Flutter | Cross-platform mobile framework | [Link][flutter] |
To build an apk from dev, use:
flutter build apk -t lib/main_dev.dart
[flutter]: https://flutter.dev/
## Links and Things
## Features
https://github.com/putraxor/flutter-login-ui
https://github.com/pbirdsall/medium_splash_tokenauth
This mobile app. provides:
- user authentication; and
- transaction logging.
## Installation
1. Install [Flutter](https://flutter.dev/docs/get-started/install);
1. if this is your first Flutter project, install the [Flutter SDK](https://flutter.dev/docs/get-started/test-drive);
1. set up [your editor](https://flutter.dev/docs/get-started/editor):
- we recommend using [Android Studio](https://developer.android.com/studio).
1. add the line `flutter.sdk=⟨ path to Flutter SDK ⟩` to the file `android/local.properties`.
## Configuration
App. configuration settings are found in `pubspec.yaml`.
Build settings are found in the `android/` directory, in the `build.gradle`, `gradle.properties` and `settings.gradle` files.
## Usage
### Development
To activate debugging, add the following import statement:
```dart
## How to debug code
// debug
import 'package:flutter/foundation.dart';
```
After that, you can generate debugging output using `debugPrint()`.
### Production
Run `flutter build apk -t lib/main_dev.dart` to generate an APK file.
## Testing
TODO
## Code Formatting
TODO
## Documentation
TODO
## Acknowledgements
LocalLoop is the result of collaboration between the [Small Green Consultancy](http://www.smallgreenconsultancy.co.uk/), [Shadowcat Systems](https://shadow.cat/), [Independent Lancaster](http://www.independent-lancaster.co.uk/) and the [Ethical Small Traders Association](http://www.lancasteresta.org/).
## License
This project is released under the [MIT license](https://mit-license.org/).
## Contact
| Name | Link(s) |
|----------------|-------------------|
| Mark Keating | [Email][mkeating] |
| Michael Hallam | [Email][mhallam] |
[mkeating]: mailto:m.keating@shadowcat.co.uk
[mhallam]: mailto:info@lancasteresta.org
debugPrint('$foo');

17
To Do List.txt Normal file
View file

@ -0,0 +1,17 @@
TODO
Show username
add confirm logout
Splash Screen
Splash screen transition - fade
Login
save login details option (checkbox or something)
Navigation
Make it look good
Submit Receipt
Categories
Recurring
'organisation name' dialog works

View file

@ -1,18 +0,0 @@
include: package:pedantic/analysis_options.yaml
analyzer:
exclude:
- lib/src/locations.g.dart
linter:
rules:
- always_declare_return_types
- camel_case_types
- empty_constructor_bodies
- annotate_overrides
- avoid_init_to_null
- constant_identifier_names
- one_member_abstracts
- slash_for_doc_comments
- sort_constructors_first
- unnecessary_brace_in_string_interps

View file

@ -25,7 +25,7 @@ apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 28
compileSdkVersion 27
lintOptions {
disable 'InvalidPackage'

View file

@ -13,18 +13,11 @@
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:label="SpendTracker"
android:name="io.flutter.app.FlutterApplication"
android:label="local_spend"
android:icon="@mipmap/ic_launcher">
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<meta-data android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyD0vsoT6Omnn01hbUiCjAhiS47uFYWnEHE"/>
<!-- Please don't be stupid with the above key-->
<activity android:name=".MainActivity"
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
@ -34,19 +27,9 @@
until Flutter renders its first frame. It can be removed if
there is no splash screen (such as the default splash screen
defined in @style/LaunchTheme). -->
<!-- Specify that the launch screen should continue being displayed -->
<!-- until Flutter renders its first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background" />
<!-- Theme to apply as soon as Flutter begins rendering frames -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>

View file

@ -1,6 +1,13 @@
package uk.co.localspend.localspend;
import io.flutter.embedding.android.FlutterActivity;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!--<item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item>-->
</layer-list>

View file

@ -5,8 +5,4 @@
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/normal_background</item>
</style>
</resources>

View file

@ -26,4 +26,4 @@ subprojects {
task clean(type: Delete) {
delete rootProject.buildDir
}
}

View file

@ -1,4 +1 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
android.enableR8=true

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

View file

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
platform :ios, '9.0'
# platform :ios, '9.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View file

@ -1,33 +1,18 @@
PODS:
- Flutter (1.0.0)
- google_maps_flutter (0.0.1):
- Flutter
- GoogleMaps
- GoogleMaps (3.3.0):
- GoogleMaps/Maps (= 3.3.0)
- GoogleMaps/Base (3.3.0)
- GoogleMaps/Maps (3.3.0):
- GoogleMaps/Base
- shared_preferences (0.0.1):
- Flutter
- url_launcher (0.0.1):
- Flutter
DEPENDENCIES:
- Flutter (from `.symlinks/flutter/ios-profile`)
- google_maps_flutter (from `.symlinks/plugins/google_maps_flutter/ios`)
- Flutter (from `.symlinks/flutter/ios`)
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
SPEC REPOS:
https://github.com/cocoapods/specs.git:
- GoogleMaps
EXTERNAL SOURCES:
Flutter:
:path: ".symlinks/flutter/ios-profile"
google_maps_flutter:
:path: ".symlinks/plugins/google_maps_flutter/ios"
:path: ".symlinks/flutter/ios"
shared_preferences:
:path: ".symlinks/plugins/shared_preferences/ios"
url_launcher:
@ -35,11 +20,9 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
Flutter: 58dd7d1b27887414a370fcccb9e645c08ffd7a6a
google_maps_flutter: 78a52114c898b42ea647919679a4c58b70abe876
GoogleMaps: cfee83da305b9aaeccf92c24ac79df11c3003492
shared_preferences: 1feebfa37bb57264736e16865e7ffae7fc99b523
url_launcher: 0067ddb8f10d36786672aa0722a21717dba3a298
PODFILE CHECKSUM: 3389836f37640698630b8f0670315d626d5f1469
PODFILE CHECKSUM: aff02bfeed411c636180d6812254b2daeea14d09
COCOAPODS: 1.7.5
COCOAPODS: 1.7.3

View file

@ -163,7 +163,6 @@
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
ACE3DC71CDB8FA5537935604 /* [CP] Embed Pods Frameworks */,
E6C2807EE928990FB790046F /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@ -282,7 +281,7 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${PODS_ROOT}/../.symlinks/flutter/ios-profile/Flutter.framework",
"${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
@ -293,24 +292,6 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
E6C2807EE928990FB790046F /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh",
"${PODS_ROOT}/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMaps.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@ -399,7 +380,7 @@
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = G78D5X4L92;
@ -409,7 +390,6 @@
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
@ -529,7 +509,7 @@
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = G78D5X4L92;
@ -539,7 +519,6 @@
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
@ -557,7 +536,7 @@
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = G78D5X4L92;
@ -567,7 +546,6 @@
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",

View file

@ -1,12 +1,10 @@
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
#import "GoogleMaps/GoogleMaps.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GMSServices provideAPIKey:@"AIzaSyBkVgDYRQoKjCWlGMyl3V6ROzmLEsa5a0w"];
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];

View file

@ -2,8 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>io.flutter.embedded_views_preview</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>

View file

@ -3,8 +3,18 @@ import 'dart:async';
import 'package:http/http.dart' as http;
import 'package:local_spend/common/functions/get_token.dart';
Future<List<String>> getCategories() async {
const url = "https://dev.localspend.co.uk/api/search/category";
class Category {
String name;
String index;
Category({
this.name,
this.index,
});
}
Future<List<Category>> getCategories() async {
const url = "https://dev.peartrade.org/api/search/category";
var token;
await getToken().then((result) {
@ -12,10 +22,10 @@ Future<List<String>> getCategories() async {
});
Map<String, String> body = {
"session_key": token,
"session_key":token,
};
final response = await http.post(
final response = await http.post (
url,
body: json.encode(body),
);
@ -24,7 +34,7 @@ Future<List<String>> getCategories() async {
if (response.statusCode == 200) {
//request successful
List<String> categories = new List<String>();
List<Category> categories = new List<Category>();
Map responseMap = json.decode(response.body);
var categoriesJSON = responseMap['categories'];
@ -35,12 +45,14 @@ Future<List<String>> getCategories() async {
// print(categoriesJSON['11']); // prints "Banana"
int i = 1; // starts on 1. that was annoying to debug!
int i = 1; // starts on 1. that was annoying to debug!
while (true) {
if (categoriesJSON[i.toString()] != null) {
// print("Iteration " + i.toString());
// print(categoriesJSON[i.toString()]);
categories.add(categoriesJSON[i.toString()]);
categories.add(new Category(
name: categoriesJSON[i.toString()], index: i.toString()));
// print(categories.last);
i++;
} else {
@ -58,11 +70,9 @@ Future<List<String>> getCategories() async {
// });
// print(categories[10].name.toString()); // prints "Banana"
return categories; // categories is List<Category>
// Category is defined at the top^^
return categories;
} else {
// not successful
return null;
}
}
}

View file

@ -1,85 +1,109 @@
import 'dart:convert';
import 'dart:async';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:local_spend/common/functions/get_token.dart';
class Organisation {
Organisation(
this.id,
this.name,
this.postcode,
this.streetName,
this.town,
);
var id = 0;
var name = "";
var postcode = "";
var streetName = ""; //street_name
var streetName = ""; //street_name
var town = "";
Organisation(int id, String name, String postcode, String streetName, String town) {
this.id = id;
this.name = name;
this.postcode = postcode;
this.streetName = streetName; //street_name
this.town = town;
}
}
class Organisations {
List<Organisation> getTestData() {
var numItems = 10;
var itemsList = new List<Organisation>();
List<Organisation> jsonToOrganisations(String json) {
Map decoded = jsonDecode(json);
// print(decoded);
for (int i = 0; i < numItems; i++) {
itemsList.add(new Organisation(i, "Payee " + (i + 1).toString(),
"tee hee hee", "yeet street", "Robloxia"));
}
List<dynamic> validated = decoded['validated'];
// Map organisation = validated[0];
//
// print("");
// print("Response:");
// for (var i = 0; i < validated.length; i++) {
// print(validated[i]);
// }
return itemsList;
}
List<Map> organisationsMaps = new List<Map>();
List<Organisation> _jsonToOrganisations(String json) {
Map decoded = jsonDecode(json);
List<dynamic> validated = decoded['validated'];
List<Map> organisationsMaps = new List<Map>();
validated.forEach((element) => organisationsMaps.add(element));
List<Organisation> organisations = new List<Organisation>();
validated.forEach((element) => organisationsMaps.add(element));
for (var i = 0; i < organisationsMaps.length; i++) {
final params = organisationsMaps[i].values.toList();
// print("");
// print("organisationsMaps:");
// print(organisationsMaps);
var newOrganisation = new Organisation(
params[0].toInt(),
params[1].toString(),
params[2].toString(), // oof
params[3].toString(), // this could be improved...
params[4].toString(),
);
List<Organisation> organisations = new List<Organisation>();
// organisationsMaps[0].forEach((k,v) => print('${k}: ${v}'));
organisations.add(newOrganisation);
}
for (var i = 0; i < organisationsMaps.length; i++) {
final params = organisationsMaps[i].values.toList();
return organisations;
}
Future<List<Organisation>> findOrganisations(String search) async {
final url = "https://dev.localspend.co.uk/api/search";
var token;
await getToken().then((result) {
token = result;
});
Map<String, String> body = {
"search_name": search,
"session_key": token,
};
final response = await http.post(
url,
body: json.encode(body),
var newOrganisation = new Organisation(
params[0].toInt(),
params[1].toString(),
params[2].toString(), // oof
params[3].toString(), // this could be improved...
params[4].toString(),
);
if (response.statusCode == 200) {
//request successful
return _jsonToOrganisations(response.body);
} else {
// not successful
return null;
}
organisations.add(newOrganisation);
}
// the reason some organizations do not show up is because they are not all validated
// option to 'show unvalidated' should be added along with maybe a settings section
//
// print("");
// print("Local:");
// for (var i = 0; i < organisations.length; i++)
// {
// print(organisations[i].name);
// }
// print("");
return organisations;
}
Future<List<Organisation>> findOrganisations(String search) async {
final url = "https://dev.peartrade.org/api/search";
var token;
await getToken().then((result) {
token = result;
});
Map<String, String> body = {
"search_name":search,
"session_key":token,
};
final response = await http.post (
url,
body: json.encode(body),
);
// print(response.body);
if (response.statusCode == 200) {
//request successful
return jsonToOrganisations(response.body);
} else {
// not successful
return null;
}
}
class OrganizationController extends TextEditingController {
Organisation organisation;
}

View file

@ -1,270 +0,0 @@
import 'dart:convert';
import 'dart:async';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:charts_flutter/flutter.dart' as charts;
/// Customer graph types: https://dev.localspend.co.uk/api/v1/customer/graphs
/// - total_last_week
/// - avg_spend_last_week
/// - total_last_month
/// - avg_spend_last_month
/// Organisations' graphs types: to fetch, POST to https://dev.localspend.co.uk/api/stats/[name] as {"session_key":"[boop beep]"}
/// - organisations_all : organisation
/// - pies : organisation/pies
/// - snippets : organisation/snippets
/// - graphs : organisation/graphs
/// - {"graph":"customers_last_7_days","session_key":"[bleep]"}
/// - {"graph":"customers_last_30_days","session_key":"[blah]"}
/// - {"graph":"sales_last_7_days","session_key":"[bloop]"}
/// - {"graph":"sales_last_7_days","session_key":"[reee]"}
/// - {"graph":"purchases_last_7_days","session_key":"[yee]"}
/// - {"graph":"purchases_last_30_days","session_key":"[yah]"}
/// - {"graph":"purchases_all;","session_key":"[kappa]"} // I don't think this one works
///
/// HTTP POST request sample:
/// {"graph":"total_last_week","session_key":"blahblahblah"}
class OrganisationGraph {
OrganisationGraph(this.chartType, {this.graphsType = ""});
String graphsType =
""; // type of graph, eg customers_last_7_days, sales_last_30_days, purchases_last_30_days etc
String
chartType; // type of chart, eg organisations_all, pies, snippets or graphs
List<charts.Series<TimeSeriesCustomersOrSales, DateTime>> graph;
List<TimeSeriesCustomersOrSales> cachedData;
bool loaded = false;
Future<void> getGraphData() async {
if (loaded) {
this.graph = [
new charts.Series<TimeSeriesCustomersOrSales, DateTime>(
id: 'Chart',
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
domainFn: (TimeSeriesCustomersOrSales spend, _) => spend.time,
measureFn: (TimeSeriesCustomersOrSales spend, _) =>
spend.numberOfStuff,
data: cachedData,
)
];
return null;
}
String url = "https://dev.localspend.co.uk/api/v1/organisation/";
if (!(this.chartType == "organisations_all")) {
url += this.chartType;
}
SharedPreferences preferences = await SharedPreferences.getInstance();
Map<String, String> body;
if (this.chartType == "graphs") {
body = {
'graph': this.graphsType,
'session_key': preferences.get('LastToken'),
};
} else {
body = {
'session_key': preferences.get('LastToken'),
};
}
print(url);
print(json.encode(body).toString());
final response = await http.post(
url,
body: json.encode(body),
);
try {
if (response.statusCode == 200) {
final responseJson = jsonDecode(response.body);
final List<dynamic> labels = responseJson['graph']['labels'];
final List<dynamic> data = responseJson['graph']['data'];
List<TimeSeriesCustomersOrSales> graphDataList =
new List<TimeSeriesCustomersOrSales>();
for (int i = 0; i < labels.length; i++) {
graphDataList.add(new TimeSeriesCustomersOrSales(
data[i] * 1.00, DateTime.parse(labels[i])));
}
this.cachedData = graphDataList;
this.loaded = true;
this.graph = [
new charts.Series<TimeSeriesCustomersOrSales, DateTime>(
id: 'Chart',
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
domainFn: (TimeSeriesCustomersOrSales spend, _) => spend.time,
measureFn: (TimeSeriesCustomersOrSales spend, _) =>
spend.numberOfStuff,
data: graphDataList,
)
];
return this.graph;
} else {
final errorMessage = json.decode(response.body)['message'];
print("Error: " + errorMessage);
this.graph = null;
}
} catch (error) {
print(error.toString());
}
}
}
class GraphData {
GraphData(
this.chartType,
);
var chartType;
List<charts.Series<dynamic, DateTime>> graph;
List<TimeSeriesSpend> cachedData;
bool loaded = false;
Future<List<charts.Series<dynamic, DateTime>>> setGraphData() async {
if (loaded) {
this.graph = [
new charts.Series<TimeSeriesSpend, DateTime>(
id: 'Spend',
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
domainFn: (TimeSeriesSpend spend, _) => spend.time,
measureFn: (TimeSeriesSpend spend, _) => spend.spend,
data: cachedData,
)
];
return null;
}
final url = "https://dev.localspend.co.uk/api/v1/customer/graphs";
SharedPreferences preferences = await SharedPreferences.getInstance();
Map<String, String> body = {
'graph': this.chartType,
'session_key': preferences.get('LastToken'),
};
final response = await http.post(
url,
body: json.encode(body),
);
if (response.statusCode == 200) {
final responseJson = jsonDecode(response.body);
final List<dynamic> labels = responseJson['graph']['labels'];
final List<dynamic> data = responseJson['graph']['data'];
List<TimeSeriesSpend> timeSeriesSpendList = new List<TimeSeriesSpend>();
for (int i = 0; i < labels.length; i++) {
timeSeriesSpendList.add(
new TimeSeriesSpend(data[i] * 1.00, DateTime.parse(labels[i])));
// print(timeSeriesSpendList[i].time.toString() + " : " + timeSeriesSpendList[i].spend.toString());
}
cachedData = timeSeriesSpendList;
loaded = true;
this.graph = [
new charts.Series<TimeSeriesSpend, DateTime>(
id: 'Spend',
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
domainFn: (TimeSeriesSpend spend, _) => spend.time,
measureFn: (TimeSeriesSpend spend, _) => spend.spend,
data: timeSeriesSpendList,
)
];
return this.graph;
} else {
final errorMessage = json.decode(response.body)['message'];
print("Error: " + errorMessage);
this.graph = null;
return null;
}
}
Future<List<charts.Series<dynamic, DateTime>>> getGraphData() async {
if (loaded == true) {
return [
new charts.Series<TimeSeriesSpend, DateTime>(
id: 'Spend',
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
domainFn: (TimeSeriesSpend spend, _) => spend.time,
measureFn: (TimeSeriesSpend spend, _) => spend.spend,
data: cachedData,
)
];
}
final url = "https://dev.localspend.co.uk/api/v1/customer/graphs";
SharedPreferences preferences = await SharedPreferences.getInstance();
Map<String, String> body = {
'graph': this.chartType,
'session_key': preferences.get('LastToken'),
};
final response = await http.post(
url,
body: json.encode(body),
);
if (response.statusCode == 200) {
final responseJson = jsonDecode(response.body);
final List<dynamic> labels = responseJson['graph']['labels'];
final List<dynamic> data = responseJson['graph']['data'];
List<TimeSeriesSpend> timeSeriesSpendList = new List<TimeSeriesSpend>();
for (int i = 0; i < labels.length; i++) {
timeSeriesSpendList.add(
new TimeSeriesSpend(data[i] * 1.00, DateTime.parse(labels[i])));
// print(timeSeriesSpendList[i].time.toString() + " : " + timeSeriesSpendList[i].spend.toString());
}
cachedData = timeSeriesSpendList;
loaded = true;
return [
new charts.Series<TimeSeriesSpend, DateTime>(
id: 'Spend',
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
domainFn: (TimeSeriesSpend spend, _) => spend.time,
measureFn: (TimeSeriesSpend spend, _) => spend.spend,
data: timeSeriesSpendList,
)
];
} else {
final errorMessage = json.decode(response.body)['message'];
print("Error: " + errorMessage);
return null;
}
}
}
class TimeSeriesSpend {
TimeSeriesSpend(this.spend, this.time);
final DateTime time;
final double spend;
}
class TimeSeriesCustomersOrSales {
TimeSeriesCustomersOrSales(this.numberOfStuff, this.time);
final DateTime time;
final double numberOfStuff;
}

View file

@ -1,87 +0,0 @@
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:json_annotation/json_annotation.dart';
import 'package:local_spend/common/apifunctions/find_organisations.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart' as gmaps;
// /v1/supplier/location
@JsonSerializable()
class LatLng {
LatLng({
this.lat,
this.lng
});
final double lat, lng;
}
@JsonSerializable()
class Region {
Region({
this.coords,
this.id,
this.name,
this.zoom
});
final LatLng coords;
final String id, name;
final double zoom;
}
@JsonSerializable()
class Location {
Location({
this.organisation,
this.lat,
this.lng
});
final Organisation organisation;
final double lat, lng;
}
@JsonSerializable()
class Locations {
Locations({
this.locations,
this.regions
});
final List<Location> locations;
final List<Region> regions;
}
Future<Locations> getLocations(gmaps.LatLng ne, gmaps.LatLng sw) async {
const pearLocationsURL = 'https://dev.localspend.co.uk/api/v1/supplier/location';
SharedPreferences preferences = await SharedPreferences.getInstance();
Map<String, Map<String, double>> mapData = {
'session_key': preferences.get('LastToken'),
'north_east': {
'latitude': ne.latitude,
'longitude': ne.longitude
},
'south_west': {
'latitude': sw.latitude,
'longitude': sw.longitude
},
};
final response = await http.post(
pearLocationsURL,
body: json.encode(mapData),
);
if (response.statusCode == 200) {
print(response.body.toString());
} else {
print(response.body.toString());
throw HttpException(
'Error - ' + response.reasonPhrase,
);
}
}

View file

@ -4,72 +4,49 @@ 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';
Future<void> _incorrectDialog(BuildContext context, bool isLoginWrong) async {
return showDialog<void>(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return AnimatedContainer(
duration: Duration(seconds: 2),
child: AlertDialog(
title: Text("Uh-oh!"),
content: Text(isLoginWrong
? "Incorrect login details. Please try again."
: "Our servers are having issues at the moment; sorry for the inconvenience. Please try again later."),
actions: <Widget>[
FlatButton(
child: Text('OK'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
);
},
);
}
Future<LoginModel> requestLoginAPI(
BuildContext context, String email, String password) async {
final url = "https://dev.localspend.co.uk/api/login";
//var apiUrl = ConfigWrapper.of(context).apiKey;
final url = "https://dev.peartrade.org/api/login";
Map<String, String> body = {
'email': email,
'password': password,
};
try {
final response = await http
.post(
url,
body: json.encode(body),
)
.timeout(Duration(seconds: 5));
// debugPrint('$body');
if (response.statusCode == 200) {
final responseJson = json.decode(response.body);
final response = await http.post(
url,
body: json.encode(body),
);
saveCurrentLogin(responseJson, body["email"]);
await Navigator.of(context).pushReplacementNamed('/HomePage');
// debugPrint(response.body);
return LoginModel.fromJson(responseJson);
} else {
final responseJson = json.decode(response.body);
if (response.statusCode == 200) {
final responseJson = json.decode(response.body);
var user = new LoginModel.fromJson(responseJson);
saveCurrentLogin(responseJson, body["email"]);
saveCurrentLogin(responseJson, body["email"]);
Navigator.of(context).pushReplacementNamed('/ReceiptPage');
await _incorrectDialog(context, true);
return LoginModel.fromJson(responseJson);
} else {
// debugPrint("Invalid, either credentials are wrong or server is down");
return null;
}
} on TimeoutException catch (_) {
await _incorrectDialog(context, false);
} catch (error) {
debugPrint(error.toString());
await _incorrectDialog(context, false);
final responseJson = json.decode(response.body);
saveCurrentLogin(responseJson, body["email"]);
showDialogSingleButton(
context,
"Unable to Login",
"You may have supplied an invalid 'Email' / 'Password' combination. Please try again or email an administrator.",
"OK");
return null;
}
return null;
}

View file

@ -1,14 +1,14 @@
import 'dart:async';
import 'dart:convert';
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<bool> requestLogoutAPI() async {
saveLogout();
final url = "https://dev.localspend.co.uk/api/logout";
Future<LoginModel> requestLogoutAPI(BuildContext context) async {
final url = "https://dev.peartrade.org/api/logout";
var token;
@ -17,13 +17,23 @@ Future<bool> requestLogoutAPI() async {
});
Map<String, String> body = {
"Token": token,
"Token":token,
};
await http.post(
final response = await http.post(
url,
body: json.encode(body),
);
return true;
if (response.statusCode == 200) {
// debugPrint("Logout successful: " + response.body);
saveLogout();
return null;
} else {
// debugPrint("Logout unsuccessful: " + response.body);
saveLogout();
return null;
}
}

View file

@ -23,12 +23,12 @@ class Receipt {
Future<LoginModel> submitReceiptAPI(
BuildContext context, Receipt receipt) async {
//var apiUrl = ConfigWrapper.of(context).apiKey;
final url = "https://dev.localspend.co.uk/api/upload";
final url = "https://dev.peartrade.org/api/upload";
SharedPreferences preferences = await SharedPreferences.getInstance();
Map<String, String> body = {
'transaction_type': "3",
'transaction_type' : "3",
'transaction_value': receipt.amount,
'purchase_time': receipt.time,
'category': receipt.category,
@ -37,12 +37,14 @@ Future<LoginModel> submitReceiptAPI(
'recurring': receipt.recurring,
'street_name': receipt.street,
'postcode': receipt.postcode,
'town': receipt.town,
'session_key': preferences.get('LastToken'),
};
// debugPrint('$body');
debugPrint(json.encode(body));
// debugPrint(json.encode(body));
final response = await http.post(
url,
@ -60,11 +62,13 @@ Future<LoginModel> submitReceiptAPI(
context,
responseJson[0] == "" ? responseJson[0] : "Upload Successful",
"Transaction successfully submitted to server",
"OK");
"OK"
);
return LoginModel.fromJson(responseJson);
} else {
final responseJson = json.decode(response.body);
showDialogSingleButton(
context,
"Unable to Submit Receipt",

View file

@ -103,8 +103,7 @@ class AboutListTile extends StatelessWidget {
return ListTile(
leading: icon,
title: child ??
Text(MaterialLocalizations.of(context).aboutListTileTitle(
applicationName ?? _defaultApplicationName(context))),
Text(MaterialLocalizations.of(context).aboutListTileTitle(applicationName ?? _defaultApplicationName(context))),
onTap: () {
showAboutDialog(
context: context,
@ -179,14 +178,13 @@ void showLicensePage({
String applicationLegalese,
}) {
assert(context != null);
Navigator.push(
context,
MaterialPageRoute<void>(
builder: (BuildContext context) => LicensePage(
applicationName: applicationName,
applicationVersion: applicationVersion,
applicationLegalese: applicationLegalese,
)));
Navigator.push(context, MaterialPageRoute<void>(
builder: (BuildContext context) => LicensePage(
applicationName: applicationName,
applicationVersion: applicationVersion,
applicationLegalese: applicationLegalese,
)
));
}
/// An about box. This is a dialog box with the application's icon, name,
@ -258,27 +256,19 @@ class AboutDialog extends StatelessWidget {
Widget build(BuildContext context) {
assert(debugCheckHasMaterialLocalizations(context));
final String name = applicationName ?? _defaultApplicationName(context);
final String version =
applicationVersion ?? _defaultApplicationVersion(context);
final String version = applicationVersion ?? _defaultApplicationVersion(context);
final Widget icon = applicationIcon ?? _defaultApplicationIcon(context);
List<Widget> body = <Widget>[];
if (icon != null) {
body.add(
IconTheme(data: const IconThemeData(size: 45.0), child: icon),
);
}
if (icon != null)
body.add(IconTheme(data: const IconThemeData(size: 45.0), child: icon));
body.add(Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: ListBody(
children: <Widget>[
Container(
child: Text(name, style: Theme.of(context).textTheme.title),
),
Text(name, style: Theme.of(context).textTheme.title),
Text(version, style: Theme.of(context).textTheme.body1),
Text(applicationLegalese ?? '',
style: Theme.of(context).textTheme.caption),
Text(applicationLegalese ?? '', style: Theme.of(context).textTheme.caption),
],
),
),
@ -289,15 +279,15 @@ class AboutDialog extends StatelessWidget {
children: body,
),
];
if (children != null) body.addAll(children);
if (children != null)
body.addAll(children);
return AlertDialog(
content: SingleChildScrollView(
child: ListBody(children: body),
),
actions: <Widget>[
FlatButton(
child:
Text(MaterialLocalizations.of(context).viewLicensesButtonLabel),
child: Text(MaterialLocalizations.of(context).viewLicensesButtonLabel),
onPressed: () {
showLicensePage(
context: context,
@ -382,7 +372,7 @@ class _LicensePageState extends State<LicensePage> {
int debugFlowId = -1;
assert(() {
final dev.Flow flow = dev.Flow.begin();
dev.Timeline.timeSync('_initLicenses()', () {}, flow: flow);
dev.Timeline.timeSync('_initLicenses()', () { }, flow: flow);
debugFlowId = flow.id;
return true;
}());
@ -391,12 +381,11 @@ class _LicensePageState extends State<LicensePage> {
return;
}
assert(() {
dev.Timeline.timeSync('_initLicenses()', () {},
flow: dev.Flow.step(debugFlowId));
dev.Timeline.timeSync('_initLicenses()', () { }, flow: dev.Flow.step(debugFlowId));
return true;
}());
final List<LicenseParagraph> paragraphs =
await SchedulerBinding.instance.scheduleTask<List<LicenseParagraph>>(
await SchedulerBinding.instance.scheduleTask<List<LicenseParagraph>>(
license.paragraphs.toList,
Priority.animation,
debugLabel: 'License',
@ -411,7 +400,8 @@ class _LicensePageState extends State<LicensePage> {
));
_licenses.add(Container(
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(width: 0.0))),
border: Border(bottom: BorderSide(width: 0.0))
),
child: Text(
license.packages.join(', '),
style: const TextStyle(fontWeight: FontWeight.bold),
@ -431,8 +421,7 @@ class _LicensePageState extends State<LicensePage> {
} else {
assert(paragraph.indent >= 0);
_licenses.add(Padding(
padding: EdgeInsetsDirectional.only(
top: 8.0, start: 16.0 * paragraph.indent),
padding: EdgeInsetsDirectional.only(top: 8.0, start: 16.0 * paragraph.indent),
child: Text(paragraph.text),
));
}
@ -443,8 +432,7 @@ class _LicensePageState extends State<LicensePage> {
_loaded = true;
});
assert(() {
dev.Timeline.timeSync('Build scheduled', () {},
flow: dev.Flow.end(debugFlowId));
dev.Timeline.timeSync('Build scheduled', () { }, flow: dev.Flow.end(debugFlowId));
return true;
}());
}
@ -452,27 +440,16 @@ class _LicensePageState extends State<LicensePage> {
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterialLocalizations(context));
final String name =
widget.applicationName ?? _defaultApplicationName(context);
final String version =
widget.applicationVersion ?? _defaultApplicationVersion(context);
final MaterialLocalizations localizations =
MaterialLocalizations.of(context);
final String name = widget.applicationName ?? _defaultApplicationName(context);
final String version = widget.applicationVersion ?? _defaultApplicationVersion(context);
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final List<Widget> contents = <Widget>[
Text(name,
style: Theme.of(context).textTheme.headline,
textAlign: TextAlign.center),
Text(version,
style: Theme.of(context).textTheme.body1,
textAlign: TextAlign.center),
Text(name, style: Theme.of(context).textTheme.headline, textAlign: TextAlign.center),
Text(version, style: Theme.of(context).textTheme.body1, textAlign: TextAlign.center),
Container(height: 18.0),
Text(widget.applicationLegalese ?? '',
style: Theme.of(context).textTheme.caption,
textAlign: TextAlign.center),
Text(widget.applicationLegalese ?? '', style: Theme.of(context).textTheme.caption, textAlign: TextAlign.center),
Container(height: 18.0),
Text('Powered by Flutter',
style: Theme.of(context).textTheme.body1,
textAlign: TextAlign.center),
Text('Powered by Flutter', style: Theme.of(context).textTheme.body1, textAlign: TextAlign.center),
Container(height: 24.0),
];
contents.addAll(_licenses);
@ -499,8 +476,7 @@ class _LicensePageState extends State<LicensePage> {
bottom: false,
child: Scrollbar(
child: ListView(
padding:
const EdgeInsets.symmetric(horizontal: 8.0, vertical: 12.0),
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 12.0),
children: contents,
),
),
@ -519,8 +495,7 @@ String _defaultApplicationName(BuildContext context) {
// can provide an explicit applicationName to the widgets defined in this
// file, instead of relying on the default.
final Title ancestorTitle = context.ancestorWidgetOfExactType(Title);
return ancestorTitle?.title ??
Platform.resolvedExecutable.split(Platform.pathSeparator).last;
return ancestorTitle?.title ?? Platform.resolvedExecutable.split(Platform.pathSeparator).last;
}
String _defaultApplicationVersion(BuildContext context) {

View file

@ -2,7 +2,8 @@ import 'package:flutter/material.dart';
final TextEditingController _feedbackController = TextEditingController();
void feedback(BuildContext context) {
void feedback(
BuildContext context) {
// flutter defined function
showDialog(
context: context,
@ -14,11 +15,13 @@ void feedback(BuildContext context) {
children: <Widget>[
Text("We would love to hear your thoughts."),
Text("\nThis section needs to be fixed."),
Container(
margin: const EdgeInsets.fromLTRB(0, 20, 0, 0),
width: 200,
height: 70,
child: new TextField(
child : new TextField(
controller: _feedbackController,
decoration: InputDecoration(
hintText: 'Enter your feedback here',
@ -38,7 +41,7 @@ void feedback(BuildContext context) {
),
new Container(
padding: const EdgeInsets.fromLTRB(0, 10, 0, 0),
child: new FlatButton(
child : new FlatButton(
child: new Text("Submit"),
onPressed: () {
submit(_feedbackController.text);
@ -55,4 +58,4 @@ void feedback(BuildContext context) {
void submit(String feedback) {
debugPrint(feedback);
}
}

View file

@ -1,7 +1,6 @@
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:async';
Future<String> getToken() async {
getToken() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
String getToken = preferences.getString("LastToken");

View file

@ -2,16 +2,16 @@ import 'package:flutter/material.dart';
import 'package:local_spend/common/apifunctions/request_logout_api.dart';
import 'package:shared_preferences/shared_preferences.dart';
void logout(context) {
_clearLoginDetails().then((_) {
requestLogoutAPI();
Navigator.of(context).pushReplacementNamed('/LoginPage');
});
logout(context) {
requestLogoutAPI(context);
Navigator.of(context).pushReplacementNamed('/LoginPage');
_clearLoginDetails();
}
Future<void> _clearLoginDetails() async {
_clearLoginDetails() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
await preferences.setString('username', "");
await preferences.setString('password', "");
}
preferences.setString('username', "");
preferences.setString('password', "");
print("details cleared");
}

View file

@ -1,7 +1,7 @@
import 'package:local_spend/model/json/login_model.dart';
import 'package:shared_preferences/shared_preferences.dart';
void saveCurrentLogin(Map responseJson, loginEmail) async {
saveCurrentLogin(Map responseJson, loginEmail) async {
SharedPreferences preferences = await SharedPreferences.getInstance();
var user;
@ -13,17 +13,14 @@ void saveCurrentLogin(Map responseJson, loginEmail) async {
var token = (responseJson != null && responseJson.isNotEmpty)
? LoginModel.fromJson(responseJson).token
: "";
var email = (loginEmail != null) ? loginEmail : "";
var userType = (responseJson != null && responseJson.isNotEmpty)
? LoginModel.fromJson(responseJson).userType
var email = (loginEmail != null)
? loginEmail
: "";
await preferences.setString(
'LastUser', (user != null && user.length > 0) ? user : "");
await preferences.setString(
'LastToken', (token != null && token.isNotEmpty) ? token : "");
'LastToken', (token != null && token.length > 0) ? token : "");
await preferences.setString(
'LastEmail', (email != null && email.length > 0) ? email : "");
await preferences.setString('LastUserType',
(userType != null && userType.isNotEmpty) ? userType : "");
}

View file

@ -1,6 +1,6 @@
import 'package:shared_preferences/shared_preferences.dart';
void saveLogout() async {
saveLogout() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
await preferences.setString('LastUser', "");

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
void showDialogTwoButtons(BuildContext context, String title, String message,
String buttonLabel1, String buttonLabel2, Function action) {
void showDialogTwoButtons(
BuildContext context, String title, String message, String buttonLabel1, String buttonLabel2, Function action) {
// flutter defined function
showDialog(
context: context,

View file

@ -4,24 +4,6 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class PlatformScaffold extends StatelessWidget {
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
final Key key;
final PreferredSizeWidget appBar;
final Widget body;
@ -36,6 +18,23 @@ class PlatformScaffold extends StatelessWidget {
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

View file

@ -1,40 +0,0 @@
import 'package:flutter/material.dart';
import 'package:simple_animations/simple_animations.dart';
class AnimatedBackground extends StatelessWidget {
AnimatedBackground(
this.animateColors,
this.lastColor,
this.begin,
this.end,
this.duration,
);
final List<Color> animateColors;
final Color lastColor;
final Alignment begin, end;
final int duration;
@override
Widget build(BuildContext context) {
final tween = MultiTrackTween([
Track("color1").add(Duration(seconds: this.duration),
ColorTween(begin: this.animateColors[0], end: this.animateColors[1])),
]);
return ControlledAnimation(
playback: Playback.MIRROR,
tween: tween,
duration: tween.duration,
builder: (context, animation) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: this.begin,
end: this.end,
colors: [animation["color1"], this.lastColor])),
);
},
);
}
}

View file

@ -0,0 +1,71 @@
import 'package:flutter/material.dart';
import 'dart:io' show Platform;
class AwesomeDrawer {
Widget getDrawer(BuildContext context) {
return new Container(
width: 250,
color: Colors.white,
// padding: EdgeInsets.fromLTRB(32, 25, 32, 0),
child: ListView(
children: <Widget> [
Container(
padding: const EdgeInsets.fromLTRB(0, 15, 0, 0),
child :
FlatButton(onPressed: () => Navigator.of(context).pushReplacementNamed("/ReceiptPage"),
child:
Text("Submit Receipt",
textAlign: Platform.isIOS ? TextAlign.right : TextAlign.left,
style: new TextStyle(
fontSize: 32.0,
color: Colors.black,
),
),
),
),
Container(
padding: const EdgeInsets.fromLTRB(0, 15, 0, 0),
child :
FlatButton(onPressed: () => Navigator.of(context).pushReplacementNamed("/StatsPage"), child:
Text("Statistics",
textAlign: Platform.isIOS ? TextAlign.right : TextAlign.left,
style: new TextStyle(
fontSize: 32.0,
color: Colors.black,
),
),
),
),
Container(
padding: const EdgeInsets.fromLTRB(0, 15, 0, 0),
child :
FlatButton(onPressed: () => Navigator.of(context).pushReplacementNamed("/MorePage"), child:
Text("More",
style: new TextStyle(
fontSize: 32.0,
color: Colors.black,
),
),
),
),
Container(
padding: const EdgeInsets.fromLTRB(0, 15, 0, 0),
child :
FlatButton(onPressed: () => debugPrint("pressed"), child:
Text("reeeeeeeeee",
style: new TextStyle(
fontSize: 32.0,
color: Colors.black,
),
),
),
),
]
),
);
}
}

View file

@ -4,6 +4,9 @@ import 'package:charts_flutter/flutter.dart' as charts;
import 'package:flutter/material.dart';
class DonutAutoLabelChart extends StatelessWidget {
final List<charts.Series> seriesList;
final bool animate;
DonutAutoLabelChart(this.seriesList, {this.animate});
/// Creates a [PieChart] with sample data and no transition.
@ -15,8 +18,6 @@ class DonutAutoLabelChart extends StatelessWidget {
);
}
final List<charts.Series> seriesList;
final bool animate;
@override
Widget build(BuildContext context) {
@ -66,8 +67,8 @@ class DonutAutoLabelChart extends StatelessWidget {
/// Sample linear data type.
class LinearSales {
LinearSales(this.key, this.sales);
final String key;
final int sales;
}
LinearSales(this.key, this.sales);
}

View file

@ -1,18 +0,0 @@
import 'package:flutter/material.dart';
import 'package:local_spend/common/widgets/charts/time_series_simple.dart';
class TimeSeries extends StatelessWidget {
TimeSeries({
this.chartDataName,
});
final String chartDataName;
@override
Widget build(BuildContext context) {
return new Container(
padding: EdgeInsets.symmetric(horizontal: 7.5, vertical: 7.5),
child: SimpleTimeSeriesChart.withSampleData(),
);
}
}

View file

@ -3,6 +3,9 @@ import 'package:charts_flutter/flutter.dart' as charts;
import 'package:flutter/material.dart';
class DonutPieChart extends StatelessWidget {
final List<charts.Series> seriesList;
final bool animate;
DonutPieChart(this.seriesList, {this.animate});
/// Creates a [PieChart] with sample data and no transition.
@ -13,8 +16,6 @@ class DonutPieChart extends StatelessWidget {
);
}
final List<charts.Series> seriesList;
final bool animate;
@override
Widget build(BuildContext context) {
@ -45,10 +46,11 @@ class DonutPieChart extends StatelessWidget {
}
}
/// Sample linear data type.
class LinearSales {
LinearSales(this.year, this.sales);
final int year;
final int sales;
}
LinearSales(this.year, this.sales);
}

View file

@ -3,6 +3,9 @@ import 'package:flutter/material.dart';
import 'package:charts_flutter/flutter.dart' as charts;
class GroupedBarChart extends StatelessWidget {
final List<charts.Series> seriesList;
final bool animate;
GroupedBarChart(this.seriesList, {this.animate});
factory GroupedBarChart.withSampleData() {
@ -13,8 +16,6 @@ class GroupedBarChart extends StatelessWidget {
);
}
final List<charts.Series> seriesList;
final bool animate;
@override
Widget build(BuildContext context) {
@ -73,8 +74,8 @@ class GroupedBarChart extends StatelessWidget {
/// Sample ordinal data type.
class OrdinalSales {
OrdinalSales(this.year, this.sales);
final String year;
final int sales;
}
OrdinalSales(this.year, this.sales);
}

View file

@ -4,6 +4,9 @@ import 'package:charts_flutter/flutter.dart' as charts;
import 'package:flutter/material.dart';
class NumericComboLineBarChart extends StatelessWidget {
final List<charts.Series> seriesList;
final bool animate;
NumericComboLineBarChart(this.seriesList, {this.animate});
/// Creates a [LineChart] with sample data and no transition.
@ -15,8 +18,6 @@ class NumericComboLineBarChart extends StatelessWidget {
);
}
final List<charts.Series> seriesList;
final bool animate;
@override
Widget build(BuildContext context) {
@ -28,7 +29,7 @@ class NumericComboLineBarChart extends StatelessWidget {
// Custom renderer configuration for the bar series.
customSeriesRenderers: [
new charts.BarRendererConfig(
// ID used to link series to this renderer.
// ID used to link series to this renderer.
customRendererId: 'customBar')
]);
}
@ -64,7 +65,7 @@ class NumericComboLineBarChart extends StatelessWidget {
measureFn: (LinearSales sales, _) => sales.sales,
data: desktopSalesData,
)
// Configure our custom bar renderer for this series.
// Configure our custom bar renderer for this series.
..setAttribute(charts.rendererIdKey, 'customBar'),
new charts.Series<LinearSales, int>(
id: 'Tablet',
@ -73,7 +74,7 @@ class NumericComboLineBarChart extends StatelessWidget {
measureFn: (LinearSales sales, _) => sales.sales,
data: tableSalesData,
)
// Configure our custom bar renderer for this series.
// Configure our custom bar renderer for this series.
..setAttribute(charts.rendererIdKey, 'customBar'),
new charts.Series<LinearSales, int>(
id: 'Mobile',
@ -87,8 +88,8 @@ class NumericComboLineBarChart extends StatelessWidget {
/// Sample linear data type.
class LinearSales {
LinearSales(this.year, this.sales);
final int year;
final int sales;
}
LinearSales(this.year, this.sales);
}

View file

@ -3,6 +3,9 @@ import 'package:charts_flutter/flutter.dart' as charts;
import 'package:flutter/material.dart';
class PieOutsideLabelChart extends StatelessWidget {
final List<charts.Series> seriesList;
final bool animate;
PieOutsideLabelChart(this.seriesList, {this.animate});
/// Creates a [PieChart] with sample data and no transition.
@ -14,8 +17,6 @@ class PieOutsideLabelChart extends StatelessWidget {
);
}
final List<charts.Series> seriesList;
final bool animate;
@override
Widget build(BuildContext context) {
@ -61,8 +62,8 @@ class PieOutsideLabelChart extends StatelessWidget {
/// Sample linear data type.
class LinearSales {
LinearSales(this.year, this.sales);
final int year;
final int sales;
}
LinearSales(this.year, this.sales);
}

View file

@ -8,6 +8,9 @@ import 'package:charts_flutter/flutter.dart' as charts;
import 'package:flutter/material.dart';
class BucketingAxisScatterPlotChart extends StatelessWidget {
final List<charts.Series> seriesList;
final bool animate;
BucketingAxisScatterPlotChart(this.seriesList, {this.animate});
/// Creates a [ScatterPlotChart] with sample data and no transition.
@ -19,8 +22,6 @@ class BucketingAxisScatterPlotChart extends StatelessWidget {
);
}
final List<charts.Series> seriesList;
final bool animate;
@override
Widget build(BuildContext context) {
@ -76,7 +77,7 @@ class BucketingAxisScatterPlotChart extends StatelessWidget {
new charts.Series<LinearSales, int>(
id: 'Cheese',
colorFn: (LinearSales sales, _) =>
charts.MaterialPalette.blue.shadeDefault,
charts.MaterialPalette.blue.shadeDefault,
domainFn: (LinearSales sales, _) => sales.year,
measureFn: (LinearSales sales, _) => sales.revenueShare,
radiusPxFn: (LinearSales sales, _) => sales.radius,
@ -84,7 +85,7 @@ class BucketingAxisScatterPlotChart extends StatelessWidget {
new charts.Series<LinearSales, int>(
id: 'Carrots',
colorFn: (LinearSales sales, _) =>
charts.MaterialPalette.red.shadeDefault,
charts.MaterialPalette.red.shadeDefault,
domainFn: (LinearSales sales, _) => sales.year,
measureFn: (LinearSales sales, _) => sales.revenueShare,
radiusPxFn: (LinearSales sales, _) => sales.radius,
@ -92,7 +93,7 @@ class BucketingAxisScatterPlotChart extends StatelessWidget {
new charts.Series<LinearSales, int>(
id: 'Cucumbers',
colorFn: (LinearSales sales, _) =>
charts.MaterialPalette.green.shadeDefault,
charts.MaterialPalette.green.shadeDefault,
domainFn: (LinearSales sales, _) => sales.year,
measureFn: (LinearSales sales, _) => sales.revenueShare,
radiusPxFn: (LinearSales sales, _) => sales.radius,
@ -100,7 +101,7 @@ class BucketingAxisScatterPlotChart extends StatelessWidget {
new charts.Series<LinearSales, int>(
id: 'Crayons',
colorFn: (LinearSales sales, _) =>
charts.MaterialPalette.purple.shadeDefault,
charts.MaterialPalette.purple.shadeDefault,
domainFn: (LinearSales sales, _) => sales.year,
measureFn: (LinearSales sales, _) => sales.revenueShare,
radiusPxFn: (LinearSales sales, _) => sales.radius,
@ -108,7 +109,7 @@ class BucketingAxisScatterPlotChart extends StatelessWidget {
new charts.Series<LinearSales, int>(
id: 'Celery',
colorFn: (LinearSales sales, _) =>
charts.MaterialPalette.indigo.shadeDefault,
charts.MaterialPalette.indigo.shadeDefault,
domainFn: (LinearSales sales, _) => sales.year,
measureFn: (LinearSales sales, _) => sales.revenueShare,
radiusPxFn: (LinearSales sales, _) => sales.radius,
@ -116,7 +117,7 @@ class BucketingAxisScatterPlotChart extends StatelessWidget {
new charts.Series<LinearSales, int>(
id: 'Cauliflower',
colorFn: (LinearSales sales, _) =>
charts.MaterialPalette.gray.shadeDefault,
charts.MaterialPalette.gray.shadeDefault,
domainFn: (LinearSales sales, _) => sales.year,
measureFn: (LinearSales sales, _) => sales.revenueShare,
radiusPxFn: (LinearSales sales, _) => sales.radius,
@ -127,9 +128,9 @@ class BucketingAxisScatterPlotChart extends StatelessWidget {
/// Sample linear data type.
class LinearSales {
LinearSales(this.year, this.revenueShare, this.radius);
final int year;
final double revenueShare;
final double radius;
}
LinearSales(this.year, this.revenueShare, this.radius);
}

View file

@ -11,6 +11,9 @@ import 'package:charts_flutter/flutter.dart' as charts;
///
/// Also shows the option to provide a custom measure formatter.
class LegendWithMeasures extends StatelessWidget {
final List<charts.Series> seriesList;
final bool animate;
LegendWithMeasures(this.seriesList, {this.animate});
factory LegendWithMeasures.withSampleData() {
@ -21,8 +24,6 @@ class LegendWithMeasures extends StatelessWidget {
);
}
final List<charts.Series> seriesList;
final bool animate;
@override
Widget build(BuildContext context) {
@ -121,8 +122,8 @@ class LegendWithMeasures extends StatelessWidget {
/// Sample ordinal data type.
class OrdinalSales {
OrdinalSales(this.year, this.sales);
final String year;
final int sales;
}
OrdinalSales(this.year, this.sales);
}

View file

@ -1,59 +0,0 @@
/// Timeseries chart example
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:flutter/material.dart';
class SimpleTimeSeriesChart extends StatelessWidget {
SimpleTimeSeriesChart(this.seriesList, {this.animate});
/// Creates a [TimeSeriesChart] with sample data and no transition.
factory SimpleTimeSeriesChart.withSampleData() {
return new SimpleTimeSeriesChart(
_createSampleData(),
// Disable animations for image tests.
animate: true,
);
}
final List<charts.Series> seriesList;
final bool animate;
@override
Widget build(BuildContext context) {
return new charts.TimeSeriesChart(
seriesList,
animate: animate,
// Optionally pass in a [DateTimeFactory] used by the chart. The factory
// should create the same type of [DateTime] as the data provided. If none
// specified, the default creates local date time.
dateTimeFactory: const charts.LocalDateTimeFactory(),
);
}
/// Create one series with sample hard coded data.
static List<charts.Series<TimeSeriesSales, DateTime>> _createSampleData() {
final data = [
new TimeSeriesSales(new DateTime(2017, 9, 19), 5),
new TimeSeriesSales(new DateTime(2017, 9, 26), 25),
new TimeSeriesSales(new DateTime(2017, 10, 3), 100),
new TimeSeriesSales(new DateTime(2017, 10, 10), 75),
];
return [
new charts.Series<TimeSeriesSales, DateTime>(
id: 'Sales',
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
domainFn: (TimeSeriesSales sales, _) => sales.time,
measureFn: (TimeSeriesSales sales, _) => sales.sales,
data: data,
)
];
}
}
/// Sample time series data type.
class TimeSeriesSales {
TimeSeriesSales(this.time, this.sales);
final DateTime time;
final int sales;
}

View file

@ -58,10 +58,12 @@ class CustomCheckbox extends StatefulWidget {
this.checkColor,
this.materialTapTargetSize,
this.useTapTarget = true,
}) : assert(tristate != null),
}) : assert(tristate != null),
assert(tristate || value != null),
super(key: key);
final bool useTapTarget;
/// Whether this checkbox is checked.
@ -136,8 +138,7 @@ class CustomCheckbox extends StatefulWidget {
_CustomCheckboxState createState() => _CustomCheckboxState();
}
class _CustomCheckboxState extends State<CustomCheckbox>
with TickerProviderStateMixin {
class _CustomCheckboxState extends State<CustomCheckbox> with TickerProviderStateMixin {
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
@ -146,26 +147,25 @@ class _CustomCheckboxState extends State<CustomCheckbox>
Size size;
switch (widget.materialTapTargetSize ?? themeData.materialTapTargetSize) {
case MaterialTapTargetSize.padded:
size = const Size(
2 * kRadialReactionRadius + 8.0, 2 * kRadialReactionRadius + 8.0);
size = const Size(2 * kRadialReactionRadius + 8.0, 2 * kRadialReactionRadius + 8.0);
break;
case MaterialTapTargetSize.shrinkWrap:
size = const Size(2 * kRadialReactionRadius, 2 * kRadialReactionRadius);
break;
}
Size noTapTargetSize = Size(CustomCheckbox.width, CustomCheckbox.width);
Size noTapTargetSize = Size(CustomCheckbox.width,
CustomCheckbox.width);
final BoxConstraints additionalConstraints =
BoxConstraints.tight(widget.useTapTarget ? size : noTapTargetSize);
BoxConstraints.tight(widget
.useTapTarget? size : noTapTargetSize);
return _CheckboxRenderObjectWidget(
value: widget.value,
tristate: widget.tristate,
activeColor: widget.activeColor ?? themeData.toggleableActiveColor,
checkColor: widget.checkColor ?? const Color(0xFFFFFFFF),
inactiveColor: widget.onChanged != null
? themeData.unselectedWidgetColor
: themeData.disabledColor,
inactiveColor: widget.onChanged != null ? themeData.unselectedWidgetColor : themeData.disabledColor,
onChanged: widget.onChanged,
additionalConstraints: additionalConstraints,
vsync: this,
@ -174,18 +174,19 @@ class _CustomCheckboxState extends State<CustomCheckbox>
}
class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
const _CheckboxRenderObjectWidget(
{Key key,
@required this.value,
@required this.tristate,
@required this.activeColor,
@required this.checkColor,
@required this.inactiveColor,
@required this.onChanged,
@required this.vsync,
@required this.additionalConstraints,
this.useTapTarget = true})
: assert(tristate != null),
const _CheckboxRenderObjectWidget({
Key key,
@required this.value,
@required this.tristate,
@required this.activeColor,
@required this.checkColor,
@required this.inactiveColor,
@required this.onChanged,
@required this.vsync,
@required this.additionalConstraints,
this.useTapTarget = true
}) : assert(tristate != null),
assert(tristate || value != null),
assert(activeColor != null),
assert(inactiveColor != null),
@ -204,15 +205,15 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
@override
_RenderCheckbox createRenderObject(BuildContext context) => _RenderCheckbox(
value: value,
tristate: tristate,
activeColor: activeColor,
checkColor: checkColor,
inactiveColor: inactiveColor,
onChanged: onChanged,
vsync: vsync,
additionalConstraints: additionalConstraints,
);
value: value,
tristate: tristate,
activeColor: activeColor,
checkColor: checkColor,
inactiveColor: inactiveColor,
onChanged: onChanged,
vsync: vsync,
additionalConstraints: additionalConstraints,
);
@override
void updateRenderObject(BuildContext context, _RenderCheckbox renderObject) {
@ -242,23 +243,24 @@ class _RenderCheckbox extends RenderToggleable {
BoxConstraints additionalConstraints,
ValueChanged<bool> onChanged,
@required TickerProvider vsync,
}) : _oldValue = value,
}) : _oldValue = value,
super(
value: value,
tristate: tristate,
activeColor: activeColor,
inactiveColor: inactiveColor,
onChanged: onChanged,
additionalConstraints: additionalConstraints,
vsync: vsync,
);
value: value,
tristate: tristate,
activeColor: activeColor,
inactiveColor: inactiveColor,
onChanged: onChanged,
additionalConstraints: additionalConstraints,
vsync: vsync,
);
bool _oldValue;
Color checkColor;
@override
set value(bool newValue) {
if (newValue == value) return;
if (newValue == value)
return;
_oldValue = value;
super.value = newValue;
}
@ -276,8 +278,7 @@ class _RenderCheckbox extends RenderToggleable {
RRect _outerRectAt(Offset origin, double t) {
final double inset = 1.0 - (t - 0.5).abs() * 2.0;
final double size = _kEdgeSize - inset * _kStrokeWidth;
final Rect rect =
Rect.fromLTWH(origin.dx + inset, origin.dy + inset, size, size);
final Rect rect = Rect.fromLTWH(origin.dx + inset, origin.dy + inset, size, size);
return RRect.fromRectAndRadius(rect, _kEdgeRadius);
}
@ -287,9 +288,7 @@ class _RenderCheckbox extends RenderToggleable {
// As t goes from 0.0 to 0.25, animate from the inactiveColor to activeColor.
return onChanged == null
? inactiveColor
: (t >= 0.25
? activeColor
: Color.lerp(inactiveColor, activeColor, t * 4.0));
: (t >= 0.25 ? activeColor : Color.lerp(inactiveColor, activeColor, t * 4.0));
}
// White stroke used to paint the check and dash.
@ -304,8 +303,7 @@ class _RenderCheckbox extends RenderToggleable {
assert(t >= 0.0 && t <= 0.5);
final double size = outer.width;
// As t goes from 0.0 to 1.0, gradually fill the outer RRect.
final RRect inner =
outer.deflate(math.min(size / 2.0, _kStrokeWidth + size * t));
final RRect inner = outer.deflate(math.min(size / 2.0, _kStrokeWidth + size * t));
canvas.drawDRRect(outer, inner, paint);
}
@ -349,13 +347,11 @@ class _RenderCheckbox extends RenderToggleable {
final Canvas canvas = context.canvas;
paintRadialReaction(canvas, offset, size.center(Offset.zero));
final Offset origin =
offset + (size / 2.0 - const Size.square(_kEdgeSize) / 2.0);
final Offset origin = offset + (size / 2.0 - const Size.square(_kEdgeSize) / 2.0);
final AnimationStatus status = position.status;
final double tNormalized =
status == AnimationStatus.forward || status == AnimationStatus.completed
? position.value
: 1.0 - position.value;
final double tNormalized = status == AnimationStatus.forward || status == AnimationStatus.completed
? position.value
: 1.0 - position.value;
// Four cases: false to null, false to true, null to false, true to false
if (_oldValue == false || value == false) {
@ -370,33 +366,29 @@ class _RenderCheckbox extends RenderToggleable {
_initStrokePaint(paint);
final double tShrink = (t - 0.5) * 2.0;
if (_oldValue == null || value == null) {
if (_oldValue == null || value == null)
_drawDash(canvas, origin, tShrink, paint);
} else {
else
_drawCheck(canvas, origin, tShrink, paint);
}
}
} else {
// Two cases: null to true, true to null
} else { // Two cases: null to true, true to null
final RRect outer = _outerRectAt(origin, 1.0);
final Paint paint = Paint()..color = _colorAt(1.0);
final Paint paint = Paint() ..color = _colorAt(1.0);
canvas.drawRRect(outer, paint);
_initStrokePaint(paint);
if (tNormalized <= 0.5) {
final double tShrink = 1.0 - tNormalized * 2.0;
if (_oldValue == true) {
if (_oldValue == true)
_drawCheck(canvas, origin, tShrink, paint);
} else {
else
_drawDash(canvas, origin, tShrink, paint);
}
} else {
final double tExpand = (tNormalized - 0.5) * 2.0;
if (value == true) {
if (value == true)
_drawCheck(canvas, origin, tExpand, paint);
} else {
else
_drawDash(canvas, origin, tExpand, paint);
}
}
}
}

View file

@ -28,8 +28,11 @@ class LabeledCheckboxWithIcon extends StatelessWidget {
onTap: () {
onChanged(!value);
},
child: Padding(
padding: padding,
child: Row(
// crossAxisAlignment: CrossAxisAlignment.center, //doesn't do anything
@ -37,18 +40,16 @@ class LabeledCheckboxWithIcon extends StatelessWidget {
Container(
padding: EdgeInsets.all(0),
width: iconSize,
child: Icon(
child : Icon(
icon,
// size: iconSize,
// size: iconSize,
color: iconColor,
),
),
Expanded(
child: Text(
label,
style: textStyle,
textAlign: TextAlign.center,
)),
Expanded(child: Text(label, style: textStyle, textAlign: TextAlign.center,)),
CustomCheckbox(
//custom checkbox removes padding so the form looks nice
@ -88,9 +89,11 @@ class LabeledCheckbox extends StatelessWidget {
},
child: Padding(
padding: padding,
child: Row(
children: <Widget>[
Expanded(child: Text(label, style: textStyle)),
CustomCheckbox(
//custom checkbox removes padding so the form looks nice
@ -107,6 +110,7 @@ class LabeledCheckbox extends StatelessWidget {
}
}
/*
//USAGE:
@ -130,4 +134,4 @@ Widget build(BuildContext context) {
),
);
}
*/
*/

View file

@ -1,241 +0,0 @@
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:local_spend/common/apifunctions/find_organisations.dart';
class FindOrganisations {
TextField getSearchBar(TextEditingController controller, String hintText) {
return TextField(
controller: controller,
decoration: InputDecoration(
hintText: hintText,
icon: Icon(Icons.search),
),
);
}
// todo: get all organisations, favourites and all data from one 'organisations' class or similar
// eg items: organisations.getFavourites().orderBy(name),
Future<dynamic> _moreInfoDialog(context, Organisation organisation) {
TextStyle informationTitleStyle = new TextStyle(fontSize: 16);
TextStyle informationStyle =
new TextStyle(fontSize: 16, fontWeight: FontWeight.bold);
return showDialog<Organisation>(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (context, setState) {
return SimpleDialog(
children: <Widget>[
Container(
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
child: Text(
organisation.name,
style: new TextStyle(
fontSize: 21, fontWeight: FontWeight.bold),
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Divider(),
),
Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.symmetric(horizontal: 10),
child: Table(
// defaultColumnWidth: FixedColumnWidth(100),
children: [
TableRow(
children: [
Text("Street:", style: informationTitleStyle),
Text(organisation.streetName,
style: informationStyle),
],
),
TableRow(
children: [
Text("Postcode:", style: informationTitleStyle),
Text(organisation.postcode.toUpperCase(),
style: informationStyle),
],
),
TableRow(
children: [
Text("Town:", style: informationTitleStyle),
Text(organisation.town, style: informationStyle),
],
),
],
),
),
],
);
},
);
},
);
}
Future<Organisation> dialog(context) {
bool _searchEnabled = false;
bool _orgsFetched = false;
TextEditingController searchBarText = new TextEditingController();
var organisations = new Organisations();
var listTitle = "All Organisations";
var organisationsList = List<Organisation>();
Future<int> _submitSearch(String search) async {
_searchEnabled = false;
listTitle = "Results for \'" + search + "\'";
var futureOrgs = await organisations.findOrganisations(search);
organisationsList = futureOrgs;
_searchEnabled = true;
return futureOrgs.length;
}
return showDialog<Organisation>(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (context, setState) {
return SimpleDialog(
children: <Widget>[
Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
padding: EdgeInsets.fromLTRB(20, 0, 0, 0),
width: 150,
height: 50,
child: TextField(
autofocus: true,
controller: searchBarText,
decoration: InputDecoration(
hintText: "Payee Name",
),
onChanged: (value) {
if (value.isNotEmpty) {
_searchEnabled = true;
} else {
_searchEnabled = false;
}
setState(() => {_searchEnabled});
},
onSubmitted: _searchEnabled
? ((_) {
SystemChannels.textInput
.invokeMethod('TextInput.hide');
var result =
_submitSearch(searchBarText.text);
result.then((_) {
setState(() {
_orgsFetched = true;
});
});
})
: null,
),
),
Container(
width: 80,
padding: EdgeInsets.fromLTRB(20, 0, 0, 0),
child: RaisedButton(
onPressed: _searchEnabled
? (() {
SystemChannels.textInput
.invokeMethod('TextInput.hide');
var result =
_submitSearch(searchBarText.text);
result.then((_) {
setState(() {
_orgsFetched = true;
});
});
})
: null,
child: Icon(Icons.search, color: Colors.white),
color: Colors.blue,
),
),
],
),
],
),
Column(
children: _orgsFetched
? [
Container(
padding: EdgeInsets.fromLTRB(20, 20, 20, 0),
child: Text(
listTitle,
style: new TextStyle(
fontSize: 23, fontWeight: FontWeight.bold),
),
),
Container(
padding: EdgeInsets.fromLTRB(10, 10, 10, 0),
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height * 0.67,
child: Material(
shadowColor: Colors.transparent,
color: Colors.transparent,
child: ListView.builder(
itemCount: organisationsList.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
leading: Icon(Icons.person),
title: Text(organisationsList[index].name,
style: new TextStyle(fontSize: 18)),
subtitle: Text(organisationsList[index]
.postcode
.toUpperCase()),
// trailing: Icon(Icons.arrow_forward_ios),
// onTap: _chosenOrg(organisationsList[index]),
onTap: () {
Navigator.of(context)
.pop(organisationsList[index]);
},
onLongPress: () {
// show more details about the organisation in a new dialog
var moreInfo = _moreInfoDialog(
context, organisationsList[index]);
moreInfo.whenComplete(null);
},
),
);
},
),
),
),
Center(
child: Container(
padding: EdgeInsets.fromLTRB(0, 10, 0, 0),
child: Text("Long press a payee for more info",
style:
TextStyle(fontStyle: FontStyle.italic)),
),
),
]
: [Container()],
),
// help button for if org not listed
// cancel and ok buttons
],
// ),
);
},
);
},
);
}
}

View file

@ -6,6 +6,7 @@ class PopupListView {
return showDialog<dynamic>(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return SimpleDialog(
title: Text(title),
@ -15,8 +16,7 @@ class PopupListView {
);
}
List<Widget> getDialogOptions(
context, List<String> options /*, Function onPressed*/) {
List<Widget> getDialogOptions(context, List<String> options /*, Function onPressed*/) {
var dialogOptionsList = new List<SimpleDialogOption>();
for (var i = 0; i < options.length; i++) {
@ -33,4 +33,4 @@ class PopupListView {
return dialogOptionsList;
}
}
}

View file

@ -5,13 +5,13 @@ part 'config.g.dart';
@JsonSerializable(createToJson: false)
class Config {
Config({this.env, this.production, this.apiKey});
factory Config.fromJson(Map<String, dynamic> json) => _$ConfigFromJson(json);
final String env;
final bool production;
final String apiKey;
Config({this.env, this.production, this.apiKey});
factory Config.fromJson(Map<String, dynamic> json) => _$ConfigFromJson(json);
}
class ConfigWrapper extends StatelessWidget {
@ -24,7 +24,7 @@ class ConfigWrapper extends StatelessWidget {
static Config of(BuildContext context) {
final _InheritedConfig inheritedConfig =
context.inheritFromWidgetOfExactType(_InheritedConfig);
context.inheritFromWidgetOfExactType(_InheritedConfig);
return inheritedConfig.config;
}
@ -43,4 +43,4 @@ class _InheritedConfig extends InheritedWidget {
@override
bool updateShouldNotify(_InheritedConfig oldWidget) =>
config != oldWidget.config;
}
}

2
lib/env/dev.dart vendored
View file

@ -3,4 +3,4 @@ import 'package:json_annotation/json_annotation.dart';
part 'dev.g.dart';
@JsonLiteral('dev.json', asConst: true)
Map<String, dynamic> get config => _$configJsonLiteral;
Map<String, dynamic> get config => _$configJsonLiteral;

2
lib/env/dev.g.dart vendored
View file

@ -9,5 +9,5 @@ part of 'dev.dart';
const _$configJsonLiteral = {
'env': 'DEV',
'production': false,
'apiUrl': 'https://dev.localspend.co.uk/api'
'apiUrl': 'https://dev.peartrade.org/api'
};

2
lib/env/dev.json vendored
View file

@ -1,5 +1,5 @@
{
"env": "DEV",
"production": false,
"apiUrl": "https://dev.localspend.co.uk/api"
"apiUrl": "https://dev.peartrade.org/api"
}

2
lib/env/prod.dart vendored
View file

@ -3,4 +3,4 @@ import 'package:json_annotation/json_annotation.dart';
part 'prod.g.dart';
@JsonLiteral('prod.json', asConst: true)
Map<String, dynamic> get config => _$configJsonLiteral;
Map<String, dynamic> get config => _$configJsonLiteral;

2
lib/env/prod.g.dart vendored
View file

@ -9,5 +9,5 @@ part of 'prod.dart';
const _$configJsonLiteral = {
'env': 'PROD',
'production': true,
'apiUrl': 'https://www.localspend.co.uk/api'
'apiUrl': 'https://www.peartrade.org/api'
};

2
lib/env/prod.json vendored
View file

@ -1,5 +1,5 @@
{
"env": "PROD",
"production": true,
"apiUrl": "https://www.localspend.co.uk/api"
"apiUrl": "https://www.peartrade.org/api"
}

View file

@ -1,38 +1,30 @@
import 'package:flutter/material.dart';
import 'package:local_spend/pages/home_page.dart';
import 'package:local_spend/pages/login_page.dart';
import 'package:local_spend/pages/map_page.dart';
import 'package:local_spend/pages/receipt_page_2.dart';
import 'package:local_spend/pages/receipt_page.dart';
import 'package:local_spend/pages/spash_screen.dart';
import 'package:local_spend/pages/more_page.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:local_spend/common/apifunctions/get_graph_data.dart';
import 'package:local_spend/pages/stats_page.dart';
void main() {
runApp(MyApp());
}
void loadGraphs() {}
class GraphWithTitle {
GraphWithTitle({this.graph, this.title});
GraphData graph;
String title;
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: load graphs on app login and send to graph widgets
//var config = ConfigWrapper.of(context);
return new MaterialApp(
debugShowCheckedModeBanner: false,
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [Locale("en")],
supportedLocales: [
Locale("en")
],
title: "Local Spend Tracker",
theme: new ThemeData(
primarySwatch: Colors.blueGrey,
@ -40,9 +32,9 @@ class MyApp extends StatelessWidget {
routes: <String, WidgetBuilder>{
"/HomePage": (BuildContext context) => HomePage(),
"/LoginPage": (BuildContext context) => LoginPage(),
'/MapPage': (BuildContext context) => MapPage(),
"/ReceiptPage": (BuildContext context) => ReceiptPage2(),
"/ReceiptPage": (BuildContext context) => ReceiptPage(),
"/MorePage": (BuildContext context) => MorePage(),
"/StatsPage" : (BuildContext context) => StatsPage(),
},
home: SplashScreen(),
);

View file

@ -1,18 +1,15 @@
class LoginModel {
LoginModel(this.userName, this.token, this.userType);
final String userName;
final String token;
LoginModel(this.userName, this.token);
LoginModel.fromJson(Map<String, dynamic> json)
: userName = json['display_name'],
userType = json['user_type'],
token = json['session_key'];
final String userName;
final String token;
final String userType;
Map<String, dynamic> toJson() => {
'name': userName,
'user_type': userType,
'token': token,
};
}

View file

@ -1,163 +0,0 @@
import 'package:flutter/material.dart';
import 'package:local_spend/common/apifunctions/get_graph_data.dart';
import 'package:charts_flutter/flutter.dart' as charts;
class CustomerGraphs extends StatefulWidget {
CustomerGraphs({Key key}) : super(key: key);
@override
_CustomerGraphsState createState() {
return _CustomerGraphsState();
}
}
class _CustomerGraphsState extends State<CustomerGraphs> {
GraphData totalLastWeekGraph = new GraphData("total_last_week");
GraphData avgSpendLastWeekGraph = new GraphData("avg_spend_last_week");
GraphData totalLastMonthGraph = new GraphData("total_last_month");
GraphData avgSpendLastMonth = new GraphData("avg_spend_last_month");
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
// Initializing graphs:
if (!totalLastWeekGraph.loaded) {
totalLastWeekGraph.setGraphData().then((_) {
setState(() {});
});
}
if (!avgSpendLastWeekGraph.loaded) {
avgSpendLastWeekGraph.setGraphData().then((_) {
setState(() {});
});
}
if (!totalLastMonthGraph.loaded) {
totalLastMonthGraph.setGraphData().then((_) {
setState(() {});
});
}
if (!avgSpendLastMonth.loaded) {
avgSpendLastMonth.setGraphData().then((_) {
setState(() {});
});
}
return ListView(
children: <Widget>[
Container(
padding: EdgeInsets.fromLTRB(0.0, 17, 0.0, 0.0),
child: Text(
"Last Week's Total Spend",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
Tooltip(
message: "Graph of total spend last week",
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10),
height: 200,
child: totalLastWeekGraph.graph != null
? new charts.TimeSeriesChart(totalLastWeekGraph.graph)
: Center(
child: CircularProgressIndicator(
valueColor:
new AlwaysStoppedAnimation<Color>(Colors.orange),
)), //List<Series<dynamic, DateTime>>es<dynamic, DateTime>>
),
),
Container(
padding: EdgeInsets.fromLTRB(0.0, 17, 0.0, 0.0),
child: Text(
"Last Week's Average Spend",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
Tooltip(
message: "Graph of average spend last week",
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10),
height: 200,
child: avgSpendLastWeekGraph.graph != null
? new charts.TimeSeriesChart(avgSpendLastWeekGraph.graph)
: Center(
child: CircularProgressIndicator(
valueColor: new AlwaysStoppedAnimation<Color>(Colors.blue),
)), //List<Series<dynamic, DateTime>>es<dynamic, DateTime>>
),
),
Container(
padding: EdgeInsets.fromLTRB(0.0, 17, 0.0, 0.0),
child: Text(
"Last Month's Total Spend",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
Tooltip(
message: "Graph of total spend last month",
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10),
height: 200,
child: totalLastMonthGraph.graph != null
? new charts.TimeSeriesChart(totalLastMonthGraph.graph)
: Center(
child: CircularProgressIndicator(
valueColor: new AlwaysStoppedAnimation<Color>(Colors.green),
)), //List<Series<dynamic, DateTime>>es<dynamic, DateTime>>
),
),
Container(
padding: EdgeInsets.fromLTRB(0.0, 17, 0.0, 0.0),
child: Text(
"Last Month's Average Spend",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
Tooltip(
message: "Graph of average spend last month",
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10),
height: 200,
child: avgSpendLastMonth.graph != null
? new charts.TimeSeriesChart(avgSpendLastMonth.graph)
: Center(
child: CircularProgressIndicator(
valueColor: new AlwaysStoppedAnimation<Color>(Colors.red),
)), //List<Series<dynamic, DateTime>>es<dynamic, DateTime>>
),
),
],
);
}
}

View file

@ -1,11 +1,10 @@
import 'package:flutter/material.dart';
import 'package:local_spend/pages/receipt_page_2.dart';
import 'package:local_spend/pages/receipt_page.dart';
import 'package:local_spend/pages/more_page.dart';
import 'package:local_spend/pages/stats_page.dart';
import 'package:local_spend/pages/map_page.dart';
class HomePage extends StatelessWidget {
static String _title = 'SpendTracker';
static String _title = 'Text here';
@override
Widget build(BuildContext context) {
@ -25,13 +24,11 @@ class HomePageWidget extends StatefulWidget {
class _HomePageState extends State<HomePageWidget> {
int _selectedIndex = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
static List<Widget> _widgetOptions = <Widget>[
ReceiptPage2(),
ReceiptPage(),
StatsPage(),
MapPage(),
MorePage()
];
@ -57,20 +54,15 @@ class _HomePageState extends State<HomePageWidget> {
icon: Icon(Icons.show_chart),
title: Text('Statistics'),
),
BottomNavigationBarItem(
icon: Icon(Icons.map),
title: Text('Locations'),
),
BottomNavigationBarItem(
icon: Icon(Icons.more_horiz),
title: Text('More'),
),
],
currentIndex: _selectedIndex,
unselectedItemColor: Colors.grey[400],
selectedItemColor: Colors.blue[400],
onTap: _onItemTapped,
),
);
}
}
}

View file

@ -7,9 +7,8 @@ import 'package:local_spend/common/platform/platform_scaffold.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:local_spend/common/widgets/labeled_checkbox.dart';
import 'package:local_spend/common/widgets/animatedGradientButton.dart';
const url = "https://flutter.io/";
const URL = "https://flutter.io/";
class LoginPage extends StatefulWidget {
@override
@ -19,12 +18,12 @@ class LoginPage extends StatefulWidget {
}
class LoginPageState extends State<LoginPage> {
bool _isLoggingIn = false;
final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
bool _saveLoginDetails = true;
final TextEditingController _emailController = TextEditingController(/*text: 'test@example.com'*/); // remove
final TextEditingController _passwordController = TextEditingController(/*text: 'abc123'*/); // remove
bool _saveLoginDetails = true; // I am extremely sorry for the placement of this variable
// it will be fixed soon I promise
FocusNode focusNode; // added so focus can move automatically
FocusNode focusNode; // added so focus can move automatically
Future launchURL(String url) async {
if (await canLaunch(url)) {
@ -33,7 +32,7 @@ class LoginPageState extends State<LoginPage> {
showDialogSingleButton(
context,
"Unable to reach your website.",
"Currently unable to reach the website $url. Please try again at a later time.",
"Currently unable to reach the website $URL. Please try again at a later time.",
"OK");
}
}
@ -54,7 +53,7 @@ class LoginPageState extends State<LoginPage> {
super.dispose();
}
void _fillLoginDetails() async {
_fillLoginDetails() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
var username = await preferences.get('username');
@ -64,27 +63,27 @@ class LoginPageState extends State<LoginPage> {
_passwordController.text = await password;
}
void _saveCurrentRoute(String lastRoute) async {
_saveCurrentRoute(String lastRoute) async {
SharedPreferences preferences = await SharedPreferences.getInstance();
await preferences.setString('LastPageRoute', lastRoute);
}
void login(String username, String password) async {
_isLoggingIn = true;
await SystemChannels.textInput.invokeMethod('TextInput.hide');
login(String username, String password) async {
SystemChannels.textInput.invokeMethod('TextInput.hide');
SharedPreferences preferences = await SharedPreferences.getInstance();
if (_saveLoginDetails) {
await preferences.setString('username', username);
await preferences.setString('password', password);
print("details saved");
} else {
await preferences.setString('username', "");
await preferences.setString('password', "");
print("details cleared");
}
await requestLoginAPI(context, username, password).then((value) {
_isLoggingIn = false;
});
requestLoginAPI(context, username,
password);
}
@override
@ -93,132 +92,157 @@ class LoginPageState extends State<LoginPage> {
onWillPop: () {
if (Navigator.canPop(context)) {
Navigator.of(context).pushNamedAndRemoveUntil(
'/HomePage', (Route<dynamic> route) => false);
'/ReceiptPage', (Route<dynamic> route) => false);
} else {
Navigator.of(context).pushReplacementNamed('/HomePage');
Navigator.of(context).pushReplacementNamed('/ReceiptPage');
}
return null;
},
child: PlatformScaffold(
body: Stack(
children: [
AnimatedBackground([Colors.lightBlue[50], Colors.lightBlue[50]],
Colors.white, Alignment.topRight, Alignment.bottomLeft, 3),
Container(
margin: EdgeInsets.fromLTRB(60, 30, 60, 0),
child: Column(
children: <Widget>[
Expanded(
child: AnimatedContainer(
duration: Duration(seconds: 2),
margin: EdgeInsets.fromLTRB(15, 0, 15, 0),
decoration: BoxDecoration(
image: DecorationImage(
image:
AssetImage('assets/images/launch_image.png')),
// drawer: BasicDrawer(),
// body: Container(
// decoration: BoxDecoration(color: Colors.white),
// margin: const EdgeInsets.all(20),
// child: Padding(
// padding: EdgeInsets.fromLTRB(30.0, 170.0, 30.0, 0.0),
// child: ListView(
// children: <Widget>[
body: Container(
decoration: new BoxDecoration(
gradient: new LinearGradient(
colors: [Colors.white, Colors.blue[50]],
stops: [0,1],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
), child: Container(
margin: EdgeInsets.fromLTRB(60,30,60,0),
child: Column(
children: <Widget>[
Expanded(
child: Container(
margin: EdgeInsets.fromLTRB(15,0,15,0),
// alignment: FractionalOffset(0.5, 0.3), // not sure what this does ngl :/
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/launch_image.png')
),
),
),
Padding(
padding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 0.0),
child: TextField(
autocorrect: false,
textAlign: TextAlign.center,
controller: _emailController,
decoration: InputDecoration(
hintText: "EMAIL",
hintStyle: TextStyle(fontSize: 15),
),
style: TextStyle(
fontSize: 18.0,
color: Colors.grey[800],
fontWeight: FontWeight.bold,
),
onSubmitted: (_) {
FocusScope.of(context).requestFocus(focusNode);
},
),
Padding(
padding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 0.0),
child: TextField(
autocorrect: false,
textAlign: TextAlign.center,
controller: _emailController,
decoration: InputDecoration(
hintText: "EMAIL",
hintStyle: TextStyle(fontSize: 15),
),
),
Padding(
padding: EdgeInsets.fromLTRB(0.0, 15.0, 0.0, 0.0),
child: TextField(
autocorrect: false,
textAlign: TextAlign.center,
controller: _passwordController,
focusNode: focusNode,
decoration: InputDecoration(
hintText: 'PASSWORD',
hintStyle: TextStyle(fontSize: 15),
),
obscureText: true,
style: TextStyle(
fontSize: 18.0,
color: Colors.grey[800],
fontWeight: FontWeight.bold,
),
onSubmitted: (_) {
login(_emailController.text, _passwordController.text);
},
style: TextStyle(
fontSize: 18.0,
color: Colors.grey[800],
fontWeight: FontWeight.bold,
),
onSubmitted: (_) {
FocusScope.of(context).requestFocus(focusNode);
},
),
Container(
margin: EdgeInsets.fromLTRB(0.0, 40.0, 0.0, 30.0),
width: 100,
height: 50,
child: Opacity(
opacity: _isLoggingIn ? 0.5 : 1,
child: ClipRRect(
borderRadius: BorderRadius.circular(2),
child: Stack(
children: [
AnimatedBackground(
[Colors.blue, Colors.lightBlue[300]],
Colors.lightBlue,
Alignment.bottomRight,
Alignment.topLeft,
3),
Material(
type: MaterialType.transparency,
child: InkWell(
onTap: _isLoggingIn
? null
: () => login(_emailController.text,
_passwordController.text),
child: new Center(
child: new Text(
'GO',
style: new TextStyle(
fontSize: 18, color: Colors.white),
),
),
),
),
],
),
Padding(
padding: EdgeInsets.fromLTRB(0.0, 15.0, 0.0, 0.0),
child: TextField(
autocorrect: false,
textAlign: TextAlign.center,
controller: _passwordController,
focusNode: focusNode,
decoration: InputDecoration(
hintText: 'PASSWORD',
hintStyle: TextStyle(fontSize: 15),
),
obscureText: true,
style: TextStyle(
fontSize: 18.0,
color: Colors.grey[800],
fontWeight: FontWeight.bold,
),
onSubmitted: (_) {
login( _emailController.text,
_passwordController.text);
},
),
),
Padding(
padding: EdgeInsets.fromLTRB(0.0, 40.0, 0.0, 30.0),
child : Material(
child: new Container(
child : InkWell(
onTap: () => login( _emailController.text, _passwordController.text),
child: new Container(
width: 100,
height: 50,
child: new Center(
child: new Text(
'GO', style: new TextStyle(fontSize: 18, color: Colors.white),),
),
),
splashColor: Colors.lightBlueAccent,
),
decoration: new BoxDecoration(
border: new Border.all(color : Colors.transparent, width: 2),
borderRadius: BorderRadius.all(Radius.circular(2)),
gradient: new LinearGradient(
colors: [
Colors.blue[300],
Colors.blue[500],
],
stops: [0,1],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
Padding(
padding: EdgeInsets.fromLTRB(0, 10, 0, 50),
child: LabeledCheckbox(
label: "SAVE LOGIN",
textStyle: TextStyle(
fontSize: 18,
color: Colors.black54,
fontWeight: FontWeight.bold),
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
value: _saveLoginDetails,
onChanged: (bool newValue) {
setState(() {
_saveLoginDetails = newValue;
});
},
),
),
],
),
),
Padding(
padding: EdgeInsets.fromLTRB(0, 10, 0, 50),
child: LabeledCheckbox(
label : "SAVE LOGIN",
textStyle: TextStyle(fontSize: 18, color: Colors.black54, fontWeight: FontWeight.bold),
padding: const EdgeInsets.fromLTRB(0,0,0,0),
value : _saveLoginDetails,
onChanged: (bool newValue) {
setState(() {
_saveLoginDetails = newValue;
});
},
),
/*child: LabeledCheckboxWithIcon(
label : "SAVE LOGIN",
textStyle: TextStyle(fontSize: 18, color: Colors.black54, fontWeight: FontWeight.bold),
icon: Icons.account_box, // need to remove icon padding!!
iconSize: 18,
iconColor: Colors.black54,
padding: const EdgeInsets.fromLTRB(0,0,0,0),
value : _saveLoginDetails,
onChanged: (bool newValue) {
setState(() {
_saveLoginDetails = newValue;
});
},
),*/
),
],
),
],
),
),
),
);

View file

@ -1,62 +0,0 @@
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:google_maps_flutter/google_maps_flutter.dart' as gmaps;
import 'package:local_spend/common/apifunctions/get_map_data.dart' as mapData;
import 'package:local_spend/common/platform/platform_scaffold.dart';
class MapPage extends StatefulWidget {
MapPage({Key key}) : super(key: key);
@override
_MapPageState createState() {
return _MapPageState();
}
}
class _MapPageState extends State<MapPage> {
final Map<String, gmaps.Marker> _markers = {};
Future<void> _onMapCreated(gmaps.GoogleMapController controller) async {
final region = await controller.getVisibleRegion();
final locations = await mapData.getLocations(region.northeast, region.southwest);
setState(() {
_markers.clear();
for (final location in locations.locations) {
final marker = gmaps.Marker(
markerId: gmaps.MarkerId(location.organisation.name),
position: gmaps.LatLng(location.lat, location.lng),
infoWindow: gmaps.InfoWindow(
title: location.organisation.name,
snippet: location.organisation.postcode,
),
);
_markers[location.organisation.name] = marker;
}
});
}
@override
Widget build(BuildContext context) {
return PlatformScaffold(
appBar: AppBar(
backgroundColor: Colors.blue[400],
title: Text(
"Map",
style: TextStyle(
fontSize: 20,
color: Colors.white,
),
),
centerTitle: true,
iconTheme: IconThemeData(color: Colors.black),
),
body: gmaps.GoogleMap(
myLocationButtonEnabled: false,
mapType: gmaps.MapType.hybrid,
onMapCreated: _onMapCreated,
initialCameraPosition: gmaps.CameraPosition(
target: gmaps.LatLng(54.0411301, -2.8104042),
zoom: 15,
),
),
);
}
}

View file

@ -5,8 +5,9 @@ import 'package:local_spend/common/functions/logout.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:local_spend/common/functions/customAbout.dart' as custom;
import 'package:local_spend/common/functions/showDialogTwoButtons.dart';
import 'package:local_spend/common/widgets/awesome_drawer.dart';
const url = "https://flutter.io/";
const URL = "https://flutter.io/";
const demonstration = false;
class MorePage extends StatefulWidget {
@ -17,7 +18,7 @@ class MorePage extends StatefulWidget {
}
class MorePageState extends State<MorePage> {
FocusNode focusNode; // added so focus can move automatically
FocusNode focusNode; // added so focus can move automatically
DateTime date;
@ -36,25 +37,20 @@ class MorePageState extends State<MorePage> {
focusNode.dispose(); //disposes focus node when form disposed
}
void _saveCurrentRoute(String lastRoute) async {
_saveCurrentRoute(String lastRoute) async {
SharedPreferences preferences = await SharedPreferences.getInstance();
await preferences.setString('LastPageRoute', lastRoute);
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
if (Navigator.canPop(context)) {
Navigator.of(context).pushNamedAndRemoveUntil(
'/LoginPage', (Route<dynamic> route) => false);
} else {
Navigator.of(context).pushReplacementNamed('/LoginPage');
}
return null;
},
child: PlatformScaffold(
var drawer = new AwesomeDrawer();
return new PlatformScaffold(
drawer: drawer.getDrawer(context),
appBar: AppBar(
backgroundColor: Colors.blue[400],
title: Text(
"More",
@ -67,12 +63,15 @@ class MorePageState extends State<MorePage> {
centerTitle: true,
iconTheme: IconThemeData(color: Colors.black),
),
body: Container(
padding: EdgeInsets.fromLTRB(30.0, 0.0, 30.0, 0.0),
child: ListView(
children: <Widget>[
Container(
padding: EdgeInsets.fromLTRB(30.0, 25, 30.0, 0.0),
child: Text(
padding: EdgeInsets.fromLTRB(0.0,25,0.0,0.0),
child : Text(
"Local Spend Tracker",
textAlign: TextAlign.center,
style: TextStyle(
@ -82,93 +81,76 @@ class MorePageState extends State<MorePage> {
),
),
),
//
// Padding(
// padding: EdgeInsets.fromLTRB(10, 15, 0, 0),
// child: Text("helloooo"),
// ),
Padding(
padding: EdgeInsets.fromLTRB(30.0, 25.0, 30.0, 0.0),
padding: EdgeInsets.fromLTRB(0.0, 25.0, 0.0, 0.0),
child: Container(
height: 65.0,
child: RaisedButton(
onPressed: () {
custom.showAboutDialog(
context: context,
applicationIcon: new Icon(Icons.receipt),
applicationName: "Local Spend Tracker",
children: <Widget>[
Text("Pear Trading is a commerce company designed to register and monitor money circulating in the local economy.\n"),
Container(
margin: EdgeInsets.symmetric(horizontal: 10),
height: 35,
child: RaisedButton(
onPressed: () => launch('http://www.peartrade.org'),
child: Text("Pear Trading",
children: <Widget> [
Text("Pear Trading is a commerce company designed to register and monitor money circulating in the local economy."),
Text("\nContact at test@example.com or +44(0)1524 64544"),
Padding(
padding: EdgeInsets.fromLTRB(0,20,0,0),
child: InkWell(
child: Text
('Developed by Shadowcat Systems',
style: TextStyle(
color: Colors.white, fontSize: 18.0)),
color: Colors.green,
),
),
Container(
margin: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 0.0),
height: 40.0,
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(3),
onTap: () => launch('https://shadow.cat'),
child: Column(
children: [
Align(
child: Text("Developed by"),
alignment: Alignment.centerLeft),
Container(
margin: EdgeInsets.all(0),
child : Text(
"Shadowcat Systems",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold
),
),
),
],
color: Colors.blue,
),
),
),
onTap: () => launch('https://shadow.cat/')
),
),
],
);
},
child: Text("ABOUT",
style: TextStyle(color: Colors.white, fontSize: 22.0)),
style:
TextStyle(color: Colors.white, fontSize: 22.0)),
color: Colors.blue,
),
),
),
Padding(
padding: EdgeInsets.fromLTRB(30.0, 20.0, 30.0, 0.0),
padding: EdgeInsets.fromLTRB(0.0, 20.0, 0.0, 0.0),
child: Container(
height: 65.0,
child: RaisedButton(
onPressed: () {
showDialogTwoButtons(
context,
"Logout",
"Are you sure you want to log out?",
"Cancel",
"Logout",
logout);
context,
"Logout",
"Are you sure you want to log out?",
"Cancel",
"Logout",
logout
);
},
child: Text("LOGOUT",
style: TextStyle(color: Colors.white, fontSize: 22.0)),
style:
TextStyle(color: Colors.white, fontSize: 22.0)),
color: Colors.red,
),
),
),
// Padding(
// padding: EdgeInsets.fromLTRB(30.0, 20.0, 30.0, 0.0),
// padding: EdgeInsets.fromLTRB(0.0, 20.0, 0.0, 0.0),
// child: Container(
// height: 65.0,
// child: RaisedButton(
@ -182,10 +164,10 @@ class MorePageState extends State<MorePage> {
// ),
// ),
// ),
],
),
),
),
);
);
}
}

View file

@ -1,57 +0,0 @@
import 'package:flutter/material.dart';
import 'package:local_spend/common/platform/platform_scaffold.dart';
import 'package:shared_preferences/shared_preferences.dart';
const url = "https://flutter.io/";
const demonstration = false;
class NewStatsPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return new NewStatsPageState();
}
}
class NewStatsPageState extends State<NewStatsPage> {
/// Graph types:
/// - total_last_week
/// - avg_spend_last_week
/// - total_last_month
/// - avg_spend_last_month
@override
void initState() {
super.initState();
_saveCurrentRoute("/StatsPageState");
}
@override
void dispose() {
super.dispose();
}
void _saveCurrentRoute(String lastRoute) async {
SharedPreferences preferences = await SharedPreferences.getInstance();
await preferences.setString('LastPageRoute', lastRoute);
}
@override
Widget build(BuildContext context) {
return PlatformScaffold(
appBar: AppBar(
backgroundColor: Colors.blue[400],
title: Text(
"Statistics",
style: TextStyle(
fontSize: 20,
color: Colors.white,
),
),
// leading: BackButton(),
centerTitle: true,
iconTheme: IconThemeData(color: Colors.black),
),
body: Container(),
);
}
}

View file

@ -1,178 +0,0 @@
import 'package:flutter/material.dart';
import 'package:local_spend/common/apifunctions/get_graph_data.dart';
import 'package:charts_flutter/flutter.dart' as charts;
class OrgGraphs extends StatefulWidget {
OrgGraphs({Key key}) : super(key: key);
@override
_OrgGraphsState createState() {
return _OrgGraphsState();
}
}
class _OrgGraphsState extends State<OrgGraphs> {
/// Organisations' graphs types: to fetch, POST to https://dev.localspend.co.uk/api/stats/[graph_type] as {"session_key":"[boop beep]"}
/// - organisations_all : organisation
/// - pies : organisation/pies
/// - snippets : organisation/snippets
/// - graphs : organisation/graphs
/// - {"graph":"customers_last_7_days","session_key":"[bleep]"}
/// - {"graph":"customers_last_30_days","session_key":"[blah]"}
/// - {"graph":"sales_last_7_days","session_key":"[bloop]"}
/// - {"graph":"sales_last_7_days","session_key":"[reee]"}
/// - {"graph":"purchases_last_7_days","session_key":"[yee]"}
/// - {"graph":"purchases_last_30_days","session_key":"[yah]"}
/// - {"graph":"purchases_all;","session_key":"[kappa]"} // I don't think this one works
///
/// HTTP POST request sample:
/// {"graph":"total_last_week","session_key":"blahblahblah"}
// OrganisationGraph customersLastWeek = new OrganisationGraph("graphs", graphsType: "customers_last_7_days");
OrganisationGraph customersLastMonth =
new OrganisationGraph("graphs", graphsType: "customers_last_30_days");
OrganisationGraph salesLastMonth =
new OrganisationGraph("graphs", graphsType: "sales_last_30_days");
OrganisationGraph purchasesLastMonth = new OrganisationGraph("graphs",
graphsType: "purchases_last_30_days"); //purchases_last_30_days
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
// if (!customersLastWeek.loaded) {
// customersLastWeek.getGraphData().then((_) {
// setState(() {});
// });
// }
if (!customersLastMonth.loaded) {
customersLastMonth.getGraphData().then((_) {
setState(() {});
});
}
if (!salesLastMonth.loaded) {
salesLastMonth.getGraphData().then((_) {
setState(() {});
});
}
if (!purchasesLastMonth.loaded) {
purchasesLastMonth.getGraphData().then((_) {
setState(() {});
});
}
return ListView(
children: <Widget>[
// Container(
// padding: EdgeInsets.fromLTRB(0.0, 17, 0.0, 0.0),
// child: Text(
// "Last Week's Customers",
// textAlign: TextAlign.center,
// style: TextStyle(
// fontSize: 22.0,
// color: Colors.black,
// fontWeight: FontWeight.bold,
// ),
// ),
// ),
// Tooltip(
// message: "Graph of customers last week",
// child: Container(
// padding: EdgeInsets.symmetric(horizontal: 10),
// height: 200,
// child: customersLastWeek.graph != null
// ? new charts.TimeSeriesChart(customersLastWeek.graph)
// : Center(
// child: Text(
// "Loading graph...")), //List<Series<dynamic, DateTime>>
// ),
// ),
Container(
padding: EdgeInsets.fromLTRB(0.0, 17, 0.0, 0.0),
child: Text(
"This Month's Customers",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
Tooltip(
message: "Customers this month", // this needs to be better
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10),
height: 200,
child: customersLastMonth.graph != null
? new charts.TimeSeriesChart(customersLastMonth.graph)
: Center(
child:
CircularProgressIndicator()), //List<Series<dynamic, DateTime>>
),
),
Container(
padding: EdgeInsets.fromLTRB(0.0, 17, 0.0, 0.0),
child: Text(
"This Month's Revenue",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
Tooltip(
message: "Revenue from sales this month",
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10),
height: 200,
child: salesLastMonth.graph != null
? new charts.TimeSeriesChart(salesLastMonth.graph)
: Center(
child: CircularProgressIndicator(
valueColor: new AlwaysStoppedAnimation<Color>(Colors.green),
)), //List<Series<dynamic, DateTime>>
),
),
Container(
padding: EdgeInsets.fromLTRB(0.0, 17, 0.0, 0.0),
child: Text(
"This Month's Sales",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
Tooltip(
message: "Number of sales this month",
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10),
height: 200,
child: purchasesLastMonth.graph != null
? new charts.TimeSeriesChart(purchasesLastMonth.graph)
: Center(
child: CircularProgressIndicator(
valueColor: new AlwaysStoppedAnimation<Color>(Colors.red),
)), //List<Series<dynamic, DateTime>>
),
),
],
);
}
}

617
lib/pages/receipt_page.dart Normal file
View file

@ -0,0 +1,617 @@
import 'dart:async';
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:local_spend/common/apifunctions/submit_receipt_api.dart';
import 'package:local_spend/common/functions/show_dialog_single_button.dart';
import 'package:local_spend/common/platform/platform_scaffold.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:intl/intl.dart';
import 'package:datetime_picker_formfield/datetime_picker_formfield.dart';
import 'package:local_spend/common/apifunctions/find_organisations.dart';
import 'package:local_spend/common/widgets/popupListView.dart';
import 'package:local_spend/common/apifunctions/categories.dart';
import 'package:local_spend/common/widgets/awesome_drawer.dart';
const URL = "https://flutter.io/";
const demonstration = false;
class ReceiptPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return new ReceiptPageState();
}
}
class ReceiptPageState extends State<ReceiptPage> {
final TextEditingController _timeController = TextEditingController();
final TextEditingController _amountController = TextEditingController();
final TextEditingController _essentialController = TextEditingController();
final TextEditingController _recurringController = TextEditingController();
final TextEditingController _categoryController = TextEditingController();
final TextEditingController _orgController = TextEditingController();
final OrganizationController _organizationController = OrganizationController();
List<String> _categoryDropDownItems = List<String>();
FocusNode focusNode;
DateTime date;
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() {
getCategoriesStrings().then((value) {
_categoryDropDownItems = value;
});
super.initState();
_saveCurrentRoute("/ReceiptPageState");
focusNode = FocusNode();
_recurringController.text = "None";
_categoryController.text = "";
// getCategoriesStrings().then((value) {
// _categoryDropDownItems = value;
// });
}
@override
void dispose() {
super.dispose();
focusNode.dispose(); //disposes focus node when form disposed
}
_saveCurrentRoute(String lastRoute) async {
SharedPreferences preferences = await SharedPreferences.getInstance();
await preferences.setString('LastPageRoute', lastRoute);
}
// this file is getting really messy sorry everyone
void submitReceipt(String amount, String time, Organisation organisation, String recurring, String category, String essential) async {
SystemChannels.textInput.invokeMethod('TextInput.hide');
if (organisation == null) {
_findOrganizationsDialog(context);
/* await showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: new Text("Missing organisation"),
content: new Text(
"Please press 'Find' to select your desired organization."),
actions: <Widget>[
new FlatButton(
child: new Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);*/
}
else {
if (amount == "" || time == "") {
await showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: new Text("Missing required data"),
content: new Text(
"We couldn't process your request because one or more required fields are missing."),
actions: <Widget>[
new FlatButton(
child: new Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
else {
if (demonstration) {
await showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: new Text("Success"),
content: new Text("Receipt successfully submitted."),
actions: <Widget>[
// usually buttons at the bottom of the dialog
new FlatButton(
child: new Text("OK"),
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).pushReplacementNamed("/ReceiptPage");
},
),
],
);
},
).then((_) {});
}
else {
Receipt receipt = new Receipt();
// setting up 'receipt'
receipt.amount = amount;
receipt.time = formatDate(time);
// debugPrint(organisation.name + ", " + organisation.streetName + ", " + organisation.town + ", " + organisation.postcode);
receipt.organisationName = organisation.name;
receipt.street = organisation.streetName;
receipt.town = organisation.town;
receipt.postcode = organisation.postcode;
receipt.recurring = recurring;
receipt.category = category;
receipt.essential = essential;
submitReceiptAPI(context, receipt);
Navigator.of(context).pushReplacementNamed("/ReceiptPage");
}
}
}
}
String convertBoolToString(bool toConvert) {
if (toConvert)
{
return "true";
}
return "false";
}
Future<List<String>> getCategoriesStrings() async {
var categories = getCategories(); //future<list<cat>>
var categoriesStrings = List<String>();
categories.then((val) {
val.forEach((thisCategory) {
// print(thisCategory.name);
categoriesStrings.add(thisCategory.name);
});
// print(categoriesStrings[10]); // prints 'Banana'
// print(categoriesStrings.toString());
_categoryDropDownItems = categoriesStrings;
return categoriesStrings;
});
}
List<String> getRecurringOptions() {
var options = new List<String>(7);
options[0] = "None";
options[1] = "Daily";
options[2] = "Weekly";
options[3] = "Fortnightly";
options[4] = "Monthly";
options[5] = "Quarterly";
options[6] = "Yearly";
return options;
}
String formatDate(String date) {
// return "";
// should be in format:
// yyyy-MM-ddThh:mm:00.000+01:00
// eg 2019-07-05T10:24:00.000+01.00 (real life example, works)
// current format = "dd/MM/yyyy 'at' hh:mm"
// 0123456789ABCDEFGHIJK
var components = new List(5);
components[0] = (date.substring(0,2)); // dd
components[1] = (date.substring(3,5)); // MM
components[2] = (date.substring(6,10)); // yyyy
components[3] = (date.substring(14,16)); // hh
components[4] = (date.substring(17,19)); // mm
//print(components);
return (components[2] + "-" + components[1] + "-" + components[0]
+ "T" + components[3] + ":" + components[4] + ":00.000+01:00");
// Yes, there is probably a function to convert dates, but I didn't
// know that before writing this and it's done now so I'm keeping it.
}
Organisation listOrganisations(List<Organisation> organisations, context) {
if (organisations.length == 0) {
showDialogSingleButton(
context,
"No matching organizations",
"We were unable to find any organizations matching this text.",
"OK"
);
return null;
}
var optionsList = new List<String>();
for (var i = 0; i < organisations.length; i++) {
optionsList.add(organisations[i].name);
}
// var popupListView = new PopupListView(context, optionsList, "Choose Organization");
var popupListView = new PopupListView();
var dialog = popupListView.dialog(context, optionsList, "Choose Organization");
// dialog.then((value) => debugPrint(value));
dialog.then((value) {
_orgController.text = value;
_organizationController.organisation = organisations.where((thisOrg) => thisOrg.name == value).elementAt(0);
// this may not work when two organisations have the same name,
// then again the popupListView can't display two of the same names properly either
});
//can't return value as it is <future> and thus would block
}
_findOrganizationsDialog(context) {
if (_orgController.text != "") {
var organisations = findOrganisations(
_orgController.text); // returns Future<List<Organisation>>
var choice = organisations.then((data) =>
listOrganisations(data, context));
choice.then((value) => _orgController.text = value.name);
choice.then((value) => _organizationController.organisation = value);
} else {
// no data entered
showDialogSingleButton(
context,
"No data",
"We were unable to service your request because no data was entered.",
"OK"
);
}
}
@override
Widget build(BuildContext context) {
var drawer = new AwesomeDrawer();
return PlatformScaffold(
drawer: drawer.getDrawer(context),
appBar: AppBar(
automaticallyImplyLeading: !Platform.isIOS, // done to remove UI glitch, now works on both platforms
// leading: Drawer(),
backgroundColor: Colors.blue[400],
title: Text(
"Submit Receipt",
style: TextStyle(
fontSize: 20,
color: Colors.white,
),
),
centerTitle: true,
// iconTheme: IconThemeData(color: Colors.black),
),
body: Container(
padding: EdgeInsets.fromLTRB(30.0, 0.0, 30.0, 0.0),
child: ListView(
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(0.0,25,0.0,0.0),
child : Text(
"Time of Transaction",
style: TextStyle(
fontSize: 18.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
DateTimePickerFormField(
inputType: InputType.both,
format: DateFormat("dd/MM/yyyy 'at' hh:mm"),
editable: true,
controller: _timeController,
decoration: InputDecoration(
labelText: 'Date/Time of Transaction', hasFloatingPlaceholder: false),
onChanged: (dt) => setState(() => date = dt),
),
Padding(
padding: EdgeInsets.fromLTRB(0.0,25,0.0,0.0),
child: Text(
"Amount",
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: _amountController,
decoration: InputDecoration(
hintText: 'Value in £',
),
// obscureText: true,
autocorrect: false,
keyboardType: TextInputType.number,
style: TextStyle(
fontSize: 18.0,
color: Colors.grey[800],
fontWeight: FontWeight.bold,
),
onSubmitted: (_) {
FocusScope.of(context).requestFocus(focusNode);
// submitReceipt(_amountController.text, _timeController.text);
},
),
),
Padding(
padding: EdgeInsets.fromLTRB(0.0,25,0.0,0.0),
child : Container (
height: 22, // this should be the same height as text
child : ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
Container(
child: Text(
"Organization Name",
style: TextStyle(
fontSize: 18.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
Container(
child : Padding(
padding: EdgeInsets.fromLTRB(5,0,0,4), // sorry about hardcoded constraints
child: FlatButton(
onPressed: () {
_findOrganizationsDialog(context);
},
child: Text("Find",
style: TextStyle(color: Colors.blue, fontSize: 18.0)
),
),
),
)
],
),
),
),
Padding(
padding: EdgeInsets.fromLTRB(0.0, 5.0, 0.0, 0.0),
child: TextField(
controller: _orgController,
focusNode: focusNode,
decoration: InputDecoration(
hintText: 'Eg. Pear Trading',
),
// obscureText: true,
autocorrect: true,
style: TextStyle(
fontSize: 18.0,
color: Colors.grey[800],
fontWeight: FontWeight.bold,
),
onSubmitted: (_) {
submitReceipt(_amountController.text,
_timeController.text, _organizationController.organisation, _recurringController.text, _categoryController.text, _essentialController.text);
},
),
),
Padding(
padding: EdgeInsets.fromLTRB(0.0,25,0.0,0.0),
child : Container (
height: 18,
child : ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
Container(
child: Text(
"Essential",
style: TextStyle(
fontSize: 18.0,
color: Colors.black,
),
),
),
Container(
child : Padding(
padding: EdgeInsets.fromLTRB(20.0, 0.0, 0, 0),
child: Checkbox(value:
_essentialController.text.toLowerCase() == 'true',
onChanged: (bool newValue) {
setState(() {
_essentialController.text =
convertBoolToString(newValue);
});
}),
),
),
],
),
),
),
Padding(
padding: EdgeInsets.fromLTRB(0.0,18,0.0,0.0),
child : Container (
height: 35,
// width: 400,
child : ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
Container(
padding: const EdgeInsets.fromLTRB(0, 7, 0, 8),
child: Text(
"Recurring",
style: TextStyle(
fontSize: 18.0,
color: Colors.black,
),
),
),
Container(
padding: const EdgeInsets.fromLTRB(29, 0, 0, 0),
child: DropdownButton<String>(
value: _recurringController.text,
onChanged: (String newValue) {
setState(() {
_recurringController.text = newValue;
});
},
items: getRecurringOptions().map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
})
.toList(),
)
),
],
),
),
),
Padding(
padding: EdgeInsets.fromLTRB(0.0,18,0.0,0.0),
child : Container (
height: 35,
// width: 400,
child : ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
Container(
padding: const EdgeInsets.fromLTRB(0, 7, 0, 8),
child: Text(
"Category",
style: TextStyle(
fontSize: 18.0,
color: Colors.black,
),
),
),
// Container(
// padding: const EdgeInsets.fromLTRB(29, 0, 0, 0),
// child: DropdownButton<String>(
// value: _categoryController.text,
// onChanged: (String newValue) {
// setState(() {
// _categoryController.text = newValue;
// });
// },
// items: _categoryDropDownItems.map<DropdownMenuItem<String>>((String value) {
// return DropdownMenuItem<String>(
// value: value,
// child: Text(value),
// );
// }).toList(),
// )
// ),
],
),
),
),
Padding(
padding: EdgeInsets.fromLTRB(0.0, 20.0, 0.0, 0.0),
child: Container(
height: 65.0,
child: RaisedButton(
onPressed: () {
try {
submitReceipt(
_amountController.text, _timeController.text,
_organizationController.organisation, _recurringController.text, _categoryController.text, _essentialController.text);
}
catch (_) {
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: new Text("Invalid data"),
content: new Text(
"We couldn't process your request because some of the data entered is invalid."),
actions: <Widget>[
new FlatButton(
child: new Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
},
child: Text("GO",
style:
TextStyle(color: Colors.white, fontSize: 22.0)),
color: Colors.blue,
),
),
),
],
),
),
);
}
}

View file

@ -1,606 +0,0 @@
import 'package:flutter/material.dart';
import 'package:local_spend/common/platform/platform_scaffold.dart';
import 'package:intl/intl.dart';
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:local_spend/common/widgets/animatedGradientButton.dart';
import 'package:local_spend/common/apifunctions/find_organisations.dart';
import 'package:local_spend/common/widgets/organisations_dialog.dart';
import 'package:local_spend/common/apifunctions/submit_receipt_api.dart';
import 'package:local_spend/common/apifunctions/categories.dart';
class Transaction {
Transaction(
this.date,
this.amount,
this.organisation,
this.recurring,
this.isEssential,
this.category,
);
DateTime date;
TextEditingController amount;
Organisation organisation;
String recurring;
bool isEssential;
String category;
}
class ReceiptPage2 extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return new ReceiptPage2State();
}
}
class ReceiptPage2State extends State<ReceiptPage2> {
Transaction transaction = new Transaction(
DateTime.now(),
new TextEditingController(),
new Organisation(null, null, null, null, null),
"None",
false,
"Uncategorised",
);
AlertDialog _invalidDialog(context) {
return AlertDialog(
title: new Text("Invalid data"),
content: new Text(
"We couldn't process your request because some of the data entered is invalid."),
actions: <Widget>[
new FlatButton(
child: new Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
}
Future<List<String>> getCats() async {
return await getCategories();
}
void _submitReceipt(Transaction transaction) {
Receipt receipt = new Receipt();
receipt.organisationName = transaction.organisation.name;
receipt.street = transaction.organisation.streetName;
receipt.postcode = transaction.organisation.postcode;
receipt.town = transaction.organisation.town;
receipt.recurring = transaction.recurring;
if (transaction.recurring == "None") {
receipt.recurring = "";
}
receipt.category = transaction.category;
if (transaction.category == "Uncategorised") {
receipt.category = "";
}
receipt.amount = transaction.amount.text.toString();
receipt.time = DateFormat("yyyy-MM-dd'T'hh:mm':00.000+01:00'")
.format(transaction.date)
.toString();
receipt.essential = transaction.isEssential.toString();
submitReceiptAPI(context, receipt);
}
List<String> _recurringOptions = new List<String>(7);
List<String> _categories = new List<String>();
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
var _widgetHeight = MediaQuery.of(context).size.height * 0.06 < 40.0
? 40.0
: MediaQuery.of(context).size.height * 0.06;
var _fontSize = _widgetHeight * 0.45;
var _fontSizeButton = _widgetHeight * 0.5;
if (_categories.isEmpty) {
Future<List<String>> _futureCats = getCats();
_categories.add("Fetching categories...");
_futureCats.then((value) {
_categories = value;
_categories.insert(0, "Uncategorised");
setState(() {});
});
}
_recurringOptions[0] = "None";
_recurringOptions[1] = "Daily";
_recurringOptions[2] = "Weekly";
_recurringOptions[3] = "Fortnightly";
_recurringOptions[4] = "Monthly";
_recurringOptions[5] = "Quarterly";
_recurringOptions[6] = "Yearly";
// these will be difficult to fetch from server as they are coded into the site's HTML rather than fetched
return PlatformScaffold(
appBar: AppBar(
backgroundColor: Colors.blue[400],
title: Text(
"Submit Receipt",
style: TextStyle(
fontSize: 20,
color: Colors.white,
),
),
centerTitle: true,
iconTheme: IconThemeData(color: Colors.black),
),
body: ListView(
children: <Widget>[
// each CHILD has its own horizontal padding because if the listView
// has padding, Android's end-of-scroll animation
// doesn't fit the screen properly and looks weird
Container(
padding: EdgeInsets.fromLTRB(
MediaQuery.of(context).size.width * 0.025,
MediaQuery.of(context).size.height * 0.025,
0,
0.0),
child: Text(
"Receipt Details",
style: TextStyle(
fontSize: 26,
color: Colors.grey[700],
fontWeight: FontWeight.bold,
),
),
), // "Receipt Details" title
Container(
padding: EdgeInsets.fromLTRB(
MediaQuery.of(context).size.width * 0.05,
15,
MediaQuery.of(context).size.width * 0.05,
0.0),
child: Tooltip(
message: "Date and time of transaction",
child: Row(
children: <Widget>[
Container(
child: Text(
"Date/Time",
style: TextStyle(
fontSize: _fontSize,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
width: MediaQuery.of(context).size.width * 0.3,
),
Container(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
height: _widgetHeight,
width: MediaQuery.of(context).size.width * 0.6,
child: RaisedButton(
onPressed: () {
showModalBottomSheet(
context: context,
builder: (BuildContext builder) {
return Container(
height: MediaQuery.of(context)
.copyWith()
.size
.height /
3,
child: CupertinoDatePicker(
initialDateTime:
transaction.date.isAfter(DateTime.now())
? DateTime.now()
: transaction.date,
onDateTimeChanged: (DateTime newDate) {
setState(() => {
newDate.isAfter(DateTime.now())
? transaction.date =
DateTime.now()
: transaction.date = newDate,
});
},
use24hFormat: true,
maximumDate: DateTime.now(),
),
);
});
},
child: Text(
transaction.date == null
? 'None set.'
: transaction.date.year != DateTime.now().year
? '${new DateFormat.MMMd().format(transaction.date)}' +
" " +
transaction.date.year.toString() +
" at " +
'${new DateFormat.Hm().format(transaction.date)}'
: transaction.date.day == DateTime.now().day &&
transaction.date.month ==
DateTime.now().month
? "Today at " +
'${new DateFormat.Hm().format(transaction.date)}'
: '${new DateFormat.MMMd().format(transaction.date)}' +
" at " +
'${new DateFormat.Hm().format(transaction.date)}',
style: TextStyle(
color: Colors.white, fontSize: _fontSizeButton),
),
color: Colors.blue,
),
),
],
),
),
), // Date/Time picker
Container(
padding: EdgeInsets.fromLTRB(
MediaQuery.of(context).size.width * 0.05,
15,
MediaQuery.of(context).size.width * 0.05,
0.0),
child: Tooltip(
message: "Transaction payee",
child: Row(
children: <Widget>[
Container(
child: Text(
"Payee",
style: TextStyle(
fontSize: _fontSize,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
width: MediaQuery.of(context).size.width * 0.3,
),
Container(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
height: _widgetHeight,
width: MediaQuery.of(context).size.width * 0.6,
child: RaisedButton(
onPressed: () {
var organisations = new FindOrganisations();
var orgDialog = organisations.dialog(context);
orgDialog.then((organisation) {
try {
organisation.name.length;
transaction.organisation = organisation;
// debugPrint(organisation.name);
setState(() {});
} catch (_) {
debugPrint("No organisation chosen.");
}
});
},
child: Text(
transaction.organisation.name == null
? 'Find'
: transaction.organisation.name.length > 14
? transaction.organisation.name
.substring(0, 12) +
"..."
: transaction.organisation.name,
style: TextStyle(
color: Colors.white, fontSize: _fontSizeButton),
),
color: Colors.blue,
),
),
],
),
),
), // Organisation picker
Container(
padding: EdgeInsets.fromLTRB(
MediaQuery.of(context).size.width * 0.05,
15,
MediaQuery.of(context).size.width * 0.05,
0.0),
child: Tooltip(
message: "Repeating?",
child: Row(
children: <Widget>[
Container(
child: Text(
"Recurring",
style: TextStyle(
fontSize: _fontSize,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
width: MediaQuery.of(context).size.width * 0.3,
),
Container(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
height: _widgetHeight,
width: MediaQuery.of(context).size.width * 0.6,
child: RaisedButton(
onPressed: () {
transaction.recurring = _recurringOptions[0];
setState(() {});
showModalBottomSheet(
context: context,
builder: (BuildContext builder) {
return Container(
height: MediaQuery.of(context)
.copyWith()
.size
.height /
3,
child: CupertinoPicker(
backgroundColor: Colors.white,
children: _recurringOptions
.map((thisOption) => Text(thisOption,
style: TextStyle(fontSize: 30)))
.toList(),
onSelectedItemChanged: ((newValue) {
transaction.recurring =
_recurringOptions[newValue];
setState(() {});
}),
magnification: 1.1,
useMagnifier: true,
itemExtent: 36,
),
);
});
},
child: Text(
transaction.recurring == null
? 'None'
: transaction.recurring,
style: TextStyle(
color: Colors.white, fontSize: _fontSizeButton),
),
color: Colors.blue,
),
),
],
),
),
), // Recurring picker
Container(
padding: EdgeInsets.fromLTRB(
MediaQuery.of(context).size.width * 0.05,
15,
MediaQuery.of(context).size.width * 0.05,
0.0),
child: Row(
children: <Widget>[
Container(
child: Text(
"Category",
style: TextStyle(
fontSize: _fontSize,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
width: MediaQuery.of(context).size.width * 0.3,
),
Container(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
height: _widgetHeight,
width: MediaQuery.of(context).size.width * 0.6,
child: Tooltip(
message: "Category of transaction",
child: RaisedButton(
onPressed: () {
transaction.category = _categories[0];
setState(() {});
showModalBottomSheet(
context: context,
builder: (BuildContext builder) {
return Container(
height: MediaQuery.of(context)
.copyWith()
.size
.height /
3,
child: CupertinoPicker(
backgroundColor: Colors.white,
children: _categories
.map((thisOption) => Text(
thisOption,
style: TextStyle(fontSize: 30),
))
.toList(),
onSelectedItemChanged: ((newValue) {
transaction.category =
_categories[newValue];
setState(() {});
}),
magnification: 1.1,
useMagnifier: true,
itemExtent: 36,
),
);
});
},
child: Text(
transaction.category == null
? 'None'
: transaction.category,
style: TextStyle(
color: Colors.white, fontSize: _fontSizeButton),
),
color: Colors.blue,
),
),
),
],
),
), // Category picker
Container(
padding: EdgeInsets.fromLTRB(
MediaQuery.of(context).size.width * 0.05,
15,
MediaQuery.of(context).size.width * 0.05,
0.0),
child: Tooltip(
message: "Essential or not",
child: Row(
children: <Widget>[
Container(
child: Text(
"Essential",
style: TextStyle(
fontSize: _fontSize,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
width: MediaQuery.of(context).size.width * 0.3,
),
Container(
height: _widgetHeight,
width: MediaQuery.of(context).size.width * 0.6,
child: Checkbox(
value: transaction.isEssential,
onChanged: ((value) {
setState(() => transaction.isEssential = value);
}),
),
),
],
),
),
), // Essential
Container(
padding: EdgeInsets.fromLTRB(
MediaQuery.of(context).size.width * 0.05,
15,
MediaQuery.of(context).size.width * 0.05,
0.0),
child: Tooltip(
message: "Transaction amount",
child: Row(
children: <Widget>[
Container(
child: Text(
"Amount",
style: TextStyle(
fontSize: _fontSize,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
width: MediaQuery.of(context).size.width * 0.3,
),
Container(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
height: _widgetHeight,
width: MediaQuery.of(context).size.width * 0.6,
child: TextField(
style: TextStyle(
fontSize: _fontSize,
),
textAlign: TextAlign.center,
controller: transaction.amount,
decoration: InputDecoration(hintText: "0.00"),
keyboardType: TextInputType.numberWithOptions(
decimal: true, signed: true),
),
),
],
),
),
), // Amount picker
Padding(
padding: EdgeInsets.fromLTRB(
MediaQuery.of(context).size.width * 0.05,
MediaQuery.of(context).size.height * 0.03,
MediaQuery.of(context).size.width * 0.05,
15.0),
child: Tooltip(
message: "Submit receipt",
child: Container(
height: _widgetHeight * 1.7,
child: ClipRRect(
borderRadius: BorderRadius.circular(2),
child: Opacity(
opacity: 1,
child: Stack(
children: [
AnimatedBackground(
[Colors.blue, Colors.lightBlue[300]],
Colors.lightBlue,
Alignment.topLeft,
Alignment.bottomRight,
4),
Material(
type: MaterialType.transparency,
child: InkWell(
child: Center(
child: Text(
"GO",
style: TextStyle(
color: Colors.white, fontSize: 30.0),
),
),
onTap: () {
try {
if (transaction.amount.text == "" ||
transaction.organisation.name == null) {
showDialog(
context: context,
builder: (BuildContext context) {
return _invalidDialog(context);
});
} else {
if (double.tryParse(
transaction.amount.text) !=
null &&
double.tryParse(transaction.amount.text) >
0) {
_submitReceipt(transaction);
} else {
showDialog(
context: context,
builder: (BuildContext context) {
return _invalidDialog(context);
});
}
}
} catch (_) {
showDialog(
context: context,
builder: (BuildContext context) {
return _invalidDialog(context);
});
}
},
),
),
],
),
),
),
),
),
),
],
),
);
}
}

View file

@ -10,9 +10,9 @@ class SplashScreen extends StatefulWidget {
}
class _SplashScreenState extends State<SplashScreen> {
final int splashDuration = 1;
final int splashDuration = 3;
Future<Timer> startTime() async {
startTime() async {
return Timer(Duration(seconds: splashDuration), () {
SystemChannels.textInput.invokeMethod('TextInput.hide');
Navigator.of(context).pushReplacementNamed('/LoginPage');
@ -27,7 +27,10 @@ class _SplashScreenState extends State<SplashScreen> {
@override
Widget build(BuildContext context) {
var drawer = Drawer();
return PlatformScaffold(
drawer: drawer,
body: Container(
decoration: BoxDecoration(color: Colors.white),
child: Column(
@ -35,9 +38,10 @@ class _SplashScreenState extends State<SplashScreen> {
Expanded(
child: Container(
margin: EdgeInsets.all(15),
alignment: FractionalOffset(0.5, 0.3),
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/launch_image.png'),
image: AssetImage('assets/images/launch_image.png')
),
),
),
@ -45,7 +49,7 @@ class _SplashScreenState extends State<SplashScreen> {
Container(
margin: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 30.0),
child: Text(
"© Copyright Pear Trading 2019",
"© Copyright Statement 2019",
style: TextStyle(
fontSize: 16.0,
color: Colors.black,

View file

@ -1,25 +1,30 @@
import 'package:flutter/material.dart';
import 'package:local_spend/common/platform/platform_scaffold.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:local_spend/pages/customerGraphs.dart';
import 'package:local_spend/pages/orgGraphs.dart';
import 'package:local_spend/common/functions/logout.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:local_spend/common/functions/customAbout.dart' as custom;
import 'package:local_spend/common/functions/showDialogTwoButtons.dart';
import 'package:local_spend/common/widgets/charts/donut_chart.dart';
import 'package:local_spend/common/widgets/charts/outside_label.dart';
import 'package:local_spend/common/widgets/charts/auto_label.dart';
import 'package:local_spend/common/widgets/charts/grouped_bar_chart.dart';
import 'package:local_spend/common/widgets/charts/scatter_bucketingAxis_legend.dart';
import 'package:local_spend/common/widgets/charts/numeric_line_bar_combo.dart';
import 'package:local_spend/common/widgets/charts/series_legend_with_measures.dart';
import 'package:local_spend/common/widgets/awesome_drawer.dart';
const url = "https://flutter.io/";
const URL = "https://flutter.io/";
const demonstration = false;
class StatsPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
print(
"TODO: The 'stats' page should be loaded on login and cached rather than reloading on every opening of the page.");
print(
"Create new List<GraphData> in instantiated MyApp() and pass that or load it from this class' child with (graphs = super.graphList) or something.");
return new StatsPageState();
}
}
class StatsPageState extends State<StatsPage> {
String userType = "-";
@override
void initState() {
@ -32,61 +37,173 @@ class StatsPageState extends State<StatsPage> {
super.dispose();
}
void _saveCurrentRoute(String lastRoute) async {
_saveCurrentRoute(String lastRoute) async {
SharedPreferences preferences = await SharedPreferences.getInstance();
await preferences.setString('LastPageRoute', lastRoute);
}
Future<String> _getUserType() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
return await preferences.get('LastUserType');
}
@override
Widget build(BuildContext context) {
if (userType == "-") {
_getUserType().then((value) {
print(value);
userType =
'${value[0].toUpperCase()}${value.substring(1)}'; // capitalises first letter
setState(() {});
});
}
var drawer = new AwesomeDrawer();
return PlatformScaffold(
drawer: drawer.getDrawer(context),
appBar: AppBar(
backgroundColor: Colors.blue[400],
title: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Statistics",
style: TextStyle(
fontSize: 20,
color: Colors.white,
),
),
Padding(padding: EdgeInsets.symmetric(horizontal: 4)),
Text(
userType,
style: TextStyle(
fontSize: 20,
color: Colors.white70,
),
),
],
title: Text(
"Statistics",
style: TextStyle(
fontSize: 20,
color: Colors.white,
),
),
centerTitle: true,
iconTheme: IconThemeData(color: Colors.black),
),
body: Container(
body : Container(
padding: EdgeInsets.fromLTRB(0, 0, 0, 0),
child: (userType == "-"
? null
: (userType.toLowerCase() == "customer"
? CustomerGraphs()
: OrgGraphs())),
child: ListView(
children: <Widget>[
Container(
padding: EdgeInsets.fromLTRB(0.0,17,0.0,0.0),
child : Text(
"GroupedBarChart",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 10),
height: 200,
child: new GroupedBarChart.withSampleData()
),
Container(
padding: EdgeInsets.fromLTRB(0.0,17,0.0,0.0),
child : Text(
"BucketingAxisScatterPlotChart",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 10),
height: 200,
child: new BucketingAxisScatterPlotChart.withSampleData()
),
Container(
padding: EdgeInsets.fromLTRB(0.0,20,0.0,0.0),
child : Text(
"PieOutsideLabelChart",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 10),
height: 200,
// width: 250,
child: new PieOutsideLabelChart.withSampleData()
),
Container(
padding: EdgeInsets.fromLTRB(0.0,17,0.0,0.0),
child : Text(
"DonutAutoLabelChart",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 10),
height: 200,
child: new DonutAutoLabelChart.withSampleData()
),
Container(
padding: EdgeInsets.fromLTRB(0.0,17,0.0,0.0),
child : Text(
"DonutPieChart",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 10),
height: 200,
child: new DonutPieChart.withSampleData()
),
Container(
padding: EdgeInsets.fromLTRB(0.0,17,0.0,0.0),
child : Text(
"NumericComboLineBarChart",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 10),
height: 200,
child: new NumericComboLineBarChart.withSampleData()
),
Container(
padding: EdgeInsets.fromLTRB(0.0,17,0.0,0.0),
child : Text(
"LegendWithMeasures",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 10),
height: 200,
child: new LegendWithMeasures.withSampleData()
),
],
),
),
);
}

View file

@ -1,160 +1,125 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
url: "https://pub.dartlang.org"
source: hosted
version: "6.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
version: "0.39.15"
version: "0.36.4"
args:
dependency: transitive
description:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "1.6.0"
version: "1.5.2"
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.2"
version: "2.2.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "1.0.4"
build:
dependency: transitive
description:
name: build
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "1.1.4"
build_config:
dependency: transitive
description:
name: build_config
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.2"
version: "0.4.0"
build_daemon:
dependency: transitive
description:
name: build_daemon
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.4"
version: "1.1.0"
build_resolvers:
dependency: transitive
description:
name: build_resolvers
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.10"
version: "1.0.5"
build_runner:
dependency: "direct dev"
description:
name: build_runner
url: "https://pub.dartlang.org"
source: hosted
version: "1.10.0"
version: "1.6.1"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
url: "https://pub.dartlang.org"
source: hosted
version: "5.2.0"
version: "3.0.6"
built_collection:
dependency: transitive
description:
name: built_collection
url: "https://pub.dartlang.org"
source: hosted
version: "4.3.2"
version: "4.2.2"
built_value:
dependency: transitive
description:
name: built_value
url: "https://pub.dartlang.org"
source: hosted
version: "7.1.0"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
version: "6.6.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.3"
version: "1.1.2"
charts_common:
dependency: transitive
description:
name: charts_common
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.0"
version: "0.6.0"
charts_flutter:
dependency: "direct main"
description:
name: charts_flutter
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.0"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
cli_util:
dependency: transitive
description:
name: cli_util
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.4"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
version: "0.6.0"
code_builder:
dependency: transitive
description:
name: code_builder
url: "https://pub.dartlang.org"
source: hosted
version: "3.4.0"
version: "3.2.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.14.13"
version: "1.14.11"
convert:
dependency: transitive
description:
@ -168,56 +133,42 @@ packages:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.5"
version: "2.0.6"
csslib:
dependency: transitive
description:
name: csslib
url: "https://pub.dartlang.org"
source: hosted
version: "0.16.2"
version: "0.16.0"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.3"
version: "0.1.2"
dart_style:
dependency: transitive
description:
name: dart_style
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.6"
version: "1.2.7"
datetime_picker_formfield:
dependency: "direct main"
description:
name: datetime_picker_formfield
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
file:
dependency: transitive
description:
name: file
url: "https://pub.dartlang.org"
source: hosted
version: "5.2.1"
version: "0.1.8"
fixnum:
dependency: transitive
description:
name: fixnum
url: "https://pub.dartlang.org"
source: hosted
version: "0.10.11"
version: "0.10.9"
flutter:
dependency: "direct main"
description: flutter
@ -236,50 +187,31 @@ packages:
name: flutter_linkify
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.3"
version: "1.1.1"
flutter_localizations:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.8"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
front_end:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
description:
name: front_end
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.19"
glob:
dependency: transitive
description:
name: glob
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
google_maps_flutter:
dependency: "direct main"
description:
name: google_maps_flutter
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.28+1"
google_maps_flutter_platform_interface:
dependency: transitive
description:
name: google_maps_flutter_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
version: "1.1.7"
graphs:
dependency: transitive
description:
@ -293,91 +225,91 @@ packages:
name: html
url: "https://pub.dartlang.org"
source: hosted
version: "0.14.0+3"
version: "0.14.0+2"
http:
dependency: "direct main"
description:
name: http
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.2"
version: "0.12.0+2"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.0"
version: "2.1.0"
http_parser:
dependency: transitive
description:
name: http_parser
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.4"
version: "3.1.3"
intl:
dependency: transitive
description:
name: intl
url: "https://pub.dartlang.org"
source: hosted
version: "0.16.1"
version: "0.15.8"
io:
dependency: transitive
description:
name: io
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.4"
version: "0.3.3"
js:
dependency: transitive
description:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.2"
version: "0.6.1+1"
json_annotation:
dependency: "direct main"
description:
name: json_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
version: "2.3.0"
json_serializable:
dependency: "direct main"
dependency: "direct dev"
description:
name: json_serializable
url: "https://pub.dartlang.org"
source: hosted
version: "3.3.0"
linkify:
version: "2.3.0"
kernel:
dependency: transitive
description:
name: linkify
name: kernel
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
version: "0.3.19"
logging:
dependency: transitive
description:
name: logging
url: "https://pub.dartlang.org"
source: hosted
version: "0.11.4"
version: "0.11.3+2"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.8"
version: "0.12.5"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.8"
version: "1.1.6"
mime:
dependency: transitive
description:
@ -385,76 +317,34 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.6+3"
node_interop:
dependency: transitive
description:
name: node_interop
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
node_io:
dependency: transitive
description:
name: node_io
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
package_config:
dependency: transitive
description:
name: package_config
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.3"
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:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1+2"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
version: "1.6.2"
pedantic:
dependency: "direct dev"
dependency: transitive
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.0"
platform:
dependency: transitive
description:
name: platform
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.1"
platform_detect:
dependency: transitive
description:
name: platform_detect
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.0"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
version: "1.7.0"
pool:
dependency: transitive
description:
@ -462,104 +352,41 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.0"
process:
dependency: transitive
description:
name: process
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.13"
pub_semver:
dependency: transitive
description:
name: pub_semver
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.4"
version: "1.4.2"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.5"
version: "0.1.4"
quiver:
dependency: transitive
description:
name: quiver
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.3"
sa_anicoto:
dependency: transitive
description:
name: sa_anicoto
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
sa_multi_tween:
dependency: transitive
description:
name: sa_multi_tween
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
sa_stateless_animation:
dependency: transitive
description:
name: sa_stateless_animation
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
sa_v1_migration:
dependency: transitive
description:
name: sa_v1_migration
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.2"
version: "2.0.3"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.8"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.2+1"
shared_preferences_macos:
dependency: transitive
description:
name: shared_preferences_macos
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1+10"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.2+7"
version: "0.4.3"
shelf:
dependency: transitive
description:
name: shelf
url: "https://pub.dartlang.org"
source: hosted
version: "0.7.7"
version: "0.7.5"
shelf_web_socket:
dependency: transitive
description:
@ -567,13 +394,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.3"
simple_animations:
dependency: "direct main"
description:
name: simple_animations
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.1"
sky_engine:
dependency: transitive
description: flutter
@ -585,21 +405,21 @@ packages:
name: source_gen
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.6"
version: "0.9.4+2"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
version: "1.5.5"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.5"
version: "1.9.3"
stream_channel:
dependency: transitive
description:
@ -613,21 +433,14 @@ packages:
name: stream_transform
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "0.0.19"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
supercharged:
dependency: transitive
description:
name: supercharged
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
version: "1.0.4"
term_glyph:
dependency: transitive
description:
@ -641,56 +454,28 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.17"
version: "0.2.5"
timing:
dependency: transitive
description:
name: timing
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.1+2"
version: "0.1.1+1"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.1.6"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
url: "https://pub.dartlang.org"
source: hosted
version: "5.5.0"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1+1"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1+7"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.7"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.2"
version: "3.0.3"
vector_math:
dependency: transitive
description:
@ -704,28 +489,21 @@ packages:
name: watcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.7+15"
version: "0.9.7+10"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.0"
version: "1.0.13"
yaml:
dependency: transitive
description:
name: yaml
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.1"
version: "2.1.16"
sdks:
dart: ">=2.9.0-14.0.dev <3.0.0"
flutter: ">=1.16.3 <2.0.0"
dart: ">=2.3.0-dev.0.1 <3.0.0"
flutter: ">=0.1.4 <2.0.0"

View file

@ -10,24 +10,21 @@ description: Local Spend Tracker
version: 1.0.0+1
environment:
sdk: ">=2.2.2 <3.0.0"
sdk: ">=2.0.0-dev.68.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
shared_preferences: ^0.5.8
url_launcher: ^5.5.0
json_annotation : ^3.0.1
shared_preferences: ^0.4.2
url_launcher: ^3.0.3
json_annotation : ^2.2.0
http: ^0.12.0+2
datetime_picker_formfield: ^1.0.0
flutter_linkify: ^3.1.3
datetime_picker_formfield: ^0.1.8
flutter_linkify: ^1.0.3
flutter_fadein: ^1.1.1
charts_flutter: ^0.9.0
simple_animations: ^2.2.1
google_maps_flutter: ^0.5.20+5
json_serializable: ^3.3.0
charts_flutter: ^0.6.0
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
@ -35,8 +32,9 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
pedantic: ^1.4.0
build_runner: ^1.10.0
build_runner: ^1.1.3
json_serializable: ^2.1.2
# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec
@ -57,11 +55,6 @@ flutter:
- assets/
- assets/images/
- assets/images/launch_image.png
fonts:
- family: Consolas
fonts:
- asset: assets/Consolas.ttf
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.io/assets-and-images/#resolution-aware.

View file

@ -11,16 +11,20 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:local_spend/main.dart';
void main() {
testWidgets('GO button repetition test', (WidgetTester tester) async {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
// Tap the GO button and trigger a frame.
await tester.tap(find.byKey(Key("goButton")));
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that the dialog has shown
// expect(find.text('GO'), findsNothing);
// expect(find.text('Invalid data'), findsOneWidget);
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}