Compare commits

...
This repository has been archived on 2023-08-16. You can view files and clone it, but cannot push or open issues or pull requests.

46 commits

Author SHA1 Message Date
Rumperuu
ceafbb279f Fix typo 2021-03-20 09:43:18 +00:00
Rumperuu
9381458d79 Update readme 2021-03-20 09:40:50 +00:00
Tom Bloor
203d375aa4
Merge pull request #9 from Pear-Trading/fix/update
Merge `felix/main` branch into `development`, whilst also updating it
2020-08-05 14:04:43 +01:00
Rumperuu
5f53932995
Merge branch 'development' into fix/update 2020-07-29 21:55:02 +01:00
96508a0228 Upgrade resolvable dependencies 2020-07-29 21:48:40 +01:00
b7e23081e2 Upgrade to AndroidX 2020-07-29 21:47:40 +01:00
2d462788b7 Upgrade to new Android platform-side wrappers 2020-07-29 21:47:11 +01:00
Rumperuu
f2ecedd5c5
Update README.md 2020-07-29 15:12:00 +01:00
Rumperuu
efa74e2207
Update README.md 2020-07-29 15:11:33 +01:00
Felix
2d741e61c7 about page 2019-09-02 17:12:19 +01:00
Felix
a512549778 Maps, optimization, testing, code cleanup 2019-09-02 12:57:14 +01:00
Felix
c6e69b037b profile mode, no changes 2019-08-27 17:00:19 +01:00
Felix
96fdc01eea logout works properly 2019-08-27 12:18:02 +01:00
Felix
204cd74544 pickers now reset on opening
not sure if this looks too great
2019-08-27 11:50:27 +01:00
Felix
c6f6917226 Revert "that's a strange bug indeed"
This reverts commit 7aeccb46cf.
2019-08-27 11:41:08 +01:00
Felix
7aeccb46cf that's a strange bug indeed 2019-08-27 11:39:20 +01:00
Felix
ac82ec4d48 most of the bugs are just me being silly ngl 2019-08-27 11:37:13 +01:00
Felix
ece2c62762 i'm so stupid ughhh 2019-08-27 11:22:19 +01:00
Felix
f4a2e6ca7a save before debugging marathon 2019-08-27 11:00:57 +01:00
Felix
030f482a54 categories still broken 2019-08-23 17:52:48 +01:00
Felix
62d50f86a8 (profile mode) 2019-08-21 17:06:53 +01:00
Felix
d8ecd50acd progress indicators 2019-08-21 16:59:08 +01:00
Felix
7553ce25f8 organisations graphs
save login is now somehow broken 🧐
2019-08-21 16:50:26 +01:00
Felix
55310068ea Code is B-E-A-U-TIFUL! All complies to standards.
Complies to flutter/dart 'pedantic' coding standards (their name, not mine)
2019-08-21 14:53:52 +01:00
Felix
19021a9a09 maps api implementation started 2019-08-21 12:35:21 +01:00
Felix
cf6753363c lots of UI and some improvements to logic 2019-08-21 10:16:46 +01:00
Felix
68102ea628 orgs dialog button disables/enables properly 2019-08-20 16:37:32 +01:00
Felix
ebf09413fd fixed slow rebuilding slightly 2019-08-20 16:08:11 +01:00
Felix
f5d2e991a8 ui improvements 2019-08-20 16:01:56 +01:00
Felix
28cd64aaee i need to fix these pickers 2019-08-20 14:13:01 +01:00
Felix
49038e9adc consistency in ui 2019-08-20 14:06:39 +01:00
Felix
d7bcb29eda made UI more responsive to screen size 2019-08-20 14:04:31 +01:00
Felix
84c2fb1f2a button is less square 2019-08-20 13:58:53 +01:00
Felix
231ed2c9df many UI improvements and subtle tweaks 2019-08-20 13:54:45 +01:00
Felix
9a5bfbaaf0 some info added to orgs dialog 2019-08-19 15:47:28 +01:00
Felix
cda3fc57e9 fix broken dialog 2019-08-19 15:36:41 +01:00
Felix
e44687fb6b ui, save before experiment 2019-08-19 15:33:08 +01:00
Felix
b4c93ff80d writing functions to get org graph data 2019-08-19 15:23:58 +01:00
Felix
6cd65b8ac6 implementing organisation/customer-specific graphs 2019-08-19 15:02:02 +01:00
Felix
05a57e8203 fixing Dispose() errors
some widgets aren't loading and disposing properly
2019-08-19 13:50:30 +01:00
Felix
68a33c53e9 animation, tooltips, ui sizing fixed 2019-08-19 12:36:12 +01:00
Felix
abbd0a7170 tooltips added to graphs 2019-08-19 11:58:26 +01:00
Felix
d0defec7d6 testing has commenced
adding accessibility options now
2019-08-19 11:55:09 +01:00
Felix
ff7cdf4907 let the testing begin! 2019-08-19 11:43:53 +01:00
Felix
2e1b74a222 dialog looks slightly better 2019-08-19 11:19:41 +01:00
Felix
aa40a5c926 removed broken reference 2019-08-19 10:05:07 +01:00
71 changed files with 2427 additions and 1276 deletions

2
.gitignore vendored
View file

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

View file

@ -0,0 +1,116 @@
<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,31 +1,99 @@
# local_spend # LocalSpend (Mobile App.)
Local Spend Tracker Looking to discover if the value of spending local can be measured, understood and shown.
## Getting Started This repository contains the mobile application for the LocalSpend system. See also:
This project is a starting point for a Flutter application. * the [Web application](https://github.com/Pear-Trading/Foodloop-Web); and
* the [server](https://github.com/Pear-Trading/Foodloop-Server).
A few resources to get you started if this is your first Flutter project: ## Table of Contents
- [Lab: Write your first Flutter app](https://flutter.io/docs/get-started/codelab) * [Tech Stack](#tech-stack)
- [Cookbook: Useful Flutter samples](https://flutter.io/docs/cookbook) * [Features](#features)
* [Installation](#installation)
* [Configuration](#configuration)
* [Usage](#usage)
* [Testing](#testing)
* [Code Formatting](#code-formatting)
* [Documentation](#documentation)
* [Acknowledgments](#acknowledgements)
* [License](#license)
* [Contact](#contact)
For help getting started with Flutter, view our ## Technology Stack
[online documentation](https://flutter.io/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
## Environments The mobile app. is written in [Dart](https://dart.dev/).
To build an apk from dev, use: | Technology | Description | Link |
flutter build apk -t lib/main_dev.dart |-------------|---------------------------------|---------------------|
| Flutter | Cross-platform mobile framework | [Link][flutter] |
## Links and Things [flutter]: https://flutter.dev/
https://github.com/putraxor/flutter-login-ui ## Features
https://github.com/pbirdsall/medium_splash_tokenauth
## How to debug code This mobile app. provides:
// debug
- 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
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
debugPrint('$foo'); ```
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

18
analysis_options.yaml Normal file
View file

@ -0,0 +1,18 @@
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" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android { android {
compileSdkVersion 27 compileSdkVersion 28
lintOptions { lintOptions {
disable 'InvalidPackage' disable 'InvalidPackage'

View file

@ -13,11 +13,18 @@
additional functionality it is fine to subclass or reimplement additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. --> FlutterApplication and put your custom class here. -->
<application <application
android:name="io.flutter.app.FlutterApplication" android:label="SpendTracker"
android:label="local_spend"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity" <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"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
@ -27,9 +34,19 @@
until Flutter renders its first frame. It can be removed if until Flutter renders its first frame. It can be removed if
there is no splash screen (such as the default splash screen there is no splash screen (such as the default splash screen
defined in @style/LaunchTheme). --> defined in @style/LaunchTheme). -->
<!-- Specify that the launch screen should continue being displayed -->
<!-- until Flutter renders its first frame. -->
<meta-data <meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame" android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:value="true" /> 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"
/>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>

View file

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

View file

@ -0,0 +1,12 @@
<?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,4 +5,8 @@
Flutter draws its first frame --> Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item> <item name="android:windowBackground">@drawable/launch_background</item>
</style> </style>
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/normal_background</item>
</style>
</resources> </resources>

View file

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

BIN
assets/Consolas.ttf Normal file

Binary file not shown.

BIN
assets/images/cat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
assets/images/text.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

View file

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project # 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. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View file

@ -1,5 +1,13 @@
PODS: PODS:
- Flutter (1.0.0) - 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): - shared_preferences (0.0.1):
- Flutter - Flutter
- url_launcher (0.0.1): - url_launcher (0.0.1):
@ -7,12 +15,19 @@ PODS:
DEPENDENCIES: DEPENDENCIES:
- Flutter (from `.symlinks/flutter/ios-profile`) - Flutter (from `.symlinks/flutter/ios-profile`)
- google_maps_flutter (from `.symlinks/plugins/google_maps_flutter/ios`)
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
- url_launcher (from `.symlinks/plugins/url_launcher/ios`) - url_launcher (from `.symlinks/plugins/url_launcher/ios`)
SPEC REPOS:
https://github.com/cocoapods/specs.git:
- GoogleMaps
EXTERNAL SOURCES: EXTERNAL SOURCES:
Flutter: Flutter:
:path: ".symlinks/flutter/ios-profile" :path: ".symlinks/flutter/ios-profile"
google_maps_flutter:
:path: ".symlinks/plugins/google_maps_flutter/ios"
shared_preferences: shared_preferences:
:path: ".symlinks/plugins/shared_preferences/ios" :path: ".symlinks/plugins/shared_preferences/ios"
url_launcher: url_launcher:
@ -20,9 +35,11 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS: SPEC CHECKSUMS:
Flutter: 58dd7d1b27887414a370fcccb9e645c08ffd7a6a Flutter: 58dd7d1b27887414a370fcccb9e645c08ffd7a6a
google_maps_flutter: 78a52114c898b42ea647919679a4c58b70abe876
GoogleMaps: cfee83da305b9aaeccf92c24ac79df11c3003492
shared_preferences: 1feebfa37bb57264736e16865e7ffae7fc99b523 shared_preferences: 1feebfa37bb57264736e16865e7ffae7fc99b523
url_launcher: 0067ddb8f10d36786672aa0722a21717dba3a298 url_launcher: 0067ddb8f10d36786672aa0722a21717dba3a298
PODFILE CHECKSUM: aff02bfeed411c636180d6812254b2daeea14d09 PODFILE CHECKSUM: 3389836f37640698630b8f0670315d626d5f1469
COCOAPODS: 1.7.5 COCOAPODS: 1.7.5

View file

@ -163,6 +163,7 @@
9705A1C41CF9048500538489 /* Embed Frameworks */, 9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
ACE3DC71CDB8FA5537935604 /* [CP] Embed Pods Frameworks */, ACE3DC71CDB8FA5537935604 /* [CP] Embed Pods Frameworks */,
E6C2807EE928990FB790046F /* [CP] Copy Pods Resources */,
); );
buildRules = ( buildRules = (
); );
@ -292,6 +293,24 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0; 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 */ /* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
@ -390,6 +409,7 @@
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
); );
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -519,6 +539,7 @@
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
); );
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -546,6 +567,7 @@
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
); );
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",

View file

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

View file

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

View file

@ -2,10 +2,9 @@ import 'dart:convert';
import 'dart:async'; import 'dart:async';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:local_spend/common/functions/get_token.dart'; import 'package:local_spend/common/functions/get_token.dart';
import 'package:flutter/material.dart';
Future<List<String>> getCategories() async { Future<List<String>> getCategories() async {
const url = "https://dev.peartrade.org/api/search/category"; const url = "https://dev.localspend.co.uk/api/search/category";
var token; var token;
await getToken().then((result) { await getToken().then((result) {
@ -38,7 +37,6 @@ Future<List<String>> getCategories() async {
int i = 1; // starts on 1. that was annoying to debug! int i = 1; // starts on 1. that was annoying to debug!
while (true) { while (true) {
if (categoriesJSON[i.toString()] != null) { if (categoriesJSON[i.toString()] != null) {
// print("Iteration " + i.toString()); // print("Iteration " + i.toString());
// print(categoriesJSON[i.toString()]); // print(categoriesJSON[i.toString()]);

View file

@ -1,16 +1,9 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:async'; import 'dart:async';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:local_spend/common/functions/get_token.dart'; import 'package:local_spend/common/functions/get_token.dart';
class Organisation { class Organisation {
var id = 0;
var name = "";
var postcode = "";
var streetName = ""; //street_name
var town = "";
Organisation( Organisation(
this.id, this.id,
this.name, this.name,
@ -18,22 +11,22 @@ class Organisation {
this.streetName, this.streetName,
this.town, this.town,
); );
var id = 0;
var name = "";
var postcode = "";
var streetName = ""; //street_name
var town = "";
} }
class Organisations { class Organisations {
List<Organisation> getTestData() { List<Organisation> getTestData() {
var numItems = 10; var numItems = 10;
var itemsList = new List<Organisation>(); var itemsList = new List<Organisation>();
for (int i = 0; i < numItems; i++) { for (int i = 0; i < numItems; i++) {
itemsList.add(new Organisation( itemsList.add(new Organisation(i, "Payee " + (i + 1).toString(),
i, "tee hee hee", "yeet street", "Robloxia"));
"Payee " + (i + 1).toString(),
"tee hee hee",
"yeet street",
"Robloxia"
));
} }
return itemsList; return itemsList;
@ -64,7 +57,7 @@ class Organisations {
} }
Future<List<Organisation>> findOrganisations(String search) async { Future<List<Organisation>> findOrganisations(String search) async {
final url = "https://dev.peartrade.org/api/search"; final url = "https://dev.localspend.co.uk/api/search";
var token; var token;
await getToken().then((result) { await getToken().then((result) {
@ -88,6 +81,5 @@ class Organisations {
// not successful // not successful
return null; return null;
} }
} }
} }

View file

@ -4,19 +4,137 @@ import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:charts_flutter/flutter.dart' as charts; import 'package:charts_flutter/flutter.dart' as charts;
class GraphData { /// Customer graph types: https://dev.localspend.co.uk/api/v1/customer/graphs
var chartType; /// - total_last_week
List<charts.Series<dynamic, DateTime>> graph; /// - 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( GraphData(
this.chartType, this.chartType,
); );
var chartType;
List<charts.Series<dynamic, DateTime>> graph;
List<TimeSeriesSpend> cachedData; List<TimeSeriesSpend> cachedData;
bool loaded = false; bool loaded = false;
Future<void> setGraphData() async { Future<List<charts.Series<dynamic, DateTime>>> setGraphData() async {
if (loaded == true) { if (loaded) {
this.graph = [ this.graph = [
new charts.Series<TimeSeriesSpend, DateTime>( new charts.Series<TimeSeriesSpend, DateTime>(
id: 'Spend', id: 'Spend',
@ -26,9 +144,10 @@ class GraphData {
data: cachedData, data: cachedData,
) )
]; ];
return null;
} }
final url = "https://dev.peartrade.org/api/v1/customer/graphs"; final url = "https://dev.localspend.co.uk/api/v1/customer/graphs";
SharedPreferences preferences = await SharedPreferences.getInstance(); SharedPreferences preferences = await SharedPreferences.getInstance();
Map<String, String> body = { Map<String, String> body = {
@ -49,7 +168,8 @@ class GraphData {
List<TimeSeriesSpend> timeSeriesSpendList = new List<TimeSeriesSpend>(); List<TimeSeriesSpend> timeSeriesSpendList = new List<TimeSeriesSpend>();
for (int i = 0; i < labels.length; i++) { for (int i = 0; i < labels.length; i++) {
timeSeriesSpendList.add(new TimeSeriesSpend(data[i]*1.00, DateTime.parse(labels[i]))); timeSeriesSpendList.add(
new TimeSeriesSpend(data[i] * 1.00, DateTime.parse(labels[i])));
// print(timeSeriesSpendList[i].time.toString() + " : " + timeSeriesSpendList[i].spend.toString()); // print(timeSeriesSpendList[i].time.toString() + " : " + timeSeriesSpendList[i].spend.toString());
} }
@ -65,11 +185,13 @@ class GraphData {
data: timeSeriesSpendList, data: timeSeriesSpendList,
) )
]; ];
return this.graph;
} else { } else {
final errorMessage = json.decode(response.body)['message']; final errorMessage = json.decode(response.body)['message'];
print("Error: " + errorMessage); print("Error: " + errorMessage);
this.graph = null; this.graph = null;
return null;
} }
} }
@ -86,16 +208,7 @@ class GraphData {
]; ];
} }
/// Graph types: final url = "https://dev.localspend.co.uk/api/v1/customer/graphs";
/// - total_last_week
/// - avg_spend_last_week
/// - total_last_month
/// - avg_spend_last_month
///
/// HTTP POST request sample:
/// {"graph":"total_last_week","session_key":"blahblahblah"}
final url = "https://dev.peartrade.org/api/v1/customer/graphs";
SharedPreferences preferences = await SharedPreferences.getInstance(); SharedPreferences preferences = await SharedPreferences.getInstance();
Map<String, String> body = { Map<String, String> body = {
@ -116,7 +229,8 @@ class GraphData {
List<TimeSeriesSpend> timeSeriesSpendList = new List<TimeSeriesSpend>(); List<TimeSeriesSpend> timeSeriesSpendList = new List<TimeSeriesSpend>();
for (int i = 0; i < labels.length; i++) { for (int i = 0; i < labels.length; i++) {
timeSeriesSpendList.add(new TimeSeriesSpend(data[i]*1.00, DateTime.parse(labels[i]))); timeSeriesSpendList.add(
new TimeSeriesSpend(data[i] * 1.00, DateTime.parse(labels[i])));
// print(timeSeriesSpendList[i].time.toString() + " : " + timeSeriesSpendList[i].spend.toString()); // print(timeSeriesSpendList[i].time.toString() + " : " + timeSeriesSpendList[i].spend.toString());
} }
@ -139,14 +253,18 @@ class GraphData {
return null; return null;
} }
} }
} }
class TimeSeriesSpend { class TimeSeriesSpend {
TimeSeriesSpend(this.spend, this.time);
final DateTime time; final DateTime time;
final double spend; final double spend;
TimeSeriesSpend(this.spend, this.time);
} }
class TimeSeriesCustomersOrSales {
TimeSeriesCustomersOrSales(this.numberOfStuff, this.time);
final DateTime time;
final double numberOfStuff;
}

View file

@ -0,0 +1,87 @@
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

@ -11,9 +11,13 @@ Future<void> _incorrectDialog(BuildContext context, bool isLoginWrong) async {
context: context, context: context,
barrierDismissible: true, barrierDismissible: true,
builder: (BuildContext context) { builder: (BuildContext context) {
return AlertDialog( return AnimatedContainer(
title: Text("Unable to Login"), duration: Duration(seconds: 2),
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."), 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>[ actions: <Widget>[
FlatButton( FlatButton(
child: Text('OK'), child: Text('OK'),
@ -22,6 +26,7 @@ Future<void> _incorrectDialog(BuildContext context, bool isLoginWrong) async {
}, },
), ),
], ],
),
); );
}, },
); );
@ -29,27 +34,26 @@ Future<void> _incorrectDialog(BuildContext context, bool isLoginWrong) async {
Future<LoginModel> requestLoginAPI( Future<LoginModel> requestLoginAPI(
BuildContext context, String email, String password) async { BuildContext context, String email, String password) async {
//var apiUrl = ConfigWrapper.of(context).apiKey; final url = "https://dev.localspend.co.uk/api/login";
final url = "https://dev.peartrade.org/api/login";
Map<String, String> body = { Map<String, String> body = {
'email': email, 'email': email,
'password': password, 'password': password,
}; };
// debugPrint('$body');
try { try {
final response = await http.post( final response = await http
.post(
url, url,
body: json.encode(body), body: json.encode(body),
); )
.timeout(Duration(seconds: 5));
if (response.statusCode == 200) { if (response.statusCode == 200) {
final responseJson = json.decode(response.body); final responseJson = json.decode(response.body);
saveCurrentLogin(responseJson, body["email"]); saveCurrentLogin(responseJson, body["email"]);
Navigator.of(context).pushReplacementNamed('/HomePage'); await Navigator.of(context).pushReplacementNamed('/HomePage');
return LoginModel.fromJson(responseJson); return LoginModel.fromJson(responseJson);
} else { } else {
@ -57,13 +61,15 @@ Future<LoginModel> requestLoginAPI(
saveCurrentLogin(responseJson, body["email"]); saveCurrentLogin(responseJson, body["email"]);
_incorrectDialog(context, true); await _incorrectDialog(context, true);
return null; return null;
} }
} on TimeoutException catch (_) {
await _incorrectDialog(context, false);
} catch (error) { } catch (error) {
debugPrint(error.toString()); debugPrint(error.toString());
_incorrectDialog(context, false); await _incorrectDialog(context, false);
} }
return null;
} }

View file

@ -1,14 +1,14 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:local_spend/common/functions/get_token.dart'; import 'package:local_spend/common/functions/get_token.dart';
import 'package:local_spend/common/functions/save_logout.dart'; import 'package:local_spend/common/functions/save_logout.dart';
import 'package:local_spend/model/json/login_model.dart';
Future<LoginModel> requestLogoutAPI(BuildContext context) async { Future<bool> requestLogoutAPI() async {
final url = "https://dev.peartrade.org/api/logout"; saveLogout();
final url = "https://dev.localspend.co.uk/api/logout";
var token; var token;
@ -20,20 +20,10 @@ Future<LoginModel> requestLogoutAPI(BuildContext context) async {
"Token": token, "Token": token,
}; };
final response = await http.post( await http.post(
url, url,
body: json.encode(body), body: json.encode(body),
); );
if (response.statusCode == 200) { return true;
// debugPrint("Logout successful: " + response.body);
saveLogout();
return null;
} else {
// debugPrint("Logout unsuccessful: " + response.body);
saveLogout();
return null;
}
} }

View file

@ -23,7 +23,7 @@ class Receipt {
Future<LoginModel> submitReceiptAPI( Future<LoginModel> submitReceiptAPI(
BuildContext context, Receipt receipt) async { BuildContext context, Receipt receipt) async {
//var apiUrl = ConfigWrapper.of(context).apiKey; //var apiUrl = ConfigWrapper.of(context).apiKey;
final url = "https://dev.peartrade.org/api/upload"; final url = "https://dev.localspend.co.uk/api/upload";
SharedPreferences preferences = await SharedPreferences.getInstance(); SharedPreferences preferences = await SharedPreferences.getInstance();
@ -38,7 +38,6 @@ Future<LoginModel> submitReceiptAPI(
'street_name': receipt.street, 'street_name': receipt.street,
'postcode': receipt.postcode, 'postcode': receipt.postcode,
'town': receipt.town, 'town': receipt.town,
'session_key': preferences.get('LastToken'), 'session_key': preferences.get('LastToken'),
}; };
@ -61,13 +60,11 @@ Future<LoginModel> submitReceiptAPI(
context, context,
responseJson[0] == "" ? responseJson[0] : "Upload Successful", responseJson[0] == "" ? responseJson[0] : "Upload Successful",
"Transaction successfully submitted to server", "Transaction successfully submitted to server",
"OK" "OK");
);
return LoginModel.fromJson(responseJson); return LoginModel.fromJson(responseJson);
} else { } else {
final responseJson = json.decode(response.body); final responseJson = json.decode(response.body);
showDialogSingleButton( showDialogSingleButton(
context, context,
"Unable to Submit Receipt", "Unable to Submit Receipt",

View file

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

View file

@ -2,8 +2,7 @@ import 'package:flutter/material.dart';
final TextEditingController _feedbackController = TextEditingController(); final TextEditingController _feedbackController = TextEditingController();
void feedback( void feedback(BuildContext context) {
BuildContext context) {
// flutter defined function // flutter defined function
showDialog( showDialog(
context: context, context: context,
@ -15,12 +14,10 @@ void feedback(
children: <Widget>[ children: <Widget>[
Text("We would love to hear your thoughts."), Text("We would love to hear your thoughts."),
Text("\nThis section needs to be fixed."), Text("\nThis section needs to be fixed."),
Container( Container(
margin: const EdgeInsets.fromLTRB(0, 20, 0, 0), margin: const EdgeInsets.fromLTRB(0, 20, 0, 0),
width: 200, width: 200,
height: 70, height: 70,
child: new TextField( child: new TextField(
controller: _feedbackController, controller: _feedbackController,
decoration: InputDecoration( decoration: InputDecoration(

View file

@ -1,6 +1,7 @@
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'dart:async';
getToken() async { Future<String> getToken() async {
SharedPreferences preferences = await SharedPreferences.getInstance(); SharedPreferences preferences = await SharedPreferences.getInstance();
String getToken = preferences.getString("LastToken"); 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:local_spend/common/apifunctions/request_logout_api.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
logout(context) { void logout(context) {
requestLogoutAPI(context); _clearLoginDetails().then((_) {
requestLogoutAPI();
Navigator.of(context).pushReplacementNamed('/LoginPage'); Navigator.of(context).pushReplacementNamed('/LoginPage');
_clearLoginDetails(); });
} }
_clearLoginDetails() async { Future<void> _clearLoginDetails() async {
SharedPreferences preferences = await SharedPreferences.getInstance(); SharedPreferences preferences = await SharedPreferences.getInstance();
preferences.setString('username', ""); await preferences.setString('username', "");
preferences.setString('password', ""); await preferences.setString('password', "");
print("details cleared");
} }

View file

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

View file

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

View file

@ -4,6 +4,24 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class PlatformScaffold extends StatelessWidget { 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 Key key;
final PreferredSizeWidget appBar; final PreferredSizeWidget appBar;
final Widget body; final Widget body;
@ -18,23 +36,6 @@ class PlatformScaffold extends StatelessWidget {
final bool resizeToAvoidBottomPadding; final bool resizeToAvoidBottomPadding;
final bool primary; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Platform.isIOS return Platform.isIOS

View file

@ -0,0 +1,40 @@
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

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

View file

@ -2,13 +2,12 @@ import 'package:flutter/material.dart';
import 'package:local_spend/common/widgets/charts/time_series_simple.dart'; import 'package:local_spend/common/widgets/charts/time_series_simple.dart';
class TimeSeries extends StatelessWidget { class TimeSeries extends StatelessWidget {
final String chartDataName;
TimeSeries({ TimeSeries({
this.chartDataName, this.chartDataName,
}); });
final String chartDataName;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Container( return new Container(

View file

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

View file

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

View file

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

View file

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

View file

@ -8,9 +8,6 @@ import 'package:charts_flutter/flutter.dart' as charts;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class BucketingAxisScatterPlotChart extends StatelessWidget { class BucketingAxisScatterPlotChart extends StatelessWidget {
final List<charts.Series> seriesList;
final bool animate;
BucketingAxisScatterPlotChart(this.seriesList, {this.animate}); BucketingAxisScatterPlotChart(this.seriesList, {this.animate});
/// Creates a [ScatterPlotChart] with sample data and no transition. /// Creates a [ScatterPlotChart] with sample data and no transition.
@ -22,6 +19,8 @@ class BucketingAxisScatterPlotChart extends StatelessWidget {
); );
} }
final List<charts.Series> seriesList;
final bool animate;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -128,9 +127,9 @@ class BucketingAxisScatterPlotChart extends StatelessWidget {
/// Sample linear data type. /// Sample linear data type.
class LinearSales { class LinearSales {
LinearSales(this.year, this.revenueShare, this.radius);
final int year; final int year;
final double revenueShare; final double revenueShare;
final double radius; final double radius;
LinearSales(this.year, this.revenueShare, this.radius);
} }

View file

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

View file

@ -3,9 +3,6 @@ import 'package:charts_flutter/flutter.dart' as charts;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class SimpleTimeSeriesChart extends StatelessWidget { class SimpleTimeSeriesChart extends StatelessWidget {
final List<charts.Series> seriesList;
final bool animate;
SimpleTimeSeriesChart(this.seriesList, {this.animate}); SimpleTimeSeriesChart(this.seriesList, {this.animate});
/// Creates a [TimeSeriesChart] with sample data and no transition. /// Creates a [TimeSeriesChart] with sample data and no transition.
@ -17,6 +14,8 @@ class SimpleTimeSeriesChart extends StatelessWidget {
); );
} }
final List<charts.Series> seriesList;
final bool animate;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -53,8 +52,8 @@ class SimpleTimeSeriesChart extends StatelessWidget {
/// Sample time series data type. /// Sample time series data type.
class TimeSeriesSales { class TimeSeriesSales {
TimeSeriesSales(this.time, this.sales);
final DateTime time; final DateTime time;
final int sales; final int sales;
TimeSeriesSales(this.time, this.sales);
} }

View file

@ -62,8 +62,6 @@ class CustomCheckbox extends StatefulWidget {
assert(tristate || value != null), assert(tristate || value != null),
super(key: key); super(key: key);
final bool useTapTarget; final bool useTapTarget;
/// Whether this checkbox is checked. /// Whether this checkbox is checked.
@ -138,7 +136,8 @@ class CustomCheckbox extends StatefulWidget {
_CustomCheckboxState createState() => _CustomCheckboxState(); _CustomCheckboxState createState() => _CustomCheckboxState();
} }
class _CustomCheckboxState extends State<CustomCheckbox> with TickerProviderStateMixin { class _CustomCheckboxState extends State<CustomCheckbox>
with TickerProviderStateMixin {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context)); assert(debugCheckHasMaterial(context));
@ -147,25 +146,26 @@ class _CustomCheckboxState extends State<CustomCheckbox> with TickerProviderStat
Size size; Size size;
switch (widget.materialTapTargetSize ?? themeData.materialTapTargetSize) { switch (widget.materialTapTargetSize ?? themeData.materialTapTargetSize) {
case MaterialTapTargetSize.padded: 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; break;
case MaterialTapTargetSize.shrinkWrap: case MaterialTapTargetSize.shrinkWrap:
size = const Size(2 * kRadialReactionRadius, 2 * kRadialReactionRadius); size = const Size(2 * kRadialReactionRadius, 2 * kRadialReactionRadius);
break; break;
} }
Size noTapTargetSize = Size(CustomCheckbox.width, Size noTapTargetSize = Size(CustomCheckbox.width, CustomCheckbox.width);
CustomCheckbox.width);
final BoxConstraints additionalConstraints = final BoxConstraints additionalConstraints =
BoxConstraints.tight(widget BoxConstraints.tight(widget.useTapTarget ? size : noTapTargetSize);
.useTapTarget? size : noTapTargetSize);
return _CheckboxRenderObjectWidget( return _CheckboxRenderObjectWidget(
value: widget.value, value: widget.value,
tristate: widget.tristate, tristate: widget.tristate,
activeColor: widget.activeColor ?? themeData.toggleableActiveColor, activeColor: widget.activeColor ?? themeData.toggleableActiveColor,
checkColor: widget.checkColor ?? const Color(0xFFFFFFFF), 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, onChanged: widget.onChanged,
additionalConstraints: additionalConstraints, additionalConstraints: additionalConstraints,
vsync: this, vsync: this,
@ -174,8 +174,8 @@ class _CustomCheckboxState extends State<CustomCheckbox> with TickerProviderStat
} }
class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget { class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
const _CheckboxRenderObjectWidget({ const _CheckboxRenderObjectWidget(
Key key, {Key key,
@required this.value, @required this.value,
@required this.tristate, @required this.tristate,
@required this.activeColor, @required this.activeColor,
@ -184,9 +184,8 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
@required this.onChanged, @required this.onChanged,
@required this.vsync, @required this.vsync,
@required this.additionalConstraints, @required this.additionalConstraints,
this.useTapTarget = true this.useTapTarget = true})
: assert(tristate != null),
}) : assert(tristate != null),
assert(tristate || value != null), assert(tristate || value != null),
assert(activeColor != null), assert(activeColor != null),
assert(inactiveColor != null), assert(inactiveColor != null),
@ -259,8 +258,7 @@ class _RenderCheckbox extends RenderToggleable {
@override @override
set value(bool newValue) { set value(bool newValue) {
if (newValue == value) if (newValue == value) return;
return;
_oldValue = value; _oldValue = value;
super.value = newValue; super.value = newValue;
} }
@ -278,7 +276,8 @@ class _RenderCheckbox extends RenderToggleable {
RRect _outerRectAt(Offset origin, double t) { RRect _outerRectAt(Offset origin, double t) {
final double inset = 1.0 - (t - 0.5).abs() * 2.0; final double inset = 1.0 - (t - 0.5).abs() * 2.0;
final double size = _kEdgeSize - inset * _kStrokeWidth; 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); return RRect.fromRectAndRadius(rect, _kEdgeRadius);
} }
@ -288,7 +287,9 @@ class _RenderCheckbox extends RenderToggleable {
// As t goes from 0.0 to 0.25, animate from the inactiveColor to activeColor. // As t goes from 0.0 to 0.25, animate from the inactiveColor to activeColor.
return onChanged == null return onChanged == null
? inactiveColor ? 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. // White stroke used to paint the check and dash.
@ -303,7 +304,8 @@ class _RenderCheckbox extends RenderToggleable {
assert(t >= 0.0 && t <= 0.5); assert(t >= 0.0 && t <= 0.5);
final double size = outer.width; final double size = outer.width;
// As t goes from 0.0 to 1.0, gradually fill the outer RRect. // 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); canvas.drawDRRect(outer, inner, paint);
} }
@ -347,9 +349,11 @@ class _RenderCheckbox extends RenderToggleable {
final Canvas canvas = context.canvas; final Canvas canvas = context.canvas;
paintRadialReaction(canvas, offset, size.center(Offset.zero)); 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 AnimationStatus status = position.status;
final double tNormalized = status == AnimationStatus.forward || status == AnimationStatus.completed final double tNormalized =
status == AnimationStatus.forward || status == AnimationStatus.completed
? position.value ? position.value
: 1.0 - position.value; : 1.0 - position.value;
@ -366,12 +370,14 @@ class _RenderCheckbox extends RenderToggleable {
_initStrokePaint(paint); _initStrokePaint(paint);
final double tShrink = (t - 0.5) * 2.0; final double tShrink = (t - 0.5) * 2.0;
if (_oldValue == null || value == null) if (_oldValue == null || value == null) {
_drawDash(canvas, origin, tShrink, paint); _drawDash(canvas, origin, tShrink, paint);
else } else {
_drawCheck(canvas, origin, tShrink, paint); _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 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); canvas.drawRRect(outer, paint);
@ -379,17 +385,19 @@ class _RenderCheckbox extends RenderToggleable {
_initStrokePaint(paint); _initStrokePaint(paint);
if (tNormalized <= 0.5) { if (tNormalized <= 0.5) {
final double tShrink = 1.0 - tNormalized * 2.0; final double tShrink = 1.0 - tNormalized * 2.0;
if (_oldValue == true) if (_oldValue == true) {
_drawCheck(canvas, origin, tShrink, paint); _drawCheck(canvas, origin, tShrink, paint);
else } else {
_drawDash(canvas, origin, tShrink, paint); _drawDash(canvas, origin, tShrink, paint);
}
} else { } else {
final double tExpand = (tNormalized - 0.5) * 2.0; final double tExpand = (tNormalized - 0.5) * 2.0;
if (value == true) if (value == true) {
_drawCheck(canvas, origin, tExpand, paint); _drawCheck(canvas, origin, tExpand, paint);
else } else {
_drawDash(canvas, origin, tExpand, paint); _drawDash(canvas, origin, tExpand, paint);
} }
} }
} }
} }
}

View file

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

View file

@ -1,10 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'dart:async'; import 'dart:async';
import 'package:flutter/services.dart';
import 'package:local_spend/common/apifunctions/find_organisations.dart'; import 'package:local_spend/common/apifunctions/find_organisations.dart';
class FindOrganisations { class FindOrganisations {
TextField getSearchBar(TextEditingController controller, String hintText) { TextField getSearchBar(TextEditingController controller, String hintText) {
return TextField( return TextField(
controller: controller, controller: controller,
@ -20,12 +19,12 @@ class FindOrganisations {
Future<dynamic> _moreInfoDialog(context, Organisation organisation) { Future<dynamic> _moreInfoDialog(context, Organisation organisation) {
TextStyle informationTitleStyle = new TextStyle(fontSize: 16); TextStyle informationTitleStyle = new TextStyle(fontSize: 16);
TextStyle informationStyle = new TextStyle(fontSize: 16, fontWeight: FontWeight.bold); TextStyle informationStyle =
new TextStyle(fontSize: 16, fontWeight: FontWeight.bold);
return showDialog<Organisation>( return showDialog<Organisation>(
context: context, context: context,
barrierDismissible: true, barrierDismissible: true,
builder: (BuildContext context) { builder: (BuildContext context) {
return StatefulBuilder( return StatefulBuilder(
builder: (context, setState) { builder: (context, setState) {
@ -39,12 +38,10 @@ class FindOrganisations {
fontSize: 21, fontWeight: FontWeight.bold), fontSize: 21, fontWeight: FontWeight.bold),
), ),
), ),
Container( Container(
padding: EdgeInsets.symmetric(horizontal: 10), padding: EdgeInsets.symmetric(horizontal: 10),
child: Divider(), child: Divider(),
), ),
Container( Container(
width: MediaQuery.of(context).size.width, width: MediaQuery.of(context).size.width,
padding: EdgeInsets.symmetric(horizontal: 10), padding: EdgeInsets.symmetric(horizontal: 10),
@ -54,13 +51,15 @@ class FindOrganisations {
TableRow( TableRow(
children: [ children: [
Text("Street:", style: informationTitleStyle), Text("Street:", style: informationTitleStyle),
Text(organisation.streetName, style: informationStyle), Text(organisation.streetName,
style: informationStyle),
], ],
), ),
TableRow( TableRow(
children: [ children: [
Text("Postcode:", style: informationTitleStyle), Text("Postcode:", style: informationTitleStyle),
Text(organisation.postcode.toUpperCase(), style: informationStyle), Text(organisation.postcode.toUpperCase(),
style: informationStyle),
], ],
), ),
TableRow( TableRow(
@ -72,7 +71,6 @@ class FindOrganisations {
], ],
), ),
), ),
], ],
); );
}, },
@ -83,10 +81,10 @@ class FindOrganisations {
Future<Organisation> dialog(context) { Future<Organisation> dialog(context) {
bool _searchEnabled = false; bool _searchEnabled = false;
bool _orgsFetched = false;
TextEditingController searchBarText = new TextEditingController(); TextEditingController searchBarText = new TextEditingController();
var organisations = new Organisations(); var organisations = new Organisations();
var listTitle = "All Organisations"; var listTitle = "All Organisations";
// var organisationsList = organisations.getTestData();
var organisationsList = List<Organisation>(); var organisationsList = List<Organisation>();
Future<int> _submitSearch(String search) async { Future<int> _submitSearch(String search) async {
@ -94,78 +92,86 @@ class FindOrganisations {
listTitle = "Results for \'" + search + "\'"; listTitle = "Results for \'" + search + "\'";
var futureOrgs = await organisations.findOrganisations(search); var futureOrgs = await organisations.findOrganisations(search);
// futureOrgs.then((value) {
// debugPrint("There are " + futureOrgs.length.toString() +
// " payees matching the query \'" + search + "\'.");
organisationsList = futureOrgs; organisationsList = futureOrgs;
_searchEnabled = true; _searchEnabled = true;
return futureOrgs.length; return futureOrgs.length;
// });
} }
return showDialog<Organisation>( return showDialog<Organisation>(
context: context, context: context,
barrierDismissible: true, barrierDismissible: true,
builder: (BuildContext context) { builder: (BuildContext context) {
return StatefulBuilder( return StatefulBuilder(
builder: (context, setState) { builder: (context, setState) {
return SimpleDialog( return SimpleDialog(
children: <Widget>[ children: <Widget>[
Padding( Column(
padding: EdgeInsets.fromLTRB(20, 0, 0, 0), children: [
child: Row( Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Container( Container(
width: 140, padding: EdgeInsets.fromLTRB(20, 0, 0, 0),
width: 150,
height: 50, height: 50,
child: TextField( child: TextField(
autofocus: true,
controller: searchBarText, controller: searchBarText,
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Payee Name", hintText: "Payee Name",
), ),
onChanged: (value) { onChanged: (value) {
if (value.length > 0) { if (value.isNotEmpty) {
_searchEnabled = true; _searchEnabled = true;
} else { } else {
_searchEnabled = false; _searchEnabled = false;
} }
setState(() => {_searchEnabled}); setState(() => {_searchEnabled});
}, },
onSubmitted: (_) { onSubmitted: _searchEnabled
if (_searchEnabled) { ? ((_) {
var result = _submitSearch(searchBarText.text); SystemChannels.textInput
.invokeMethod('TextInput.hide');
var result =
_submitSearch(searchBarText.text);
result.then((_) { result.then((_) {
setState(() {}); setState(() {
_orgsFetched = true;
}); });
} });
}, })
: null,
), ),
), ),
Container( Container(
width: 80, width: 80,
padding: EdgeInsets.fromLTRB(20, 0, 0, 0), padding: EdgeInsets.fromLTRB(20, 0, 0, 0),
child: RaisedButton( child: RaisedButton(
onPressed: (() { onPressed: _searchEnabled
if (_searchEnabled) { ? (() {
var result = _submitSearch(searchBarText.text); SystemChannels.textInput
.invokeMethod('TextInput.hide');
var result =
_submitSearch(searchBarText.text);
result.then((_) { result.then((_) {
setState(() {}); setState(() {
_orgsFetched = true;
}); });
} });
}), })
: null,
child: Icon(Icons.search, color: Colors.white), child: Icon(Icons.search, color: Colors.white),
color: _searchEnabled ? Colors.blue : Colors.blue[200], color: Colors.blue,
// make inactive when search in progress as activity indicator
), ),
), ),
], ],
), ),
],
), ),
Column(
children: _orgsFetched
? [
Container( Container(
padding: EdgeInsets.fromLTRB(20, 20, 20, 0), padding: EdgeInsets.fromLTRB(20, 20, 20, 0),
child: Text( child: Text(
@ -174,18 +180,10 @@ class FindOrganisations {
fontSize: 23, fontWeight: FontWeight.bold), fontSize: 23, fontWeight: FontWeight.bold),
), ),
), ),
Container( Container(
padding: EdgeInsets.fromLTRB(10, 10, 10, 0), padding: EdgeInsets.fromLTRB(10, 10, 10, 0),
width: MediaQuery width: MediaQuery.of(context).size.width,
.of(context) height: MediaQuery.of(context).size.height * 0.67,
.size
.width,
height: MediaQuery
.of(context)
.size
.height * 0.67,
child: Material( child: Material(
shadowColor: Colors.transparent, shadowColor: Colors.transparent,
color: Colors.transparent, color: Colors.transparent,
@ -195,16 +193,21 @@ class FindOrganisations {
return Card( return Card(
child: ListTile( child: ListTile(
leading: Icon(Icons.person), leading: Icon(Icons.person),
title: Text(organisationsList[index].name, style: new TextStyle(fontSize: 18)), title: Text(organisationsList[index].name,
subtitle: Text(organisationsList[index].postcode.toUpperCase()), style: new TextStyle(fontSize: 18)),
subtitle: Text(organisationsList[index]
.postcode
.toUpperCase()),
// trailing: Icon(Icons.arrow_forward_ios), // trailing: Icon(Icons.arrow_forward_ios),
// onTap: _chosenOrg(organisationsList[index]), // onTap: _chosenOrg(organisationsList[index]),
onTap: () { onTap: () {
Navigator.of(context).pop(organisationsList[index]); Navigator.of(context)
.pop(organisationsList[index]);
}, },
onLongPress: () { onLongPress: () {
// show more details about the organisation in a new dialog // show more details about the organisation in a new dialog
var moreInfo = _moreInfoDialog(context, organisationsList[index]); var moreInfo = _moreInfoDialog(
context, organisationsList[index]);
moreInfo.whenComplete(null); moreInfo.whenComplete(null);
}, },
), ),
@ -213,12 +216,22 @@ class FindOrganisations {
), ),
), ),
), ),
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 // help button for if org not listed
// cancel and ok buttons // cancel and ok buttons
], ],
// ),
); );
}, },
); );

View file

@ -6,7 +6,6 @@ class PopupListView {
return showDialog<dynamic>( return showDialog<dynamic>(
context: context, context: context,
barrierDismissible: true, barrierDismissible: true,
builder: (BuildContext context) { builder: (BuildContext context) {
return SimpleDialog( return SimpleDialog(
title: Text(title), title: Text(title),
@ -16,7 +15,8 @@ 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>(); var dialogOptionsList = new List<SimpleDialogOption>();
for (var i = 0; i < options.length; i++) { for (var i = 0; i < options.length; i++) {

View file

@ -5,13 +5,13 @@ part 'config.g.dart';
@JsonSerializable(createToJson: false) @JsonSerializable(createToJson: false)
class Config { class Config {
final String env;
final bool production;
final String apiKey;
Config({this.env, this.production, this.apiKey}); Config({this.env, this.production, this.apiKey});
factory Config.fromJson(Map<String, dynamic> json) => _$ConfigFromJson(json); factory Config.fromJson(Map<String, dynamic> json) => _$ConfigFromJson(json);
final String env;
final bool production;
final String apiKey;
} }
class ConfigWrapper extends StatelessWidget { class ConfigWrapper extends StatelessWidget {

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

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

2
lib/env/dev.json vendored
View file

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

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

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

2
lib/env/prod.json vendored
View file

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

View file

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

View file

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

View file

@ -1,31 +0,0 @@
import 'package:flutter/material.dart';
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:local_spend/common/apifunctions/get_graph_data.dart';
class PresetChart extends StatefulWidget {
PresetChart({Key key}) : super(key: key);
@override
_PresetChartState createState() {
return _PresetChartState();
}
}
class _PresetChartState extends State<PresetChart> {
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return new Container(
);
}
}

View file

@ -0,0 +1,163 @@
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

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

View file

@ -7,8 +7,9 @@ import 'package:local_spend/common/platform/platform_scaffold.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:local_spend/common/widgets/labeled_checkbox.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 { class LoginPage extends StatefulWidget {
@override @override
@ -18,10 +19,10 @@ class LoginPage extends StatefulWidget {
} }
class LoginPageState extends State<LoginPage> { class LoginPageState extends State<LoginPage> {
final TextEditingController _emailController = TextEditingController(/*text: 'test@example.com'*/); // remove bool _isLoggingIn = false;
final TextEditingController _passwordController = TextEditingController(/*text: 'abc123'*/); // remove final TextEditingController _emailController = TextEditingController();
bool _saveLoginDetails = true; // I am extremely sorry for the placement of this variable final TextEditingController _passwordController = TextEditingController();
// it will be fixed soon I promise bool _saveLoginDetails = true;
FocusNode focusNode; // added so focus can move automatically FocusNode focusNode; // added so focus can move automatically
@ -32,7 +33,7 @@ class LoginPageState extends State<LoginPage> {
showDialogSingleButton( showDialogSingleButton(
context, context,
"Unable to reach your website.", "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"); "OK");
} }
} }
@ -53,7 +54,7 @@ class LoginPageState extends State<LoginPage> {
super.dispose(); super.dispose();
} }
_fillLoginDetails() async { void _fillLoginDetails() async {
SharedPreferences preferences = await SharedPreferences.getInstance(); SharedPreferences preferences = await SharedPreferences.getInstance();
var username = await preferences.get('username'); var username = await preferences.get('username');
@ -63,27 +64,27 @@ class LoginPageState extends State<LoginPage> {
_passwordController.text = await password; _passwordController.text = await password;
} }
_saveCurrentRoute(String lastRoute) async { void _saveCurrentRoute(String lastRoute) async {
SharedPreferences preferences = await SharedPreferences.getInstance(); SharedPreferences preferences = await SharedPreferences.getInstance();
await preferences.setString('LastPageRoute', lastRoute); await preferences.setString('LastPageRoute', lastRoute);
} }
login(String username, String password) async { void login(String username, String password) async {
SystemChannels.textInput.invokeMethod('TextInput.hide'); _isLoggingIn = true;
await SystemChannels.textInput.invokeMethod('TextInput.hide');
SharedPreferences preferences = await SharedPreferences.getInstance(); SharedPreferences preferences = await SharedPreferences.getInstance();
if (_saveLoginDetails) { if (_saveLoginDetails) {
await preferences.setString('username', username); await preferences.setString('username', username);
await preferences.setString('password', password); await preferences.setString('password', password);
print("details saved");
} else { } else {
await preferences.setString('username', ""); await preferences.setString('username', "");
await preferences.setString('password', ""); await preferences.setString('password', "");
print("details cleared");
} }
requestLoginAPI(context, username, await requestLoginAPI(context, username, password).then((value) {
password); _isLoggingIn = false;
});
} }
@override @override
@ -96,37 +97,25 @@ class LoginPageState extends State<LoginPage> {
} else { } else {
Navigator.of(context).pushReplacementNamed('/HomePage'); Navigator.of(context).pushReplacementNamed('/HomePage');
} }
return null;
}, },
child: PlatformScaffold( child: PlatformScaffold(
// drawer: BasicDrawer(), body: Stack(
// body: Container( children: [
// decoration: BoxDecoration(color: Colors.white), AnimatedBackground([Colors.lightBlue[50], Colors.lightBlue[50]],
// margin: const EdgeInsets.all(20), Colors.white, Alignment.topRight, Alignment.bottomLeft, 3),
// child: Padding( Container(
// 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.blue[50], Colors.white],
stops: [0,1],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
child: Container(
margin: EdgeInsets.fromLTRB(60, 30, 60, 0), margin: EdgeInsets.fromLTRB(60, 30, 60, 0),
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: Container( child: AnimatedContainer(
duration: Duration(seconds: 2),
margin: EdgeInsets.fromLTRB(15, 0, 15, 0), margin: EdgeInsets.fromLTRB(15, 0, 15, 0),
// alignment: FractionalOffset(0.5, 0.3), // not sure what this does ngl :/
decoration: BoxDecoration( decoration: BoxDecoration(
image: DecorationImage( image: DecorationImage(
image: AssetImage('assets/images/launch_image.png') image:
), AssetImage('assets/images/launch_image.png')),
), ),
), ),
), ),
@ -168,89 +157,70 @@ class LoginPageState extends State<LoginPage> {
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
onSubmitted: (_) { onSubmitted: (_) {
login( _emailController.text, login(_emailController.text, _passwordController.text);
_passwordController.text);
}, },
), ),
), ),
Container(
Padding( margin: EdgeInsets.fromLTRB(0.0, 40.0, 0.0, 30.0),
padding: EdgeInsets.fromLTRB(0.0, 40.0, 0.0, 30.0),
child : Material(
child: new Container(
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,
),
),
child : Material(
type: MaterialType.transparency,
child : InkWell(
onTap: () => login( _emailController.text, _passwordController.text),
child: new Container(
width: 100, width: 100,
height: 50, 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 Center(
child: new Text( child: new Text(
'GO', style: new TextStyle(fontSize: 18, color: Colors.white),), 'GO',
style: new TextStyle(
fontSize: 18, color: Colors.white),
), ),
), ),
), ),
), ),
),
),
),
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;
});
},
),*/
),
], ],
), ),
), ),
), ),
), ),
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;
});
},
),
),
],
),
),
],
),
),
); );
} }
} }

62
lib/pages/map_page.dart Normal file
View file

@ -0,0 +1,62 @@
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

@ -1,4 +1,3 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:local_spend/common/platform/platform_scaffold.dart'; import 'package:local_spend/common/platform/platform_scaffold.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -7,7 +6,7 @@ import 'package:url_launcher/url_launcher.dart';
import 'package:local_spend/common/functions/customAbout.dart' as custom; import 'package:local_spend/common/functions/customAbout.dart' as custom;
import 'package:local_spend/common/functions/showDialogTwoButtons.dart'; import 'package:local_spend/common/functions/showDialogTwoButtons.dart';
const URL = "https://flutter.io/"; const url = "https://flutter.io/";
const demonstration = false; const demonstration = false;
class MorePage extends StatefulWidget { class MorePage extends StatefulWidget {
@ -37,7 +36,7 @@ class MorePageState extends State<MorePage> {
focusNode.dispose(); //disposes focus node when form disposed focusNode.dispose(); //disposes focus node when form disposed
} }
_saveCurrentRoute(String lastRoute) async { void _saveCurrentRoute(String lastRoute) async {
SharedPreferences preferences = await SharedPreferences.getInstance(); SharedPreferences preferences = await SharedPreferences.getInstance();
await preferences.setString('LastPageRoute', lastRoute); await preferences.setString('LastPageRoute', lastRoute);
} }
@ -52,9 +51,9 @@ class MorePageState extends State<MorePage> {
} else { } else {
Navigator.of(context).pushReplacementNamed('/LoginPage'); Navigator.of(context).pushReplacementNamed('/LoginPage');
} }
return null;
}, },
child: PlatformScaffold( child: PlatformScaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.blue[400], backgroundColor: Colors.blue[400],
title: Text( title: Text(
@ -68,14 +67,11 @@ class MorePageState extends State<MorePage> {
centerTitle: true, centerTitle: true,
iconTheme: IconThemeData(color: Colors.black), iconTheme: IconThemeData(color: Colors.black),
), ),
body: Container( body: Container(
padding: EdgeInsets.fromLTRB(30.0, 0.0, 30.0, 0.0),
child: ListView( child: ListView(
children: <Widget>[ children: <Widget>[
Container( Container(
padding: EdgeInsets.fromLTRB(0.0,25,0.0,0.0), padding: EdgeInsets.fromLTRB(30.0, 25, 30.0, 0.0),
child: Text( child: Text(
"Local Spend Tracker", "Local Spend Tracker",
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -88,46 +84,70 @@ class MorePageState extends State<MorePage> {
), ),
Padding( Padding(
padding: EdgeInsets.fromLTRB(0.0, 25.0, 0.0, 0.0), padding: EdgeInsets.fromLTRB(30.0, 25.0, 30.0, 0.0),
child: Container( child: Container(
height: 65.0, height: 65.0,
child: RaisedButton( child: RaisedButton(
onPressed: () { onPressed: () {
custom.showAboutDialog( custom.showAboutDialog(
context: context, context: context,
applicationIcon: new Icon(Icons.receipt), applicationIcon: new Icon(Icons.receipt),
applicationName: "Local Spend Tracker", applicationName: "Local Spend Tracker",
children: <Widget>[ children: <Widget>[
Text("Pear Trading is a commerce company designed to register and monitor money circulating in the local economy."), Text("Pear Trading is a commerce company designed to register and monitor money circulating in the local economy.\n"),
Text("\nContact at test@example.com or +44 01524 64544"), Container(
margin: EdgeInsets.symmetric(horizontal: 10),
Padding( height: 35,
padding: EdgeInsets.fromLTRB(0,20,0,0), child: RaisedButton(
child: InkWell( onPressed: () => launch('http://www.peartrade.org'),
child: Text child: Text("Pear Trading",
('Developed by Shadowcat Systems',
style: TextStyle( style: TextStyle(
color: Colors.blue, color: Colors.white, fontSize: 18.0)),
color: Colors.green,
), ),
), ),
onTap: () => launch('https://shadow.cat/')
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
),
), ),
), ),
], ],
); ),
),
),
),
],
);
}, },
child: Text("ABOUT", child: Text("ABOUT",
style: style: TextStyle(color: Colors.white, fontSize: 22.0)),
TextStyle(color: Colors.white, fontSize: 22.0)),
color: Colors.blue, color: Colors.blue,
), ),
), ),
), ),
Padding( Padding(
padding: EdgeInsets.fromLTRB(0.0, 20.0, 0.0, 0.0), padding: EdgeInsets.fromLTRB(30.0, 20.0, 30.0, 0.0),
child: Container( child: Container(
height: 65.0, height: 65.0,
child: RaisedButton( child: RaisedButton(
@ -138,19 +158,17 @@ class MorePageState extends State<MorePage> {
"Are you sure you want to log out?", "Are you sure you want to log out?",
"Cancel", "Cancel",
"Logout", "Logout",
logout logout);
);
}, },
child: Text("LOGOUT", child: Text("LOGOUT",
style: style: TextStyle(color: Colors.white, fontSize: 22.0)),
TextStyle(color: Colors.white, fontSize: 22.0)),
color: Colors.red, color: Colors.red,
), ),
), ),
), ),
// Padding( // Padding(
// padding: EdgeInsets.fromLTRB(0.0, 20.0, 0.0, 0.0), // padding: EdgeInsets.fromLTRB(30.0, 20.0, 30.0, 0.0),
// child: Container( // child: Container(
// height: 65.0, // height: 65.0,
// child: RaisedButton( // child: RaisedButton(
@ -164,7 +182,6 @@ class MorePageState extends State<MorePage> {
// ), // ),
// ), // ),
// ), // ),
], ],
), ),
), ),

View file

@ -1,10 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:local_spend/common/platform/platform_scaffold.dart'; import 'package:local_spend/common/platform/platform_scaffold.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:local_spend/common/apifunctions/get_graph_data.dart';
import 'package:charts_flutter/flutter.dart' as charts;
const URL = "https://flutter.io/"; const url = "https://flutter.io/";
const demonstration = false; const demonstration = false;
class NewStatsPage extends StatefulWidget { class NewStatsPage extends StatefulWidget {
@ -15,7 +13,6 @@ class NewStatsPage extends StatefulWidget {
} }
class NewStatsPageState extends State<NewStatsPage> { class NewStatsPageState extends State<NewStatsPage> {
/// Graph types: /// Graph types:
/// - total_last_week /// - total_last_week
/// - avg_spend_last_week /// - avg_spend_last_week
@ -33,14 +30,13 @@ class NewStatsPageState extends State<NewStatsPage> {
super.dispose(); super.dispose();
} }
_saveCurrentRoute(String lastRoute) async { void _saveCurrentRoute(String lastRoute) async {
SharedPreferences preferences = await SharedPreferences.getInstance(); SharedPreferences preferences = await SharedPreferences.getInstance();
await preferences.setString('LastPageRoute', lastRoute); await preferences.setString('LastPageRoute', lastRoute);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return PlatformScaffold( return PlatformScaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.blue[400], backgroundColor: Colors.blue[400],
@ -55,11 +51,7 @@ class NewStatsPageState extends State<NewStatsPage> {
centerTitle: true, centerTitle: true,
iconTheme: IconThemeData(color: Colors.black), iconTheme: IconThemeData(color: Colors.black),
), ),
body: Container(),
body : Container(
),
); );
} }
} }

178
lib/pages/orgGraphs.dart Normal file
View file

@ -0,0 +1,178 @@
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>>
),
),
],
);
}
}

View file

@ -1,21 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:local_spend/common/platform/platform_scaffold.dart'; import 'package:local_spend/common/platform/platform_scaffold.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'dart:core'; import 'dart:async';
import 'package:flutter/cupertino.dart'; 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/apifunctions/find_organisations.dart';
import 'package:local_spend/common/widgets/organisations_dialog.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/submit_receipt_api.dart';
import 'package:local_spend/common/apifunctions/categories.dart'; import 'package:local_spend/common/apifunctions/categories.dart';
class Transaction { class Transaction {
DateTime date;
TextEditingController amount;
Organisation organisation;
String recurring;
bool isEssential;
String category;
Transaction( Transaction(
this.date, this.date,
this.amount, this.amount,
@ -24,6 +18,13 @@ class Transaction {
this.isEssential, this.isEssential,
this.category, this.category,
); );
DateTime date;
TextEditingController amount;
Organisation organisation;
String recurring;
bool isEssential;
String category;
} }
class ReceiptPage2 extends StatefulWidget { class ReceiptPage2 extends StatefulWidget {
@ -43,7 +44,7 @@ class ReceiptPage2State extends State<ReceiptPage2> {
"Uncategorised", "Uncategorised",
); );
_invalidDialog(context) { AlertDialog _invalidDialog(context) {
return AlertDialog( return AlertDialog(
title: new Text("Invalid data"), title: new Text("Invalid data"),
content: new Text( content: new Text(
@ -63,18 +64,7 @@ class ReceiptPage2State extends State<ReceiptPage2> {
return await getCategories(); return await getCategories();
} }
_submitReceipt(Transaction transaction) { void _submitReceipt(Transaction transaction) {
DateTime dt = new DateTime.now();
// sample transaction:
// {
// "transaction_type":1,
// "transaction_value":33,
// "purchase_time":"2019-08-12T11:06:00.000+01:00",
// "organisation_id":59661,
// "essential":false,
// "session_key":"C438432A-B775-11E9-8EE8-147589E69626"
// }
Receipt receipt = new Receipt(); Receipt receipt = new Receipt();
receipt.organisationName = transaction.organisation.name; receipt.organisationName = transaction.organisation.name;
receipt.street = transaction.organisation.streetName; receipt.street = transaction.organisation.streetName;
@ -94,36 +84,49 @@ class ReceiptPage2State extends State<ReceiptPage2> {
} }
receipt.amount = transaction.amount.text.toString(); receipt.amount = transaction.amount.text.toString();
receipt.time = DateFormat("yyyy-MM-dd'T'hh:mm':00.000+01:00'").format(transaction.date).toString(); receipt.time = DateFormat("yyyy-MM-dd'T'hh:mm':00.000+01:00'")
.format(transaction.date)
.toString();
receipt.essential = transaction.isEssential.toString(); receipt.essential = transaction.isEssential.toString();
submitReceiptAPI(context, receipt); submitReceiptAPI(context, receipt);
} }
List<String> _sampleRecurringOptions = new List<String>(7); List<String> _recurringOptions = new List<String>(7);
List<String> _categories = new List<String>(); List<String> _categories = new List<String>();
@override @override
Widget build(BuildContext context) { void initState() {
super.initState();
}
if (_categories.length == 0) { @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(); Future<List<String>> _futureCats = getCats();
_categories.add("Fetching categories..."); _categories.add("Fetching categories...");
_futureCats.then((value) { _futureCats.then((value) {
_categories = null;
_categories = value; _categories = value;
_categories.insert(0, "Uncategorised");
setState(() {}); setState(() {});
}); });
} }
_sampleRecurringOptions[0] = "None"; _recurringOptions[0] = "None";
_sampleRecurringOptions[1] = "Daily"; _recurringOptions[1] = "Daily";
_sampleRecurringOptions[2] = "Weekly"; _recurringOptions[2] = "Weekly";
_sampleRecurringOptions[3] = "Fortnightly"; _recurringOptions[3] = "Fortnightly";
_sampleRecurringOptions[4] = "Monthly"; _recurringOptions[4] = "Monthly";
_sampleRecurringOptions[5] = "Quarterly"; _recurringOptions[5] = "Quarterly";
_sampleRecurringOptions[6] = "Yearly"; // these will be difficult to fetch from server as they are coded into the site's rather than fetched _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( return PlatformScaffold(
appBar: AppBar( appBar: AppBar(
@ -138,18 +141,22 @@ class ReceiptPage2State extends State<ReceiptPage2> {
centerTitle: true, centerTitle: true,
iconTheme: IconThemeData(color: Colors.black), iconTheme: IconThemeData(color: Colors.black),
), ),
body: ListView( body: ListView(
children: <Widget>[ children: <Widget>[
// each CHILD has its own horizontal padding because if the listView has padding, Android's end-of-scroll animation // 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 // doesn't fit the screen properly and looks weird
Container( Container(
padding: const EdgeInsets.fromLTRB(15, 17, 0, 0), padding: EdgeInsets.fromLTRB(
MediaQuery.of(context).size.width * 0.025,
MediaQuery.of(context).size.height * 0.025,
0,
0.0),
child: Text( child: Text(
"Receipt Details", "Receipt Details",
style: TextStyle( style: TextStyle(
fontSize: 24, fontSize: 26,
color: Colors.grey[700], color: Colors.grey[700],
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@ -157,39 +164,51 @@ class ReceiptPage2State extends State<ReceiptPage2> {
), // "Receipt Details" title ), // "Receipt Details" title
Container( Container(
padding: EdgeInsets.fromLTRB(25,15,15.0,0.0), 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( child: Row(
children: <Widget>[ children: <Widget>[
Container( Container(
child: Text( child: Text(
"Date/Time", "Date/Time",
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: _fontSize,
color: Colors.black, color: Colors.black,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
width: 110, width: MediaQuery.of(context).size.width * 0.3,
), ),
Container( Container(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0), padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
height: 32.0, height: _widgetHeight,
width: MediaQuery.of(context).size.width * 0.6,
child: RaisedButton( child: RaisedButton(
onPressed: () { onPressed: () {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: (BuildContext builder) { builder: (BuildContext builder) {
return Container( return Container(
height: MediaQuery.of(context).copyWith().size.height / 3, height: MediaQuery.of(context)
.copyWith()
.size
.height /
3,
child: CupertinoDatePicker( child: CupertinoDatePicker(
initialDateTime: transaction.date.isAfter(DateTime.now()) initialDateTime:
transaction.date.isAfter(DateTime.now())
? DateTime.now() ? DateTime.now()
: transaction.date, : transaction.date,
onDateTimeChanged: (DateTime newDate) { onDateTimeChanged: (DateTime newDate) {
setState(() => { setState(() => {
newDate.isAfter(DateTime.now()) newDate.isAfter(DateTime.now())
? transaction.date = DateTime.now() ? transaction.date =
DateTime.now()
: transaction.date = newDate, : transaction.date = newDate,
}); });
}, },
@ -201,43 +220,59 @@ class ReceiptPage2State extends State<ReceiptPage2> {
}, },
child: Text( child: Text(
transaction.date == null transaction.date == null
? 'None set' ? 'None set.'
: transaction.date.year == DateTime.now().year : transaction.date.year != DateTime.now().year
? '${new DateFormat.MMMd().format(transaction.date)}' + ", " + '${new DateFormat.Hm().format(transaction.date)}' ? '${new DateFormat.MMMd().format(transaction.date)}' +
: '${new DateFormat.MMMd().format(transaction.date)}' + " " + transaction.date.year.toString() + ", " + '${new DateFormat.Hm().format(transaction.date)}', " " +
style: transaction.date.year.toString() +
TextStyle(color: Colors.white, fontSize: 18.0), " 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, color: Colors.blue,
), ),
), ),
], ],
), ),
),
), // Date/Time picker ), // Date/Time picker
Container( Container(
padding: EdgeInsets.fromLTRB(25,15,15.0,0.0), 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( child: Row(
children: <Widget>[ children: <Widget>[
Container( Container(
child: Text( child: Text(
"Payee", "Payee",
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: _fontSize,
color: Colors.black, color: Colors.black,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
width: 110, width: MediaQuery.of(context).size.width * 0.3,
), ),
Container( Container(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0), padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
height: 32.0, height: _widgetHeight,
width: MediaQuery.of(context).size.width * 0.6,
child: RaisedButton( child: RaisedButton(
onPressed: () { onPressed: () {
// var popupListView = new PopupListView();
// var dialog = popupListView.dialog(context, optionsList, "Choose Organization");
var organisations = new FindOrganisations(); var organisations = new FindOrganisations();
var orgDialog = organisations.dialog(context); var orgDialog = organisations.dialog(context);
orgDialog.then((organisation) { orgDialog.then((organisation) {
@ -255,52 +290,73 @@ class ReceiptPage2State extends State<ReceiptPage2> {
transaction.organisation.name == null transaction.organisation.name == null
? 'Find' ? 'Find'
: transaction.organisation.name.length > 14 : transaction.organisation.name.length > 14
? transaction.organisation.name.substring(0,12) + "..." ? transaction.organisation.name
.substring(0, 12) +
"..."
: transaction.organisation.name, : transaction.organisation.name,
style: style: TextStyle(
TextStyle(color: Colors.white, fontSize: 18.0), color: Colors.white, fontSize: _fontSizeButton),
), ),
color: Colors.blue, color: Colors.blue,
), ),
), ),
], ],
), ),
),
), // Organisation picker ), // Organisation picker
Container( Container(
padding: EdgeInsets.fromLTRB(25,15,15.0,0.0), 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( child: Row(
children: <Widget>[ children: <Widget>[
Container( Container(
child: Text( child: Text(
"Recurring", "Recurring",
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: _fontSize,
color: Colors.black, color: Colors.black,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
width: 110, width: MediaQuery.of(context).size.width * 0.3,
), ),
Container( Container(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0), padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
height: 32.0, height: _widgetHeight,
width: MediaQuery.of(context).size.width * 0.6,
child: RaisedButton( child: RaisedButton(
onPressed: () { onPressed: () {
transaction.recurring = _recurringOptions[0];
setState(() {});
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: (BuildContext builder) { builder: (BuildContext builder) {
return Container( return Container(
height: MediaQuery.of(context).copyWith().size.height / 3, height: MediaQuery.of(context)
.copyWith()
.size
.height /
3,
child: CupertinoPicker( child: CupertinoPicker(
backgroundColor: Colors.white, backgroundColor: Colors.white,
children: _sampleRecurringOptions.map((thisOption) => Text(thisOption)).toList(), children: _recurringOptions
.map((thisOption) => Text(thisOption,
style: TextStyle(fontSize: 30)))
.toList(),
onSelectedItemChanged: ((newValue) { onSelectedItemChanged: ((newValue) {
transaction.recurring = _sampleRecurringOptions[newValue]; transaction.recurring =
_recurringOptions[newValue];
setState(() {}); setState(() {});
}), }),
itemExtent: 32, magnification: 1.1,
useMagnifier: true,
itemExtent: 36,
), ),
); );
}); });
@ -309,50 +365,71 @@ class ReceiptPage2State extends State<ReceiptPage2> {
transaction.recurring == null transaction.recurring == null
? 'None' ? 'None'
: transaction.recurring, : transaction.recurring,
style: style: TextStyle(
TextStyle(color: Colors.white, fontSize: 18.0), color: Colors.white, fontSize: _fontSizeButton),
), ),
color: Colors.blue, color: Colors.blue,
), ),
), ),
], ],
), ),
),
), // Recurring picker ), // Recurring picker
Container( Container(
padding: EdgeInsets.fromLTRB(25,15,15.0,0.0), padding: EdgeInsets.fromLTRB(
MediaQuery.of(context).size.width * 0.05,
15,
MediaQuery.of(context).size.width * 0.05,
0.0),
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
Container( Container(
child: Text( child: Text(
"Category", "Category",
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: _fontSize,
color: Colors.black, color: Colors.black,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
width: 110, width: MediaQuery.of(context).size.width * 0.3,
), ),
Container( Container(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0), padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
height: 32.0, height: _widgetHeight,
width: MediaQuery.of(context).size.width * 0.6,
child: Tooltip(
message: "Category of transaction",
child: RaisedButton( child: RaisedButton(
onPressed: () { onPressed: () {
transaction.category = _categories[0];
setState(() {});
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: (BuildContext builder) { builder: (BuildContext builder) {
return Container( return Container(
height: MediaQuery.of(context).copyWith().size.height / 3, height: MediaQuery.of(context)
.copyWith()
.size
.height /
3,
child: CupertinoPicker( child: CupertinoPicker(
backgroundColor: Colors.white, backgroundColor: Colors.white,
children: _categories.map((thisOption) => Text(thisOption)).toList(), children: _categories
.map((thisOption) => Text(
thisOption,
style: TextStyle(fontSize: 30),
))
.toList(),
onSelectedItemChanged: ((newValue) { onSelectedItemChanged: ((newValue) {
transaction.category = _categories[newValue]; transaction.category =
_categories[newValue];
setState(() {}); setState(() {});
}), }),
itemExtent: 32, magnification: 1.1,
useMagnifier: true,
itemExtent: 36,
), ),
); );
}); });
@ -361,34 +438,41 @@ class ReceiptPage2State extends State<ReceiptPage2> {
transaction.category == null transaction.category == null
? 'None' ? 'None'
: transaction.category, : transaction.category,
style: style: TextStyle(
TextStyle(color: Colors.white, fontSize: 18.0), color: Colors.white, fontSize: _fontSizeButton),
), ),
color: Colors.blue, color: Colors.blue,
), ),
), ),
),
], ],
), ),
), // Category picker ), // Category picker
Container( Container(
padding: EdgeInsets.fromLTRB(25,15,15.0,0.0), 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( child: Row(
children: <Widget>[ children: <Widget>[
Container( Container(
child: Text( child: Text(
"Essential", "Essential",
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: _fontSize,
color: Colors.black, color: Colors.black,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
width: 95, width: MediaQuery.of(context).size.width * 0.3,
), ),
Container( Container(
height: 32.0, height: _widgetHeight,
width: MediaQuery.of(context).size.width * 0.6,
child: Checkbox( child: Checkbox(
value: transaction.isEssential, value: transaction.isEssential,
onChanged: ((value) { onChanged: ((value) {
@ -398,81 +482,120 @@ class ReceiptPage2State extends State<ReceiptPage2> {
), ),
], ],
), ),
),
), // Essential ), // Essential
Container( Container(
padding: EdgeInsets.fromLTRB(25,15,15.0,0.0), 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( child: Row(
children: <Widget>[ children: <Widget>[
Container( Container(
child: Text( child: Text(
"Amount", "Amount",
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: _fontSize,
color: Colors.black, color: Colors.black,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
width: 110, width: MediaQuery.of(context).size.width * 0.3,
), ),
Container( Container(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0), padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
height: 32.0, height: _widgetHeight,
width: 100, width: MediaQuery.of(context).size.width * 0.6,
child: TextField( child: TextField(
controller: transaction.amount, style: TextStyle(
decoration: InputDecoration( fontSize: _fontSize,
hintText: "0.00"
), ),
keyboardType: TextInputType.numberWithOptions(decimal: true, signed: true), textAlign: TextAlign.center,
controller: transaction.amount,
decoration: InputDecoration(hintText: "0.00"),
keyboardType: TextInputType.numberWithOptions(
decimal: true, signed: true),
), ),
), ),
], ],
), ),
),
), // Amount picker ), // Amount picker
Padding( Padding(
padding: EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 0.0), 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( child: Container(
height: 65.0, height: _widgetHeight * 1.7,
child: RaisedButton( child: ClipRRect(
onPressed: () { 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 { try {
if (transaction.amount.text == "" || transaction.organisation.name == null) { if (transaction.amount.text == "" ||
transaction.organisation.name == null) {
showDialog( showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return _invalidDialog(context); return _invalidDialog(context);
} });
);
} else { } else {
if (double.tryParse(transaction.amount.text) != null && double.tryParse(transaction.amount.text) > 0) { if (double.tryParse(
transaction.amount.text) !=
null &&
double.tryParse(transaction.amount.text) >
0) {
_submitReceipt(transaction); _submitReceipt(transaction);
} else { } else {
showDialog( showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return _invalidDialog(context); return _invalidDialog(context);
} });
);
} }
} }
} } catch (_) {
catch (_) {
showDialog( showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return _invalidDialog(context); return _invalidDialog(context);
} });
);
} }
}, },
child: Text("GO", ),
style: ),
TextStyle(color: Colors.white, fontSize: 22.0)), ],
color: Colors.blue, ),
),
),
), ),
), ),
), ),

View file

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

View file

@ -1,44 +1,25 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:local_spend/common/platform/platform_scaffold.dart'; import 'package:local_spend/common/platform/platform_scaffold.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:local_spend/common/functions/logout.dart'; import 'package:local_spend/pages/customerGraphs.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:local_spend/pages/orgGraphs.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/charts/time_series_simple.dart';
import 'package:local_spend/common/apifunctions/get_graph_data.dart';
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:local_spend/common/widgets/charts/chart_builder.dart';
const URL = "https://flutter.io/"; const url = "https://flutter.io/";
const demonstration = false; const demonstration = false;
class StatsPage extends StatefulWidget { class StatsPage extends StatefulWidget {
@override @override
State<StatefulWidget> createState() { 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(); return new StatsPageState();
} }
} }
class StatsPageState extends State<StatsPage> { class StatsPageState extends State<StatsPage> {
String userType = "-";
/// Graph types:
/// - total_last_week
/// - avg_spend_last_week
/// - total_last_month
/// - avg_spend_last_month
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 @override
void initState() { void initState() {
@ -51,36 +32,23 @@ class StatsPageState extends State<StatsPage> {
super.dispose(); super.dispose();
} }
_saveCurrentRoute(String lastRoute) async { void _saveCurrentRoute(String lastRoute) async {
SharedPreferences preferences = await SharedPreferences.getInstance(); SharedPreferences preferences = await SharedPreferences.getInstance();
await preferences.setString('LastPageRoute', lastRoute); await preferences.setString('LastPageRoute', lastRoute);
} }
Future<String> _getUserType() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
return await preferences.get('LastUserType');
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (userType == "-") {
// Initializing graphs: _getUserType().then((value) {
print(value);
if (!totalLastWeekGraph.loaded) { userType =
totalLastWeekGraph.setGraphData().then((_) { '${value[0].toUpperCase()}${value.substring(1)}'; // capitalises first letter
setState(() {});
});
}
if (!avgSpendLastWeekGraph.loaded) {
avgSpendLastWeekGraph.setGraphData().then((_) {
setState(() {});
});
}
if (!totalLastMonthGraph.loaded) {
totalLastMonthGraph.setGraphData().then((_) {
setState(() {});
});
}
if (!avgSpendLastMonth.loaded) {
avgSpendLastMonth.setGraphData().then((_) {
setState(() {}); setState(() {});
}); });
} }
@ -88,102 +56,37 @@ class StatsPageState extends State<StatsPage> {
return PlatformScaffold( return PlatformScaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.blue[400], backgroundColor: Colors.blue[400],
title: Text( title: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Statistics", "Statistics",
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
color: Colors.white, color: Colors.white,
), ),
), ),
// leading: BackButton(), Padding(padding: EdgeInsets.symmetric(horizontal: 4)),
Text(
userType,
style: TextStyle(
fontSize: 20,
color: Colors.white70,
),
),
],
),
centerTitle: true, centerTitle: true,
iconTheme: IconThemeData(color: Colors.black), iconTheme: IconThemeData(color: Colors.black),
), ),
body: Container( body: Container(
padding: EdgeInsets.fromLTRB(0, 0, 0, 0), padding: EdgeInsets.fromLTRB(0, 0, 0, 0),
child: ListView( child: (userType == "-"
children: <Widget>[ ? null
: (userType.toLowerCase() == "customer"
Container( ? CustomerGraphs()
padding: EdgeInsets.fromLTRB(0.0,17,0.0,0.0), : OrgGraphs())),
child : Text(
"Last Week's Total Spend",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 10),
height: 200,
child: totalLastWeekGraph.graph != null ? new charts.TimeSeriesChart(totalLastWeekGraph.graph) : Center(child: Text("Loading graph...")), //List<Series<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,
),
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 10),
height: 200,
child: avgSpendLastWeekGraph.graph != null ? new charts.TimeSeriesChart(avgSpendLastWeekGraph.graph) : Center(child: Text("Loading graph...")), //List<Series<dynamic, DateTime>>
),
Container(
padding: EdgeInsets.fromLTRB(0.0,17,0.0,0.0),
child : Text(
"Last Month's Spend",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 10),
height: 200,
child: totalLastMonthGraph.graph != null ? new charts.TimeSeriesChart(totalLastMonthGraph.graph) : Center(child: Text("Loading graph...")), //List<Series<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,
),
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 10),
height: 200,
child: avgSpendLastMonth.graph != null ? new charts.TimeSeriesChart(avgSpendLastMonth.graph) : Center(child: Text("Loading graph...")), //List<Series<dynamic, DateTime>>
),
],
),
), ),
); );
} }

View file

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

View file

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

View file

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