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.

86 commits

Author SHA1 Message Date
Rumperuu
ceafbb279f Fix typo 2021-03-20 09:43:18 +00:00
Rumperuu
9381458d79 Update readme 2021-03-20 09:40:50 +00:00
Tom Bloor
203d375aa4
Merge pull request #9 from Pear-Trading/fix/update
Merge `felix/main` branch into `development`, whilst also updating it
2020-08-05 14:04:43 +01:00
Rumperuu
5f53932995
Merge branch 'development' into fix/update 2020-07-29 21:55:02 +01:00
96508a0228 Upgrade resolvable dependencies 2020-07-29 21:48:40 +01:00
b7e23081e2 Upgrade to AndroidX 2020-07-29 21:47:40 +01:00
2d462788b7 Upgrade to new Android platform-side wrappers 2020-07-29 21:47:11 +01:00
Rumperuu
f2ecedd5c5
Update README.md 2020-07-29 15:12:00 +01:00
Rumperuu
efa74e2207
Update README.md 2020-07-29 15:11:33 +01:00
Felix
2d741e61c7 about page 2019-09-02 17:12:19 +01:00
Felix
a512549778 Maps, optimization, testing, code cleanup 2019-09-02 12:57:14 +01:00
Felix
c6e69b037b profile mode, no changes 2019-08-27 17:00:19 +01:00
Felix
96fdc01eea logout works properly 2019-08-27 12:18:02 +01:00
Felix
204cd74544 pickers now reset on opening
not sure if this looks too great
2019-08-27 11:50:27 +01:00
Felix
c6f6917226 Revert "that's a strange bug indeed"
This reverts commit 7aeccb46cf.
2019-08-27 11:41:08 +01:00
Felix
7aeccb46cf that's a strange bug indeed 2019-08-27 11:39:20 +01:00
Felix
ac82ec4d48 most of the bugs are just me being silly ngl 2019-08-27 11:37:13 +01:00
Felix
ece2c62762 i'm so stupid ughhh 2019-08-27 11:22:19 +01:00
Felix
f4a2e6ca7a save before debugging marathon 2019-08-27 11:00:57 +01:00
Felix
030f482a54 categories still broken 2019-08-23 17:52:48 +01:00
Felix
62d50f86a8 (profile mode) 2019-08-21 17:06:53 +01:00
Felix
d8ecd50acd progress indicators 2019-08-21 16:59:08 +01:00
Felix
7553ce25f8 organisations graphs
save login is now somehow broken 🧐
2019-08-21 16:50:26 +01:00
Felix
55310068ea Code is B-E-A-U-TIFUL! All complies to standards.
Complies to flutter/dart 'pedantic' coding standards (their name, not mine)
2019-08-21 14:53:52 +01:00
Felix
19021a9a09 maps api implementation started 2019-08-21 12:35:21 +01:00
Felix
cf6753363c lots of UI and some improvements to logic 2019-08-21 10:16:46 +01:00
Felix
68102ea628 orgs dialog button disables/enables properly 2019-08-20 16:37:32 +01:00
Felix
ebf09413fd fixed slow rebuilding slightly 2019-08-20 16:08:11 +01:00
Felix
f5d2e991a8 ui improvements 2019-08-20 16:01:56 +01:00
Felix
28cd64aaee i need to fix these pickers 2019-08-20 14:13:01 +01:00
Felix
49038e9adc consistency in ui 2019-08-20 14:06:39 +01:00
Felix
d7bcb29eda made UI more responsive to screen size 2019-08-20 14:04:31 +01:00
Felix
84c2fb1f2a button is less square 2019-08-20 13:58:53 +01:00
Felix
231ed2c9df many UI improvements and subtle tweaks 2019-08-20 13:54:45 +01:00
Felix
9a5bfbaaf0 some info added to orgs dialog 2019-08-19 15:47:28 +01:00
Felix
cda3fc57e9 fix broken dialog 2019-08-19 15:36:41 +01:00
Felix
e44687fb6b ui, save before experiment 2019-08-19 15:33:08 +01:00
Felix
b4c93ff80d writing functions to get org graph data 2019-08-19 15:23:58 +01:00
Felix
6cd65b8ac6 implementing organisation/customer-specific graphs 2019-08-19 15:02:02 +01:00
Felix
05a57e8203 fixing Dispose() errors
some widgets aren't loading and disposing properly
2019-08-19 13:50:30 +01:00
Felix
68a33c53e9 animation, tooltips, ui sizing fixed 2019-08-19 12:36:12 +01:00
Felix
abbd0a7170 tooltips added to graphs 2019-08-19 11:58:26 +01:00
Felix
d0defec7d6 testing has commenced
adding accessibility options now
2019-08-19 11:55:09 +01:00
Felix
ff7cdf4907 let the testing begin! 2019-08-19 11:43:53 +01:00
Felix
2e1b74a222 dialog looks slightly better 2019-08-19 11:19:41 +01:00
Felix
aa40a5c926 removed broken reference 2019-08-19 10:05:07 +01:00
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
Felix
aa6de5559a more 2019-08-07 14:02:36 +01:00
Felix
60b1c5b10c added 'favourites' 2019-08-07 13:22:14 +01:00
Felix
eaf7a06f52 UI looking FANTASTIC
new organisations dialog added with very sexy listView
2019-08-07 13:15:15 +01:00
Felix
59f69b102f Layout fixed 2019-08-05 12:24:40 +01:00
Felix
e742badcc8 'amount' button
it doesn't actually function yet, but it looks pretty!
2019-08-05 12:11:40 +01:00
Felix
fa7cf8ffa0 Date/time picker added, receipt page 2.0 looking good! 2019-08-05 11:44:03 +01:00
Felix
5adb7ae902 The rebuild has begun! 2019-08-05 09:33:39 +01:00
Felix
755893ee10 just gonna redo the whole receipt page
this is because the code for it is now completely messy and impossible to follow (yeah, my fault) and I need to start remembering how to code properly again after two weeks
2019-08-05 09:27:10 +01:00
Felix
525a092f40
dropdownbutton still not working fully
...but it works a bit more than before!
2019-07-19 15:15:04 +01:00
Felix
2e0802ac73
save before experiment, broken 2019-07-19 12:06:02 +01:00
Felix
1b207c1ce6
save
also, two exclamation marks have been added (very important)
2019-07-18 16:47:58 +01:00
Felix
d9abbbd0e7
Graph objectification 2019-07-18 16:38:12 +01:00
Felix
9a09154332
save before checking-out previous 2019-07-18 14:28:34 +01:00
Felix
d7625f7b88
implementing API graphs and getting frustrated at async stuff 2019-07-18 12:05:09 +01:00
Felix
bf1046c4e5
getting graph data from api
not sure if this is needed, was suggested though
2019-07-18 10:26:37 +01:00
73 changed files with 3061 additions and 1543 deletions

4
.gitignore vendored
View file

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

View file

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

106
README.md
View file

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

View file

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

18
analysis_options.yaml Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

BIN
assets/Consolas.ttf Normal file

Binary file not shown.

BIN
assets/images/cat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
assets/images/text.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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:flutter/material.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:local_spend/common/functions/save_current_login.dart'; import 'package:local_spend/common/functions/save_current_login.dart';
import 'package:local_spend/common/functions/show_dialog_single_button.dart';
import 'package:local_spend/model/json/login_model.dart'; import 'package:local_spend/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( Future<LoginModel> requestLoginAPI(
BuildContext context, String email, String password) async { BuildContext context, String email, String password) async {
//var apiUrl = ConfigWrapper.of(context).apiKey; final url = "https://dev.localspend.co.uk/api/login";
final url = "https://dev.peartrade.org/api/login";
Map<String, String> body = { Map<String, String> body = {
'email': email, 'email': email,
'password': password, 'password': password,
}; };
// debugPrint('$body'); try {
final response = await http
.post(
url,
body: json.encode(body),
)
.timeout(Duration(seconds: 5));
final response = await http.post( if (response.statusCode == 200) {
url, final responseJson = json.decode(response.body);
body: json.encode(body),
);
// debugPrint(response.body); saveCurrentLogin(responseJson, body["email"]);
await Navigator.of(context).pushReplacementNamed('/HomePage');
if (response.statusCode == 200) { return LoginModel.fromJson(responseJson);
final responseJson = json.decode(response.body); } else {
var user = new LoginModel.fromJson(responseJson); final responseJson = json.decode(response.body);
saveCurrentLogin(responseJson, body["email"]); saveCurrentLogin(responseJson, body["email"]);
Navigator.of(context).pushReplacementNamed('/HomePage');
return LoginModel.fromJson(responseJson); await _incorrectDialog(context, true);
} else {
// debugPrint("Invalid, either credentials are wrong or server is down");
final responseJson = json.decode(response.body); return null;
}
saveCurrentLogin(responseJson, body["email"]); } on TimeoutException catch (_) {
await _incorrectDialog(context, false);
showDialogSingleButton( } catch (error) {
context, debugPrint(error.toString());
"Unable to Login", await _incorrectDialog(context, false);
"You may have supplied an invalid 'Email' / 'Password' combination. Please try again or email an administrator.",
"OK");
return null;
} }
return null;
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,13 +5,13 @@ part 'config.g.dart';
@JsonSerializable(createToJson: false) @JsonSerializable(createToJson: false)
class Config { class Config {
final String env;
final bool production;
final String apiKey;
Config({this.env, this.production, this.apiKey}); Config({this.env, this.production, this.apiKey});
factory Config.fromJson(Map<String, dynamic> json) => _$ConfigFromJson(json); factory Config.fromJson(Map<String, dynamic> json) => _$ConfigFromJson(json);
final String env;
final bool production;
final String apiKey;
} }
class ConfigWrapper extends StatelessWidget { class ConfigWrapper extends StatelessWidget {
@ -24,7 +24,7 @@ class ConfigWrapper extends StatelessWidget {
static Config of(BuildContext context) { static Config of(BuildContext context) {
final _InheritedConfig inheritedConfig = final _InheritedConfig inheritedConfig =
context.inheritFromWidgetOfExactType(_InheritedConfig); context.inheritFromWidgetOfExactType(_InheritedConfig);
return inheritedConfig.config; return inheritedConfig.config;
} }
@ -43,4 +43,4 @@ class _InheritedConfig extends InheritedWidget {
@override @override
bool updateShouldNotify(_InheritedConfig oldWidget) => bool updateShouldNotify(_InheritedConfig oldWidget) =>
config != oldWidget.config; 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'; part 'dev.g.dart';
@JsonLiteral('dev.json', asConst: true) @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 = { const _$configJsonLiteral = {
'env': 'DEV', 'env': 'DEV',
'production': false, 'production': false,
'apiUrl': 'https://dev.peartrade.org/api' 'apiUrl': 'https://dev.localspend.co.uk/api'
}; };

2
lib/env/dev.json vendored
View file

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

2
lib/env/prod.dart vendored
View file

@ -3,4 +3,4 @@ import 'package:json_annotation/json_annotation.dart';
part 'prod.g.dart'; part 'prod.g.dart';
@JsonLiteral('prod.json', asConst: true) @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 = { const _$configJsonLiteral = {
'env': 'PROD', 'env': 'PROD',
'production': true, 'production': true,
'apiUrl': 'https://www.peartrade.org/api' 'apiUrl': 'https://www.localspend.co.uk/api'
}; };

2
lib/env/prod.json vendored
View file

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

View file

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

View file

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

View file

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

View file

@ -7,8 +7,9 @@ import 'package:local_spend/common/platform/platform_scaffold.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:local_spend/common/widgets/labeled_checkbox.dart'; import 'package:local_spend/common/widgets/labeled_checkbox.dart';
import 'package:local_spend/common/widgets/animatedGradientButton.dart';
const URL = "https://flutter.io/"; const url = "https://flutter.io/";
class LoginPage extends StatefulWidget { class LoginPage extends StatefulWidget {
@override @override
@ -18,12 +19,12 @@ class LoginPage extends StatefulWidget {
} }
class LoginPageState extends State<LoginPage> { class LoginPageState extends State<LoginPage> {
final TextEditingController _emailController = TextEditingController(/*text: 'test@example.com'*/); // remove bool _isLoggingIn = false;
final TextEditingController _passwordController = TextEditingController(/*text: 'abc123'*/); // remove final TextEditingController _emailController = TextEditingController();
bool _saveLoginDetails = true; // I am extremely sorry for the placement of this variable final TextEditingController _passwordController = TextEditingController();
// it will be fixed soon I promise bool _saveLoginDetails = true;
FocusNode focusNode; // added so focus can move automatically FocusNode focusNode; // added so focus can move automatically
Future launchURL(String url) async { Future launchURL(String url) async {
if (await canLaunch(url)) { if (await canLaunch(url)) {
@ -32,7 +33,7 @@ class LoginPageState extends State<LoginPage> {
showDialogSingleButton( showDialogSingleButton(
context, context,
"Unable to reach your website.", "Unable to reach your website.",
"Currently unable to reach the website $URL. Please try again at a later time.", "Currently unable to reach the website $url. Please try again at a later time.",
"OK"); "OK");
} }
} }
@ -53,7 +54,7 @@ class LoginPageState extends State<LoginPage> {
super.dispose(); super.dispose();
} }
_fillLoginDetails() async { void _fillLoginDetails() async {
SharedPreferences preferences = await SharedPreferences.getInstance(); SharedPreferences preferences = await SharedPreferences.getInstance();
var username = await preferences.get('username'); var username = await preferences.get('username');
@ -63,27 +64,27 @@ class LoginPageState extends State<LoginPage> {
_passwordController.text = await password; _passwordController.text = await password;
} }
_saveCurrentRoute(String lastRoute) async { void _saveCurrentRoute(String lastRoute) async {
SharedPreferences preferences = await SharedPreferences.getInstance(); SharedPreferences preferences = await SharedPreferences.getInstance();
await preferences.setString('LastPageRoute', lastRoute); await preferences.setString('LastPageRoute', lastRoute);
} }
login(String username, String password) async { void login(String username, String password) async {
SystemChannels.textInput.invokeMethod('TextInput.hide'); _isLoggingIn = true;
await SystemChannels.textInput.invokeMethod('TextInput.hide');
SharedPreferences preferences = await SharedPreferences.getInstance(); SharedPreferences preferences = await SharedPreferences.getInstance();
if (_saveLoginDetails) { if (_saveLoginDetails) {
await preferences.setString('username', username); await preferences.setString('username', username);
await preferences.setString('password', password); await preferences.setString('password', password);
print("details saved");
} else { } else {
await preferences.setString('username', ""); await preferences.setString('username', "");
await preferences.setString('password', ""); await preferences.setString('password', "");
print("details cleared");
} }
requestLoginAPI(context, username, await requestLoginAPI(context, username, password).then((value) {
password); _isLoggingIn = false;
});
} }
@override @override
@ -96,153 +97,128 @@ class LoginPageState extends State<LoginPage> {
} else { } else {
Navigator.of(context).pushReplacementNamed('/HomePage'); Navigator.of(context).pushReplacementNamed('/HomePage');
} }
return null;
}, },
child: PlatformScaffold( child: PlatformScaffold(
// drawer: BasicDrawer(), body: Stack(
// body: Container( children: [
// decoration: BoxDecoration(color: Colors.white), AnimatedBackground([Colors.lightBlue[50], Colors.lightBlue[50]],
// margin: const EdgeInsets.all(20), Colors.white, Alignment.topRight, Alignment.bottomLeft, 3),
// child: Padding( Container(
// padding: EdgeInsets.fromLTRB(30.0, 170.0, 30.0, 0.0), margin: EdgeInsets.fromLTRB(60, 30, 60, 0),
// child: ListView( child: Column(
// children: <Widget>[ children: <Widget>[
body: Container( Expanded(
decoration: new BoxDecoration( child: AnimatedContainer(
gradient: new LinearGradient( duration: Duration(seconds: 2),
colors: [Colors.white, Colors.blue[50]], margin: EdgeInsets.fromLTRB(15, 0, 15, 0),
stops: [0,1], decoration: BoxDecoration(
begin: Alignment.topCenter, image: DecorationImage(
end: Alignment.bottomCenter, image:
), AssetImage('assets/images/launch_image.png')),
), child: Container(
margin: EdgeInsets.fromLTRB(60,30,60,0),
child: Column(
children: <Widget>[
Expanded(
child: Container(
margin: EdgeInsets.fromLTRB(15,0,15,0),
// alignment: FractionalOffset(0.5, 0.3), // not sure what this does ngl :/
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/launch_image.png')
), ),
), ),
), ),
), Padding(
Padding( padding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 0.0),
padding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 0.0), child: TextField(
child: TextField( autocorrect: false,
autocorrect: false, textAlign: TextAlign.center,
textAlign: TextAlign.center, controller: _emailController,
controller: _emailController, decoration: InputDecoration(
decoration: InputDecoration( hintText: "EMAIL",
hintText: "EMAIL", hintStyle: TextStyle(fontSize: 15),
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( padding: EdgeInsets.fromLTRB(0.0, 15.0, 0.0, 0.0),
padding: EdgeInsets.fromLTRB(0.0, 15.0, 0.0, 0.0), child: TextField(
child: TextField( autocorrect: false,
autocorrect: false, textAlign: TextAlign.center,
textAlign: TextAlign.center, controller: _passwordController,
controller: _passwordController, focusNode: focusNode,
focusNode: focusNode, decoration: InputDecoration(
decoration: InputDecoration( hintText: 'PASSWORD',
hintText: 'PASSWORD', hintStyle: TextStyle(fontSize: 15),
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);
},
), ),
), Container(
margin: EdgeInsets.fromLTRB(0.0, 40.0, 0.0, 30.0),
Padding( width: 100,
padding: EdgeInsets.fromLTRB(0.0, 40.0, 0.0, 30.0), height: 50,
child: Opacity(
child : Material( opacity: _isLoggingIn ? 0.5 : 1,
child: new Container( child: ClipRRect(
child : InkWell( borderRadius: BorderRadius.circular(2),
onTap: () => login( _emailController.text, _passwordController.text), child: Stack(
child: new Container( children: [
width: 100, AnimatedBackground(
height: 50, [Colors.blue, Colors.lightBlue[300]],
child: new Center( Colors.lightBlue,
child: new Text( Alignment.bottomRight,
'GO', style: new TextStyle(fontSize: 18, color: Colors.white),), 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:flutter/material.dart';
import 'package:local_spend/common/platform/platform_scaffold.dart'; import 'package:local_spend/common/platform/platform_scaffold.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -7,7 +6,7 @@ import 'package:url_launcher/url_launcher.dart';
import 'package:local_spend/common/functions/customAbout.dart' as custom; import 'package:local_spend/common/functions/customAbout.dart' as custom;
import 'package:local_spend/common/functions/showDialogTwoButtons.dart'; import 'package:local_spend/common/functions/showDialogTwoButtons.dart';
const URL = "https://flutter.io/"; const url = "https://flutter.io/";
const demonstration = false; const demonstration = false;
class MorePage extends StatefulWidget { class MorePage extends StatefulWidget {
@ -18,7 +17,7 @@ class MorePage extends StatefulWidget {
} }
class MorePageState extends State<MorePage> { class MorePageState extends State<MorePage> {
FocusNode focusNode; // added so focus can move automatically FocusNode focusNode; // added so focus can move automatically
DateTime date; DateTime date;
@ -37,7 +36,7 @@ class MorePageState extends State<MorePage> {
focusNode.dispose(); //disposes focus node when form disposed focusNode.dispose(); //disposes focus node when form disposed
} }
_saveCurrentRoute(String lastRoute) async { void _saveCurrentRoute(String lastRoute) async {
SharedPreferences preferences = await SharedPreferences.getInstance(); SharedPreferences preferences = await SharedPreferences.getInstance();
await preferences.setString('LastPageRoute', lastRoute); await preferences.setString('LastPageRoute', lastRoute);
} }
@ -52,9 +51,9 @@ class MorePageState extends State<MorePage> {
} else { } else {
Navigator.of(context).pushReplacementNamed('/LoginPage'); Navigator.of(context).pushReplacementNamed('/LoginPage');
} }
return null;
}, },
child: PlatformScaffold( child: PlatformScaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.blue[400], backgroundColor: Colors.blue[400],
title: Text( title: Text(
@ -68,15 +67,12 @@ class MorePageState extends State<MorePage> {
centerTitle: true, centerTitle: true,
iconTheme: IconThemeData(color: Colors.black), iconTheme: IconThemeData(color: Colors.black),
), ),
body: Container( body: Container(
padding: EdgeInsets.fromLTRB(30.0, 0.0, 30.0, 0.0),
child: ListView( child: ListView(
children: <Widget>[ children: <Widget>[
Container( Container(
padding: EdgeInsets.fromLTRB(0.0,25,0.0,0.0), padding: EdgeInsets.fromLTRB(30.0, 25, 30.0, 0.0),
child : Text( child: Text(
"Local Spend Tracker", "Local Spend Tracker",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
@ -86,76 +82,93 @@ class MorePageState extends State<MorePage> {
), ),
), ),
), ),
//
// Padding(
// padding: EdgeInsets.fromLTRB(10, 15, 0, 0),
// child: Text("helloooo"),
// ),
Padding( Padding(
padding: EdgeInsets.fromLTRB(0.0, 25.0, 0.0, 0.0), padding: EdgeInsets.fromLTRB(30.0, 25.0, 30.0, 0.0),
child: Container( child: Container(
height: 65.0, height: 65.0,
child: RaisedButton( child: RaisedButton(
onPressed: () { onPressed: () {
custom.showAboutDialog( custom.showAboutDialog(
context: context, context: context,
applicationIcon: new Icon(Icons.receipt), applicationIcon: new Icon(Icons.receipt),
applicationName: "Local Spend Tracker", applicationName: "Local Spend Tracker",
children: <Widget> [ children: <Widget>[
Text("Pear Trading is a commerce company designed to register and monitor money circulating in the local economy."), Text("Pear Trading is a commerce company designed to register and monitor money circulating in the local economy.\n"),
Text("\nContact at test@example.com or +44(0)1524 64544"), Container(
margin: EdgeInsets.symmetric(horizontal: 10),
Padding( height: 35,
padding: EdgeInsets.fromLTRB(0,20,0,0), child: RaisedButton(
child: InkWell( onPressed: () => launch('http://www.peartrade.org'),
child: Text child: Text("Pear Trading",
('Developed by Shadowcat Systems',
style: TextStyle( style: TextStyle(
color: Colors.blue, color: Colors.white, fontSize: 18.0)),
), color: Colors.green,
),
onTap: () => launch('https://shadow.cat/')
), ),
), ),
Container(
margin: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 0.0),
height: 40.0,
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(3),
onTap: () => launch('https://shadow.cat'),
child: Column(
children: [
Align(
child: Text("Developed by"),
alignment: Alignment.centerLeft),
Container(
margin: EdgeInsets.all(0),
child : Text(
"Shadowcat Systems",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold
),
),
),
],
),
),
),
),
], ],
); );
}, },
child: Text("ABOUT", child: Text("ABOUT",
style: style: TextStyle(color: Colors.white, fontSize: 22.0)),
TextStyle(color: Colors.white, fontSize: 22.0)),
color: Colors.blue, color: Colors.blue,
), ),
), ),
), ),
Padding( Padding(
padding: EdgeInsets.fromLTRB(0.0, 20.0, 0.0, 0.0), padding: EdgeInsets.fromLTRB(30.0, 20.0, 30.0, 0.0),
child: Container( child: Container(
height: 65.0, height: 65.0,
child: RaisedButton( child: RaisedButton(
onPressed: () { onPressed: () {
showDialogTwoButtons( showDialogTwoButtons(
context, context,
"Logout", "Logout",
"Are you sure you want to log out?", "Are you sure you want to log out?",
"Cancel", "Cancel",
"Logout", "Logout",
logout logout);
);
}, },
child: Text("LOGOUT", child: Text("LOGOUT",
style: style: TextStyle(color: Colors.white, fontSize: 22.0)),
TextStyle(color: Colors.white, fontSize: 22.0)),
color: Colors.red, color: Colors.red,
), ),
), ),
), ),
// Padding( // Padding(
// padding: EdgeInsets.fromLTRB(0.0, 20.0, 0.0, 0.0), // padding: EdgeInsets.fromLTRB(30.0, 20.0, 30.0, 0.0),
// child: Container( // child: Container(
// height: 65.0, // height: 65.0,
// child: RaisedButton( // child: RaisedButton(
@ -169,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,612 +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';
const URL = "https://flutter.io/";
const demonstration = false;
class ReceiptPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return new ReceiptPageState();
}
}
class ReceiptPageState extends State<ReceiptPage> {
final TextEditingController _timeController = TextEditingController();
final TextEditingController _amountController = TextEditingController();
final TextEditingController _essentialController = TextEditingController();
final TextEditingController _recurringController = TextEditingController();
final TextEditingController _categoryController = TextEditingController();
final TextEditingController _orgController = TextEditingController();
final OrganizationController _organizationController = OrganizationController();
List<String> _categoryDropDownItems = List<String>();
FocusNode focusNode;
DateTime date;
Future launchURL(String url) async {
if (await canLaunch(url)) {
await launch(url, forceSafariVC: true, forceWebView: true);
} else {
showDialogSingleButton(
context,
"Unable to reach your website.",
"Currently unable to reach the website $URL. Please try again at a later time.",
"OK");
}
}
@override
void initState() {
getCategoriesStrings().then((value) {
_categoryDropDownItems = value;
});
super.initState();
_saveCurrentRoute("/ReceiptPageState");
focusNode = FocusNode();
_recurringController.text = "None";
_categoryController.text = "";
// getCategoriesStrings().then((value) {
// _categoryDropDownItems = value;
// });
}
@override
void dispose() {
super.dispose();
focusNode.dispose(); //disposes focus node when form disposed
}
_saveCurrentRoute(String lastRoute) async {
SharedPreferences preferences = await SharedPreferences.getInstance();
await preferences.setString('LastPageRoute', lastRoute);
}
// this file is getting really messy sorry everyone
void submitReceipt(String amount, String time, Organisation organisation, String recurring, String category, String essential) async {
SystemChannels.textInput.invokeMethod('TextInput.hide');
if (organisation == null) {
_findOrganizationsDialog(context);
/* await showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: new Text("Missing organisation"),
content: new Text(
"Please press 'Find' to select your desired organization."),
actions: <Widget>[
new FlatButton(
child: new Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);*/
}
else {
if (amount == "" || time == "") {
await showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: new Text("Missing required data"),
content: new Text(
"We couldn't process your request because one or more required fields are missing."),
actions: <Widget>[
new FlatButton(
child: new Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
else {
if (demonstration) {
await showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: new Text("Success"),
content: new Text("Receipt successfully submitted."),
actions: <Widget>[
// usually buttons at the bottom of the dialog
new FlatButton(
child: new Text("OK"),
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).pushReplacementNamed("/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<String>> getCategoriesStrings() async {
var categories = getCategories(); //future<list<cat>>
var categoriesStrings = List<String>();
categories.then((val) {
val.forEach((thisCategory) {
// print(thisCategory.name);
categoriesStrings.add(thisCategory.name);
});
// print(categoriesStrings[10]); // prints 'Banana'
// print(categoriesStrings.toString());
_categoryDropDownItems = categoriesStrings;
return categoriesStrings;
});
}
List<String> getRecurringOptions() {
var options = new List<String>(7);
options[0] = "None";
options[1] = "Daily";
options[2] = "Weekly";
options[3] = "Fortnightly";
options[4] = "Monthly";
options[5] = "Quarterly";
options[6] = "Yearly";
return options;
}
String formatDate(String date) {
// return "";
// should be in format:
// yyyy-MM-ddThh:mm:00.000+01:00
// eg 2019-07-05T10:24:00.000+01.00 (real life example, works)
// current format = "dd/MM/yyyy 'at' hh:mm"
// 0123456789ABCDEFGHIJK
var components = new List(5);
components[0] = (date.substring(0,2)); // dd
components[1] = (date.substring(3,5)); // MM
components[2] = (date.substring(6,10)); // yyyy
components[3] = (date.substring(14,16)); // hh
components[4] = (date.substring(17,19)); // mm
//print(components);
return (components[2] + "-" + components[1] + "-" + components[0]
+ "T" + components[3] + ":" + components[4] + ":00.000+01:00");
// Yes, there is probably a function to convert dates, but I didn't
// know that before writing this and it's done now so I'm keeping it.
}
Organisation listOrganisations(List<Organisation> organisations, context) {
if (organisations.length == 0) {
showDialogSingleButton(
context,
"No matching organizations",
"We were unable to find any organizations matching this text.",
"OK"
);
return null;
}
var optionsList = new List<String>();
for (var i = 0; i < organisations.length; i++) {
optionsList.add(organisations[i].name);
}
// var popupListView = new PopupListView(context, optionsList, "Choose Organization");
var popupListView = new PopupListView();
var dialog = popupListView.dialog(context, optionsList, "Choose Organization");
// dialog.then((value) => debugPrint(value));
dialog.then((value) {
_orgController.text = value;
_organizationController.organisation = organisations.where((thisOrg) => thisOrg.name == value).elementAt(0);
// this may not work when two organisations have the same name,
// then again the popupListView can't display two of the same names properly either
});
//can't return value as it is <future> and thus would block
}
_findOrganizationsDialog(context) {
if (_orgController.text != "") {
var organisations = findOrganisations(
_orgController.text); // returns Future<List<Organisation>>
var choice = organisations.then((data) =>
listOrganisations(data, context));
choice.then((value) => _orgController.text = value.name);
choice.then((value) => _organizationController.organisation = value);
} else {
// no data entered
showDialogSingleButton(
context,
"No data",
"We were unable to service your request because no data was entered.",
"OK"
);
}
}
@override
Widget build(BuildContext context) {
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,18,0.0,0.0),
child : Container (
height: 35,
// width: 400,
child : ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
Container(
padding: const EdgeInsets.fromLTRB(0, 7, 0, 8),
child: Text(
"Category",
style: TextStyle(
fontSize: 18.0,
color: Colors.black,
),
),
),
Container(
padding: const EdgeInsets.fromLTRB(29, 0, 0, 0),
child: DropdownButton<String>(
value: _categoryController.text,
onChanged: (String newValue) {
setState(() {
_categoryController.text = newValue;
});
},
items: _categoryDropDownItems.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
)
),
],
),
),
),
Padding(
padding: EdgeInsets.fromLTRB(0.0, 20.0, 0.0, 0.0),
child: Container(
height: 65.0,
child: RaisedButton(
onPressed: () {
try {
submitReceipt(
_amountController.text, _timeController.text,
_organizationController.organisation, _recurringController.text, _categoryController.text, _essentialController.text);
}
catch (_) {
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: new Text("Invalid data"),
content: new Text(
"We couldn't process your request because some of the data entered is invalid."),
actions: <Widget>[
new FlatButton(
child: new Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
},
child: Text("GO",
style:
TextStyle(color: Colors.white, fontSize: 22.0)),
color: Colors.blue,
),
),
),
],
),
),
);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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