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.

71 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
Ben Goldsworthy 96508a0228 Upgrade resolvable dependencies 2020-07-29 21:48:40 +01:00
Ben Goldsworthy b7e23081e2 Upgrade to AndroidX 2020-07-29 21:47:40 +01:00
Ben Goldsworthy 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
Felix 1719a66e32 removed old file 2019-08-19 09:49:14 +01:00
Felix 3c1191ec54 fixed weird colors on nav bar 2019-08-19 09:41:11 +01:00
Felix 503cd3ca59 improvements to graphs 2019-08-16 16:56:46 +01:00
Felix fdb6a5c69b Graphs properly rendered and fetched 2019-08-16 16:44:24 +01:00
Felix 94150ddd76 better error catching 2019-08-16 14:23:03 +01:00
Felix eebc8aeee3 nicer error message 2019-08-16 14:22:06 +01:00
Felix 41bc274965 Added message for when the server is down 2019-08-16 14:21:21 +01:00
Felix 9b34dd370d added More Info dialog on long press of payee 2019-08-13 12:36:10 +01:00
Felix 2734131f08 fixing weird CupertinoPicker 2019-08-12 16:28:46 +01:00
Felix 8bdc413eab debugging graphs...
it's hard!!
2019-08-12 16:03:00 +01:00
Felix 3060a6d1f9 forgot to stage all the changes... 2019-08-12 15:18:01 +01:00
Felix cb75f1ff87 general fixes, login behaves slightly better 2019-08-12 15:17:50 +01:00
Felix 7d30a8fc94 receipt submits are now safe 2019-08-12 12:29:49 +01:00
Felix fc2add5636 implementing categories submit 2019-08-12 12:09:04 +01:00
Felix a7db1c528b making uploads safe 2019-08-12 11:54:13 +01:00
Felix 405a037a15 uploading receipt 2019-08-12 11:37:59 +01:00
Felix 4e67bfbbec 'Recurring' and 'Category' added 2019-08-12 11:19:37 +01:00
Felix ee682eef82 new organisation picker -actually- works now 2019-08-12 10:48:50 +01:00
Felix 9cba3e037c safer code 2019-08-12 10:42:17 +01:00
Felix 2218422df4 new organisation dialog submitting started 2019-08-12 10:33:02 +01:00
Felix 679fbd2f97 async organisations dialog works now 2019-08-12 10:15:36 +01:00
Felix fa481f839d fixed number keyboard
(also looking into UI fixes)
2019-08-09 16:45:58 +01:00
Felix 0996a1e252 organisations dialog improved 2019-08-09 14:32:47 +01:00
Felix 3dba00b0b6 literally nothing 2019-08-08 15:25:43 +01:00
Felix 1e40d78dcd organisations working sorta 2019-08-07 15:32:52 +01:00
72 changed files with 2817 additions and 1747 deletions

4
.gitignore vendored
View File

@ -234,4 +234,6 @@ fabric.properties
!**/ios/**/default.perspectivev3
!/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)
- [Cookbook: Useful Flutter samples](https://flutter.io/docs/cookbook)
* [Tech Stack](#tech-stack)
* [Features](#features)
* [Installation](#installation)
* [Configuration](#configuration)
* [Usage](#usage)
* [Testing](#testing)
* [Code Formatting](#code-formatting)
* [Documentation](#documentation)
* [Acknowledgments](#acknowledgements)
* [License](#license)
* [Contact](#contact)
For help getting started with Flutter, view our
[online documentation](https://flutter.io/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
## Technology Stack
## Environments
The mobile app. is written in [Dart](https://dart.dev/).
To build an apk from dev, use:
flutter build apk -t lib/main_dev.dart
| Technology | Description | Link |
|-------------|---------------------------------|---------------------|
| Flutter | Cross-platform mobile framework | [Link][flutter] |
## Links and Things
[flutter]: https://flutter.dev/
https://github.com/putraxor/flutter-login-ui
https://github.com/pbirdsall/medium_splash_tokenauth
## Features
## How to debug code
// debug
This mobile app. provides:
- user authentication; and
- transaction logging.
## Installation
1. Install [Flutter](https://flutter.dev/docs/get-started/install);
1. if this is your first Flutter project, install the [Flutter SDK](https://flutter.dev/docs/get-started/test-drive);
1. set up [your editor](https://flutter.dev/docs/get-started/editor):
- we recommend using [Android Studio](https://developer.android.com/studio).
1. add the line `flutter.sdk=⟨ path to Flutter SDK ⟩` to the file `android/local.properties`.
## Configuration
App. configuration settings are found in `pubspec.yaml`.
Build settings are found in the `android/` directory, in the `build.gradle`, `gradle.properties` and `settings.gradle` files.
## Usage
### Development
To activate debugging, add the following import statement:
```dart
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"
android {
compileSdkVersion 27
compileSdkVersion 28
lintOptions {
disable 'InvalidPackage'

View File

@ -13,11 +13,18 @@
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="local_spend"
android:label="SpendTracker"
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:theme="@style/LaunchTheme"
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
there is no splash screen (such as the default splash screen
defined in @style/LaunchTheme). -->
<!-- Specify that the launch screen should continue being displayed -->
<!-- until Flutter renders its first frame. -->
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background" />
<!-- Theme to apply as soon as Flutter begins rendering frames -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>

View File

@ -1,13 +1,6 @@
package uk.co.localspend.localspend;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
import io.flutter.embedding.android.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 -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/normal_background</item>
</style>
</resources>

View File

@ -1 +1,4 @@
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
# platform :ios, '9.0'
platform :ios, '9.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

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

View File

@ -163,6 +163,7 @@
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
ACE3DC71CDB8FA5537935604 /* [CP] Embed Pods Frameworks */,
E6C2807EE928990FB790046F /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@ -281,7 +282,7 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework",
"${PODS_ROOT}/../.symlinks/flutter/ios-profile/Flutter.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
@ -292,6 +293,24 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
E6C2807EE928990FB790046F /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh",
"${PODS_ROOT}/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMaps.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@ -390,6 +409,7 @@
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
@ -519,6 +539,7 @@
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
@ -546,6 +567,7 @@
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",

View File

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

View File

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

View File

@ -2,38 +2,9 @@ import 'dart:convert';
import 'dart:async';
import 'package:http/http.dart' as http;
import 'package:local_spend/common/functions/get_token.dart';
import 'package:flutter/material.dart';
class Category {
String name;
String index;
Category({
this.name,
this.index,
});
}
Future<List<DropdownMenuItem<String>>> getCategoriesList() async {
//TODO: Return a list of [String, String] where {1} is categoryName and {2} is categoryValue for request
var categoriesList = List<DropdownMenuItem>();
var categories = await getCategories();
categories.forEach((thisCategory) {
var thisMap = new DropdownMenuItem(
child: new Text(thisCategory.name),
value: thisCategory.index,
);
categoriesList.add(thisMap);
});
return categoriesList;
}
Future<List<Category>> getCategories() async { // confusing name
const url = "https://dev.peartrade.org/api/search/category";
Future<List<String>> getCategories() async {
const url = "https://dev.localspend.co.uk/api/search/category";
var token;
await getToken().then((result) {
@ -41,10 +12,10 @@ Future<List<Category>> getCategories() async { // confusing name
});
Map<String, String> body = {
"session_key":token,
"session_key": token,
};
final response = await http.post (
final response = await http.post(
url,
body: json.encode(body),
);
@ -53,7 +24,7 @@ Future<List<Category>> getCategories() async { // confusing name
if (response.statusCode == 200) {
//request successful
List<Category> categories = new List<Category>();
List<String> categories = new List<String>();
Map responseMap = json.decode(response.body);
var categoriesJSON = responseMap['categories'];
@ -64,14 +35,12 @@ Future<List<Category>> getCategories() async { // confusing name
// print(categoriesJSON['11']); // prints "Banana"
int i = 1; // starts on 1. that was annoying to debug!
int i = 1; // starts on 1. that was annoying to debug!
while (true) {
if (categoriesJSON[i.toString()] != null) {
// print("Iteration " + i.toString());
// print(categoriesJSON[i.toString()]);
categories.add(new Category(
name: categoriesJSON[i.toString()], index: i.toString()));
categories.add(categoriesJSON[i.toString()]);
// print(categories.last);
i++;
} else {
@ -90,10 +59,10 @@ Future<List<Category>> getCategories() async { // confusing name
// print(categories[10].name.toString()); // prints "Banana"
return categories; // categories is List<Category>
return categories; // categories is List<Category>
// Category is defined at the top^^
} else {
// not successful
return null;
}
}
}

View File

@ -1,17 +1,9 @@
import 'dart:convert';
import 'dart:async';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:local_spend/common/functions/get_token.dart';
class Organisation {
var id = 0;
var name = "";
var postcode = "";
var streetName = ""; //street_name
var town = "";
Organisation(
this.id,
this.name,
@ -20,60 +12,74 @@ class Organisation {
this.town,
);
var id = 0;
var name = "";
var postcode = "";
var streetName = ""; //street_name
var town = "";
}
List<Organisation> jsonToOrganisations(String json) {
Map decoded = jsonDecode(json);
List<dynamic> validated = decoded['validated'];
List<Map> organisationsMaps = new List<Map>();
validated.forEach((element) => organisationsMaps.add(element));
List<Organisation> organisations = new List<Organisation>();
class Organisations {
List<Organisation> getTestData() {
var numItems = 10;
var itemsList = new List<Organisation>();
for (var i = 0; i < organisationsMaps.length; i++) {
final params = organisationsMaps[i].values.toList();
for (int i = 0; i < numItems; i++) {
itemsList.add(new Organisation(i, "Payee " + (i + 1).toString(),
"tee hee hee", "yeet street", "Robloxia"));
}
var newOrganisation = new Organisation(
params[0].toInt(),
params[1].toString(),
params[2].toString(), // oof
params[3].toString(), // this could be improved...
params[4].toString(),
return itemsList;
}
List<Organisation> _jsonToOrganisations(String json) {
Map decoded = jsonDecode(json);
List<dynamic> validated = decoded['validated'];
List<Map> organisationsMaps = new List<Map>();
validated.forEach((element) => organisationsMaps.add(element));
List<Organisation> organisations = new List<Organisation>();
for (var i = 0; i < organisationsMaps.length; i++) {
final params = organisationsMaps[i].values.toList();
var newOrganisation = new Organisation(
params[0].toInt(),
params[1].toString(),
params[2].toString(), // oof
params[3].toString(), // this could be improved...
params[4].toString(),
);
organisations.add(newOrganisation);
}
return organisations;
}
Future<List<Organisation>> findOrganisations(String search) async {
final url = "https://dev.localspend.co.uk/api/search";
var token;
await getToken().then((result) {
token = result;
});
Map<String, String> body = {
"search_name": search,
"session_key": token,
};
final response = await http.post(
url,
body: json.encode(body),
);
organisations.add(newOrganisation);
if (response.statusCode == 200) {
//request successful
return _jsonToOrganisations(response.body);
} else {
// not successful
return null;
}
}
return organisations;
}
Future<List<Organisation>> findOrganisations(String search) async {
final url = "https://dev.peartrade.org/api/search";
var token;
await getToken().then((result) {
token = result;
});
Map<String, String> body = {
"search_name":search,
"session_key":token,
};
final response = await http.post (
url,
body: json.encode(body),
);
if (response.statusCode == 200) {
//request successful
return jsonToOrganisations(response.body);
} else {
// not successful
return null;
}
}
class OrganizationController extends TextEditingController {
Organisation organisation;
}

View File

@ -4,25 +4,154 @@ import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:charts_flutter/flutter.dart' as charts;
/// Customer graph types: https://dev.localspend.co.uk/api/v1/customer/graphs
/// - total_last_week
/// - avg_spend_last_week
/// - total_last_month
/// - avg_spend_last_month
/// Organisations' graphs types: to fetch, POST to https://dev.localspend.co.uk/api/stats/[name] as {"session_key":"[boop beep]"}
/// - organisations_all : organisation
/// - pies : organisation/pies
/// - snippets : organisation/snippets
/// - graphs : organisation/graphs
/// - {"graph":"customers_last_7_days","session_key":"[bleep]"}
/// - {"graph":"customers_last_30_days","session_key":"[blah]"}
/// - {"graph":"sales_last_7_days","session_key":"[bloop]"}
/// - {"graph":"sales_last_7_days","session_key":"[reee]"}
/// - {"graph":"purchases_last_7_days","session_key":"[yee]"}
/// - {"graph":"purchases_last_30_days","session_key":"[yah]"}
/// - {"graph":"purchases_all;","session_key":"[kappa]"} // I don't think this one works
///
/// HTTP POST request sample:
/// {"graph":"total_last_week","session_key":"blahblahblah"}
class OrganisationGraph {
OrganisationGraph(this.chartType, {this.graphsType = ""});
String graphsType =
""; // type of graph, eg customers_last_7_days, sales_last_30_days, purchases_last_30_days etc
String
chartType; // type of chart, eg organisations_all, pies, snippets or graphs
List<charts.Series<TimeSeriesCustomersOrSales, DateTime>> graph;
List<TimeSeriesCustomersOrSales> cachedData;
bool loaded = false;
Future<void> getGraphData() async {
if (loaded) {
this.graph = [
new charts.Series<TimeSeriesCustomersOrSales, DateTime>(
id: 'Chart',
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
domainFn: (TimeSeriesCustomersOrSales spend, _) => spend.time,
measureFn: (TimeSeriesCustomersOrSales spend, _) =>
spend.numberOfStuff,
data: cachedData,
)
];
return null;
}
String url = "https://dev.localspend.co.uk/api/v1/organisation/";
if (!(this.chartType == "organisations_all")) {
url += this.chartType;
}
SharedPreferences preferences = await SharedPreferences.getInstance();
Map<String, String> body;
if (this.chartType == "graphs") {
body = {
'graph': this.graphsType,
'session_key': preferences.get('LastToken'),
};
} else {
body = {
'session_key': preferences.get('LastToken'),
};
}
print(url);
print(json.encode(body).toString());
final response = await http.post(
url,
body: json.encode(body),
);
try {
if (response.statusCode == 200) {
final responseJson = jsonDecode(response.body);
final List<dynamic> labels = responseJson['graph']['labels'];
final List<dynamic> data = responseJson['graph']['data'];
List<TimeSeriesCustomersOrSales> graphDataList =
new List<TimeSeriesCustomersOrSales>();
for (int i = 0; i < labels.length; i++) {
graphDataList.add(new TimeSeriesCustomersOrSales(
data[i] * 1.00, DateTime.parse(labels[i])));
}
this.cachedData = graphDataList;
this.loaded = true;
this.graph = [
new charts.Series<TimeSeriesCustomersOrSales, DateTime>(
id: 'Chart',
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
domainFn: (TimeSeriesCustomersOrSales spend, _) => spend.time,
measureFn: (TimeSeriesCustomersOrSales spend, _) =>
spend.numberOfStuff,
data: graphDataList,
)
];
return this.graph;
} else {
final errorMessage = json.decode(response.body)['message'];
print("Error: " + errorMessage);
this.graph = null;
}
} catch (error) {
print(error.toString());
}
}
}
class GraphData {
List<charts.Series> data = new List<charts.Series>();
GraphData(
this.chartType,
);
Future<List<charts.Series>> getGraphData(String graphType) async {
/// Graph types:
/// - 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"}
var chartType;
List<charts.Series<dynamic, DateTime>> graph;
charts.Series<dynamic, DateTime> dataSeries = new charts.Series<dynamic, DateTime>();
final url = "https://dev.peartrade.org/api/v1/customer/graphs";
List<TimeSeriesSpend> cachedData;
bool loaded = false;
Future<List<charts.Series<dynamic, DateTime>>> setGraphData() async {
if (loaded) {
this.graph = [
new charts.Series<TimeSeriesSpend, DateTime>(
id: 'Spend',
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
domainFn: (TimeSeriesSpend spend, _) => spend.time,
measureFn: (TimeSeriesSpend spend, _) => spend.spend,
data: cachedData,
)
];
return null;
}
final url = "https://dev.localspend.co.uk/api/v1/customer/graphs";
SharedPreferences preferences = await SharedPreferences.getInstance();
Map<String, String> body = {
'graph': graphType,
'graph': this.chartType,
'session_key': preferences.get('LastToken'),
};
@ -35,25 +164,79 @@ class GraphData {
final responseJson = jsonDecode(response.body);
final List<dynamic> labels = responseJson['graph']['labels'];
final List<dynamic> data = responseJson['graph']['data'];
// final List<String> bounds = responseJson['graph']['bounds']; // why is this even returned?
/*
final data = [
new TimeSeriesSales(new DateTime(2017, 9, 19), 5),
new TimeSeriesSales(new DateTime(2017, 9, 26), 25),
new TimeSeriesSales(new DateTime(2017, 10, 3), 100),
new TimeSeriesSales(new DateTime(2017, 10, 10), 75),
];
*/
List<TimeSeriesSpend> timeSeriesSpendList = new List<TimeSeriesSpend>();
for (int i = 0; i < labels.length; i++) {
print(DateTime.parse(labels[i]));
timeSeriesSpendList.add(new TimeSeriesSpend(i, DateTime(i)));
// timeSeriesSpendList.add(new TimeSeriesSpend(data[i], 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());
}
cachedData = timeSeriesSpendList;
loaded = true;
this.graph = [
new charts.Series<TimeSeriesSpend, DateTime>(
id: 'Spend',
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
domainFn: (TimeSeriesSpend spend, _) => spend.time,
measureFn: (TimeSeriesSpend spend, _) => spend.spend,
data: timeSeriesSpendList,
)
];
return this.graph;
} else {
final errorMessage = json.decode(response.body)['message'];
print("Error: " + errorMessage);
this.graph = null;
return null;
}
}
Future<List<charts.Series<dynamic, DateTime>>> getGraphData() async {
if (loaded == true) {
return [
new charts.Series<TimeSeriesSpend, DateTime>(
id: 'Spend',
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
domainFn: (TimeSeriesSpend spend, _) => spend.time,
measureFn: (TimeSeriesSpend spend, _) => spend.spend,
data: cachedData,
)
];
}
final url = "https://dev.localspend.co.uk/api/v1/customer/graphs";
SharedPreferences preferences = await SharedPreferences.getInstance();
Map<String, String> body = {
'graph': this.chartType,
'session_key': preferences.get('LastToken'),
};
final response = await http.post(
url,
body: json.encode(body),
);
if (response.statusCode == 200) {
final responseJson = jsonDecode(response.body);
final List<dynamic> labels = responseJson['graph']['labels'];
final List<dynamic> data = responseJson['graph']['data'];
List<TimeSeriesSpend> timeSeriesSpendList = new List<TimeSeriesSpend>();
for (int i = 0; i < labels.length; i++) {
timeSeriesSpendList.add(
new TimeSeriesSpend(data[i] * 1.00, DateTime.parse(labels[i])));
// print(timeSeriesSpendList[i].time.toString() + " : " + timeSeriesSpendList[i].spend.toString());
}
cachedData = timeSeriesSpendList;
loaded = true;
return [
new charts.Series<TimeSeriesSpend, DateTime>(
id: 'Spend',
@ -63,18 +246,6 @@ class GraphData {
data: timeSeriesSpendList,
)
];
/*
new charts.Series<TimeSeriesSales, DateTime>(
id: 'Sales',
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
domainFn: (TimeSeriesSales sales, _) => sales.time,
measureFn: (TimeSeriesSales sales, _) => sales.sales,
data: data,
)*/
// print(labels[5]);
// print(data[5]);
} else {
final errorMessage = json.decode(response.body)['message'];
print("Error: " + errorMessage);
@ -82,14 +253,18 @@ class GraphData {
return null;
}
}
}
class TimeSeriesSpend {
final DateTime time;
final int spend;
TimeSeriesSpend(this.spend, this.time);
final DateTime time;
final double spend;
}
class TimeSeriesCustomersOrSales {
TimeSeriesCustomersOrSales(this.numberOfStuff, this.time);
final DateTime time;
final double numberOfStuff;
}

View File

@ -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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,6 +4,24 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class PlatformScaffold extends StatelessWidget {
PlatformScaffold(
{this.key,
this.appBar,
this.body,
this.floatingActionButton,
this.floatingActionButtonLocation,
this.floatingActionButtonAnimator,
this.persistentFooterButtons,
this.drawer,
this.endDrawer,
this.bottomNavigationBar,
this.backgroundColor,
this.resizeToAvoidBottomPadding = true,
this.primary = true})
: assert(primary != null),
super(key: key);
@override
final Key key;
final PreferredSizeWidget appBar;
final Widget body;
@ -18,23 +36,6 @@ class PlatformScaffold extends StatelessWidget {
final bool resizeToAvoidBottomPadding;
final bool primary;
PlatformScaffold(
{this.key,
this.appBar,
this.body,
this.floatingActionButton,
this.floatingActionButtonLocation,
this.floatingActionButtonAnimator,
this.persistentFooterButtons,
this.drawer,
this.endDrawer,
this.bottomNavigationBar,
this.backgroundColor,
this.resizeToAvoidBottomPadding: true,
this.primary: true})
: assert(primary != null),
super(key: key);
@override
Widget build(BuildContext context) {
return Platform.isIOS

View File

@ -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';
class DonutAutoLabelChart extends StatelessWidget {
final List<charts.Series> seriesList;
final bool animate;
DonutAutoLabelChart(this.seriesList, {this.animate});
/// Creates a [PieChart] with sample data and no transition.
@ -18,6 +15,8 @@ class DonutAutoLabelChart extends StatelessWidget {
);
}
final List<charts.Series> seriesList;
final bool animate;
@override
Widget build(BuildContext context) {
@ -67,8 +66,8 @@ class DonutAutoLabelChart extends StatelessWidget {
/// Sample linear data type.
class LinearSales {
LinearSales(this.key, this.sales);
final String key;
final int sales;
LinearSales(this.key, this.sales);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,9 +3,6 @@ import 'package:charts_flutter/flutter.dart' as charts;
import 'package:flutter/material.dart';
class SimpleTimeSeriesChart extends StatelessWidget {
final List<charts.Series> seriesList;
final bool animate;
SimpleTimeSeriesChart(this.seriesList, {this.animate});
/// 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
Widget build(BuildContext context) {
@ -53,8 +52,8 @@ class SimpleTimeSeriesChart extends StatelessWidget {
/// Sample time series data type.
class TimeSeriesSales {
TimeSeriesSales(this.time, this.sales);
final DateTime time;
final int sales;
TimeSeriesSales(this.time, this.sales);
}
}

View File

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

View File

@ -1,25 +0,0 @@
import 'package:flutter/material.dart';
import 'package:local_spend/common/apifunctions/categories.dart';
import 'dart:async';
Future<List<DropdownMenuItem>> getDropDownItems(String type) async {
List<DropdownMenuItem<String>> items = new List<DropdownMenuItem<String>>();
// if (type == "categories") {
var categories = await getCategories();
categories.forEach((thisValue) {
items.add(
new DropdownMenuItem(
child: new Text(thisValue.name),
value: thisValue.index,
key: Key(thisValue.index),
),
);
});
print("oof");
return items;
// }
}

View File

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

View File

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

View File

@ -6,7 +6,6 @@ class PopupListView {
return showDialog<dynamic>(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return SimpleDialog(
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>();
for (var i = 0; i < options.length; i++) {
@ -33,4 +33,4 @@ class PopupListView {
return dialogOptionsList;
}
}
}

View File

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

2
lib/env/dev.dart vendored
View File

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

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

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

2
lib/env/dev.json vendored
View File

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

2
lib/env/prod.dart vendored
View File

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

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

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

2
lib/env/prod.json vendored
View File

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

View File

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

View File

@ -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

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

View File

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

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,20 +11,16 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:local_spend/main.dart';
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.
await tester.pumpWidget(MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
// Tap the GO button and trigger a frame.
await tester.tap(find.byKey(Key("goButton")));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
// Verify that the dialog has shown
// expect(find.text('GO'), findsNothing);
// expect(find.text('Invalid data'), findsOneWidget);
});
}