Merge pull request #9 from Pear-Trading/fix/update
Merge `felix/main` branch into `development`, whilst also updating it
4
.gitignore
vendored
|
@ -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
|
||||||
|
|
|
@ -1,29 +1,116 @@
|
||||||
<component name="ProjectCodeStyleConfiguration">
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
<code_scheme name="Project" version="173">
|
<code_scheme name="Project" version="173">
|
||||||
<Objective-C-extensions>
|
<codeStyleSettings language="XML">
|
||||||
<file>
|
<indentOptions>
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
|
</indentOptions>
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
|
<arrangement>
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
|
<rules>
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
|
<section>
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
|
<rule>
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
|
<match>
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
|
<AND>
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
|
<NAME>xmlns:android</NAME>
|
||||||
</file>
|
<XML_ATTRIBUTE />
|
||||||
<class>
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
|
</AND>
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
|
</match>
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
|
</rule>
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
|
</section>
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
|
<section>
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
|
<rule>
|
||||||
</class>
|
<match>
|
||||||
<extensions>
|
<AND>
|
||||||
<pair source="cpp" header="h" fileNamingConvention="NONE" />
|
<NAME>xmlns:.*</NAME>
|
||||||
<pair source="c" header="h" fileNamingConvention="NONE" />
|
<XML_ATTRIBUTE />
|
||||||
</extensions>
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
</Objective-C-extensions>
|
</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>
|
</code_scheme>
|
||||||
</component>
|
</component>
|
|
@ -1,6 +1,6 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="main.dart" type="FlutterRunConfigurationType" factoryName="Flutter">
|
<configuration default="false" name="main.dart" type="FlutterRunConfigurationType" factoryName="Flutter" singleton="false">
|
||||||
<option name="filePath" value="$PROJECT_DIR$/lib/main.dart" />
|
<option name="filePath" value="$PROJECT_DIR$/lib/main.dart" />
|
||||||
<method />
|
<method v="2" />
|
||||||
</configuration>
|
</configuration>
|
||||||
</component>
|
</component>
|
20
README.md
|
@ -12,6 +12,21 @@ Follow these steps to get the project up-and-running:
|
||||||
1. If this is your first Flutter project, install the [Flutter SDK](https://flutter.dev/docs/get-started/test-drive)
|
1. If this is your first Flutter project, install the [Flutter SDK](https://flutter.dev/docs/get-started/test-drive)
|
||||||
1. Add the line `flutter.sdk=⟨ path to Flutter SDK ⟩` to the file `androud/local.properties`
|
1. Add the line `flutter.sdk=⟨ path to Flutter SDK ⟩` to the file `androud/local.properties`
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
To build an apk from dev, use:
|
||||||
|
```
|
||||||
|
flutter build apk -t lib/main_dev.dart
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
```
|
||||||
|
// debug
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
debugPrint('$foo');
|
||||||
|
```
|
||||||
|
|
||||||
## Further Reading
|
## Further Reading
|
||||||
|
|
||||||
A few resources to get you started if this is your first Flutter project:
|
A few resources to get you started if this is your first Flutter project:
|
||||||
|
@ -23,7 +38,10 @@ For help getting started with Flutter, view our
|
||||||
[online documentation](https://flutter.io/docs), which offers tutorials,
|
[online documentation](https://flutter.io/docs), which offers tutorials,
|
||||||
samples, guidance on mobile development, and a full API reference.
|
samples, guidance on mobile development, and a full API reference.
|
||||||
|
|
||||||
|
https://github.com/putraxor/flutter-login-ui
|
||||||
|
https://github.com/pbirdsall/medium_splash_tokenauth
|
||||||
|
|
||||||
Related repos:
|
Related repos:
|
||||||
|
|
||||||
- [`Pear-Trading/FoodLoop-Web`](https://github.com/Pear-Trading/FoodLoop-Web)
|
- [`Pear-Trading/FoodLoop-Web`](https://github.com/Pear-Trading/FoodLoop-Web)
|
||||||
- [`Pear-Trading/Foodloop-Server`](https://github.com/Pear-Trading/Foodloop-Server)
|
- [`Pear-Trading/Foodloop-Server`](https://github.com/Pear-Trading/Foodloop-Server)
|
18
analysis_options.yaml
Normal 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
|
|
@ -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'
|
||||||
|
|
|
@ -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"/>
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
<item android:drawable="@android:color/white" />
|
<item android:drawable="@android:color/white" />
|
||||||
|
|
||||||
<!-- You can insert your own image assets here -->
|
<!-- You can insert your own image assets here -->
|
||||||
<!-- <item>
|
<!--<item>
|
||||||
<bitmap
|
<bitmap
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:src="@mipmap/launch_image" />
|
android:src="@mipmap/launch_image" />
|
||||||
</item> -->
|
</item>-->
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
|
12
android/app/src/main/res/drawable/normal_background.xml
Normal 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>
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 6.9 KiB |
BIN
android/app/src/main/res/mipmap-ldpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 8.8 KiB |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 18 KiB |
BIN
android/app/src/main/res/mipmap/launch_image.png
Normal file
After Width: | Height: | Size: 131 KiB |
BIN
android/app/src/main/res/playstore-icon.png
Normal file
After Width: | Height: | Size: 55 KiB |
|
@ -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>
|
||||||
|
|
|
@ -26,4 +26,4 @@ subprojects {
|
||||||
|
|
||||||
task clean(type: Delete) {
|
task clean(type: Delete) {
|
||||||
delete rootProject.buildDir
|
delete rootProject.buildDir
|
||||||
}
|
}
|
|
@ -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
BIN
assets/images/cat.png
Normal file
After Width: | Height: | Size: 90 KiB |
BIN
assets/images/launch_image.png
Normal file
After Width: | Height: | Size: 131 KiB |
BIN
assets/images/shadowcat-long.png
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
assets/images/text.png
Normal file
After Width: | Height: | Size: 169 KiB |
|
@ -1 +1,2 @@
|
||||||
|
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||||
#include "Generated.xcconfig"
|
#include "Generated.xcconfig"
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
|
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
#include "Generated.xcconfig"
|
#include "Generated.xcconfig"
|
||||||
|
|
69
ios/Podfile
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
# Uncomment this line to define a global platform for your project
|
||||||
|
platform :ios, '9.0'
|
||||||
|
|
||||||
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
||||||
|
project 'Runner', {
|
||||||
|
'Debug' => :debug,
|
||||||
|
'Profile' => :release,
|
||||||
|
'Release' => :release,
|
||||||
|
}
|
||||||
|
|
||||||
|
def parse_KV_file(file, separator='=')
|
||||||
|
file_abs_path = File.expand_path(file)
|
||||||
|
if !File.exists? file_abs_path
|
||||||
|
return [];
|
||||||
|
end
|
||||||
|
pods_ary = []
|
||||||
|
skip_line_start_symbols = ["#", "/"]
|
||||||
|
File.foreach(file_abs_path) { |line|
|
||||||
|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
|
||||||
|
plugin = line.split(pattern=separator)
|
||||||
|
if plugin.length == 2
|
||||||
|
podname = plugin[0].strip()
|
||||||
|
path = plugin[1].strip()
|
||||||
|
podpath = File.expand_path("#{path}", file_abs_path)
|
||||||
|
pods_ary.push({:name => podname, :path => podpath});
|
||||||
|
else
|
||||||
|
puts "Invalid plugin specification: #{line}"
|
||||||
|
end
|
||||||
|
}
|
||||||
|
return pods_ary
|
||||||
|
end
|
||||||
|
|
||||||
|
target 'Runner' do
|
||||||
|
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
|
||||||
|
# referring to absolute paths on developers' machines.
|
||||||
|
system('rm -rf .symlinks')
|
||||||
|
system('mkdir -p .symlinks/plugins')
|
||||||
|
|
||||||
|
# Flutter Pods
|
||||||
|
generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig')
|
||||||
|
if generated_xcode_build_settings.empty?
|
||||||
|
puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first."
|
||||||
|
end
|
||||||
|
generated_xcode_build_settings.map { |p|
|
||||||
|
if p[:name] == 'FLUTTER_FRAMEWORK_DIR'
|
||||||
|
symlink = File.join('.symlinks', 'flutter')
|
||||||
|
File.symlink(File.dirname(p[:path]), symlink)
|
||||||
|
pod 'Flutter', :path => File.join(symlink, File.basename(p[:path]))
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
# Plugin Pods
|
||||||
|
plugin_pods = parse_KV_file('../.flutter-plugins')
|
||||||
|
plugin_pods.map { |p|
|
||||||
|
symlink = File.join('.symlinks', 'plugins', p[:name])
|
||||||
|
File.symlink(p[:path], symlink)
|
||||||
|
pod p[:name], :path => File.join(symlink, 'ios')
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
target.build_configurations.each do |config|
|
||||||
|
config.build_settings['ENABLE_BITCODE'] = 'NO'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
45
ios/Podfile.lock
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
PODS:
|
||||||
|
- Flutter (1.0.0)
|
||||||
|
- google_maps_flutter (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- GoogleMaps
|
||||||
|
- GoogleMaps (3.3.0):
|
||||||
|
- GoogleMaps/Maps (= 3.3.0)
|
||||||
|
- GoogleMaps/Base (3.3.0)
|
||||||
|
- GoogleMaps/Maps (3.3.0):
|
||||||
|
- GoogleMaps/Base
|
||||||
|
- shared_preferences (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- url_launcher (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
|
||||||
|
DEPENDENCIES:
|
||||||
|
- Flutter (from `.symlinks/flutter/ios-profile`)
|
||||||
|
- google_maps_flutter (from `.symlinks/plugins/google_maps_flutter/ios`)
|
||||||
|
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
|
||||||
|
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
|
||||||
|
|
||||||
|
SPEC REPOS:
|
||||||
|
https://github.com/cocoapods/specs.git:
|
||||||
|
- GoogleMaps
|
||||||
|
|
||||||
|
EXTERNAL SOURCES:
|
||||||
|
Flutter:
|
||||||
|
:path: ".symlinks/flutter/ios-profile"
|
||||||
|
google_maps_flutter:
|
||||||
|
:path: ".symlinks/plugins/google_maps_flutter/ios"
|
||||||
|
shared_preferences:
|
||||||
|
:path: ".symlinks/plugins/shared_preferences/ios"
|
||||||
|
url_launcher:
|
||||||
|
:path: ".symlinks/plugins/url_launcher/ios"
|
||||||
|
|
||||||
|
SPEC CHECKSUMS:
|
||||||
|
Flutter: 58dd7d1b27887414a370fcccb9e645c08ffd7a6a
|
||||||
|
google_maps_flutter: 78a52114c898b42ea647919679a4c58b70abe876
|
||||||
|
GoogleMaps: cfee83da305b9aaeccf92c24ac79df11c3003492
|
||||||
|
shared_preferences: 1feebfa37bb57264736e16865e7ffae7fc99b523
|
||||||
|
url_launcher: 0067ddb8f10d36786672aa0722a21717dba3a298
|
||||||
|
|
||||||
|
PODFILE CHECKSUM: 3389836f37640698630b8f0670315d626d5f1469
|
||||||
|
|
||||||
|
COCOAPODS: 1.7.5
|
|
@ -8,10 +8,10 @@
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; };
|
|
||||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
|
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
|
||||||
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
|
9548094CC0738B936AB604F6 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DB0D87E6339E9CE2E335662 /* libPods-Runner.a */; };
|
||||||
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
|
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
|
||||||
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
|
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
|
||||||
|
@ -38,11 +38,12 @@
|
||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
0C47C2DEBD545D4054D9940A /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; };
|
|
||||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
|
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
|
||||||
|
510C088F759CD31C97486879 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
|
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
|
||||||
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
|
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
|
||||||
|
@ -55,6 +56,8 @@
|
||||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
9DB0D87E6339E9CE2E335662 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
C40D35B4079C018C62D759E0 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
@ -64,16 +67,34 @@
|
||||||
files = (
|
files = (
|
||||||
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
|
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
|
||||||
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
|
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
|
||||||
|
9548094CC0738B936AB604F6 /* libPods-Runner.a in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
24EB461E21BAD0D319745BEA /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9DB0D87E6339E9CE2E335662 /* libPods-Runner.a */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
55C1C6FB63DDD235539F7C5D /* Pods */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
510C088F759CD31C97486879 /* Pods-Runner.debug.xcconfig */,
|
||||||
|
0C47C2DEBD545D4054D9940A /* Pods-Runner.release.xcconfig */,
|
||||||
|
C40D35B4079C018C62D759E0 /* Pods-Runner.profile.xcconfig */,
|
||||||
|
);
|
||||||
|
path = Pods;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
2D5378251FAA1A9400D5DBA9 /* flutter_assets */,
|
|
||||||
3B80C3931E831B6300D905FE /* App.framework */,
|
3B80C3931E831B6300D905FE /* App.framework */,
|
||||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||||
9740EEBA1CF902C7004384FC /* Flutter.framework */,
|
9740EEBA1CF902C7004384FC /* Flutter.framework */,
|
||||||
|
@ -90,7 +111,8 @@
|
||||||
9740EEB11CF90186004384FC /* Flutter */,
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
97C146F01CF9000F007C117D /* Runner */,
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
97C146EF1CF9000F007C117D /* Products */,
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
CF3B75C9A7D2FA2A4C99F110 /* Frameworks */,
|
55C1C6FB63DDD235539F7C5D /* Pods */,
|
||||||
|
24EB461E21BAD0D319745BEA /* Frameworks */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
@ -133,12 +155,15 @@
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
8850D50C44B3BB3289D14523 /* [CP] Check Pods Manifest.lock */,
|
||||||
9740EEB61CF901F6004384FC /* Run Script */,
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
97C146EA1CF9000F007C117D /* Sources */,
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
97C146EC1CF9000F007C117D /* Resources */,
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
|
ACE3DC71CDB8FA5537935604 /* [CP] Embed Pods Frameworks */,
|
||||||
|
E6C2807EE928990FB790046F /* [CP] Copy Pods Resources */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
|
@ -160,6 +185,8 @@
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
97C146ED1CF9000F007C117D = {
|
97C146ED1CF9000F007C117D = {
|
||||||
CreatedOnToolsVersion = 7.3.1;
|
CreatedOnToolsVersion = 7.3.1;
|
||||||
|
DevelopmentTeam = G78D5X4L92;
|
||||||
|
ProvisioningStyle = Automatic;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -168,6 +195,7 @@
|
||||||
developmentRegion = English;
|
developmentRegion = English;
|
||||||
hasScannedForEncodings = 0;
|
hasScannedForEncodings = 0;
|
||||||
knownRegions = (
|
knownRegions = (
|
||||||
|
English,
|
||||||
en,
|
en,
|
||||||
Base,
|
Base,
|
||||||
);
|
);
|
||||||
|
@ -190,7 +218,6 @@
|
||||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||||
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
|
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
|
||||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||||
2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */,
|
|
||||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -212,6 +239,28 @@
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
|
||||||
};
|
};
|
||||||
|
8850D50C44B3BB3289D14523 /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
@ -226,6 +275,42 @@
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
};
|
};
|
||||||
|
ACE3DC71CDB8FA5537935604 /* [CP] Embed Pods Frameworks */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
|
||||||
|
"${PODS_ROOT}/../.symlinks/flutter/ios-profile/Flutter.framework",
|
||||||
|
);
|
||||||
|
name = "[CP] Embed Pods Frameworks";
|
||||||
|
outputPaths = (
|
||||||
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
E6C2807EE928990FB790046F /* [CP] Copy Pods Resources */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh",
|
||||||
|
"${PODS_ROOT}/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle",
|
||||||
|
);
|
||||||
|
name = "[CP] Copy Pods Resources";
|
||||||
|
outputPaths = (
|
||||||
|
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMaps.bundle",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
@ -314,14 +399,17 @@
|
||||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
DEVELOPMENT_TEAM = S8QB4VV633;
|
DEVELOPMENT_TEAM = G78D5X4L92;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(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)",
|
||||||
|
@ -329,6 +417,7 @@
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = uk.co.localspend.localSpend;
|
PRODUCT_BUNDLE_IDENTIFIER = uk.co.localspend.localSpend;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
};
|
};
|
||||||
name = Profile;
|
name = Profile;
|
||||||
|
@ -440,13 +529,17 @@
|
||||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = G78D5X4L92;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(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)",
|
||||||
|
@ -454,6 +547,7 @@
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = uk.co.localspend.localSpend;
|
PRODUCT_BUNDLE_IDENTIFIER = uk.co.localspend.localSpend;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
|
@ -463,13 +557,17 @@
|
||||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = G78D5X4L92;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(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)",
|
||||||
|
@ -477,6 +575,7 @@
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = uk.co.localspend.localSpend;
|
PRODUCT_BUNDLE_IDENTIFIER = uk.co.localspend.localSpend;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
|
|
3
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
|
@ -4,4 +4,7 @@
|
||||||
<FileRef
|
<FileRef
|
||||||
location = "group:Runner.xcodeproj">
|
location = "group:Runner.xcodeproj">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:Pods/Pods.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
</Workspace>
|
</Workspace>
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>BuildSystemType</key>
|
||||||
|
<string>Original</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -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];
|
||||||
|
|
244
ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file → Executable file
|
@ -1,122 +1,128 @@
|
||||||
{
|
{
|
||||||
"images" : [
|
"images":[
|
||||||
{
|
{
|
||||||
"size" : "20x20",
|
"idiom":"iphone",
|
||||||
"idiom" : "iphone",
|
"size":"20x20",
|
||||||
"filename" : "Icon-App-20x20@2x.png",
|
"scale":"2x",
|
||||||
"scale" : "2x"
|
"filename":"Icon-App-20x20@2x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "20x20",
|
"idiom":"iphone",
|
||||||
"idiom" : "iphone",
|
"size":"20x20",
|
||||||
"filename" : "Icon-App-20x20@3x.png",
|
"scale":"3x",
|
||||||
"scale" : "3x"
|
"filename":"Icon-App-20x20@3x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "29x29",
|
"idiom":"iphone",
|
||||||
"idiom" : "iphone",
|
"size":"29x29",
|
||||||
"filename" : "Icon-App-29x29@1x.png",
|
"scale":"1x",
|
||||||
"scale" : "1x"
|
"filename":"Icon-App-29x29@1x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "29x29",
|
"idiom":"iphone",
|
||||||
"idiom" : "iphone",
|
"size":"29x29",
|
||||||
"filename" : "Icon-App-29x29@2x.png",
|
"scale":"2x",
|
||||||
"scale" : "2x"
|
"filename":"Icon-App-29x29@2x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "29x29",
|
"idiom":"iphone",
|
||||||
"idiom" : "iphone",
|
"size":"29x29",
|
||||||
"filename" : "Icon-App-29x29@3x.png",
|
"scale":"3x",
|
||||||
"scale" : "3x"
|
"filename":"Icon-App-29x29@3x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "40x40",
|
"idiom":"iphone",
|
||||||
"idiom" : "iphone",
|
"size":"40x40",
|
||||||
"filename" : "Icon-App-40x40@2x.png",
|
"scale":"2x",
|
||||||
"scale" : "2x"
|
"filename":"Icon-App-40x40@2x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "40x40",
|
"idiom":"iphone",
|
||||||
"idiom" : "iphone",
|
"size":"40x40",
|
||||||
"filename" : "Icon-App-40x40@3x.png",
|
"scale":"3x",
|
||||||
"scale" : "3x"
|
"filename":"Icon-App-40x40@3x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "60x60",
|
"idiom":"iphone",
|
||||||
"idiom" : "iphone",
|
"size":"60x60",
|
||||||
"filename" : "Icon-App-60x60@2x.png",
|
"scale":"2x",
|
||||||
"scale" : "2x"
|
"filename":"Icon-App-60x60@2x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "60x60",
|
"idiom":"iphone",
|
||||||
"idiom" : "iphone",
|
"size":"60x60",
|
||||||
"filename" : "Icon-App-60x60@3x.png",
|
"scale":"3x",
|
||||||
"scale" : "3x"
|
"filename":"Icon-App-60x60@3x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "20x20",
|
"idiom":"iphone",
|
||||||
"idiom" : "ipad",
|
"size":"76x76",
|
||||||
"filename" : "Icon-App-20x20@1x.png",
|
"scale":"2x",
|
||||||
"scale" : "1x"
|
"filename":"Icon-App-76x76@2x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "20x20",
|
"idiom":"ipad",
|
||||||
"idiom" : "ipad",
|
"size":"20x20",
|
||||||
"filename" : "Icon-App-20x20@2x.png",
|
"scale":"1x",
|
||||||
"scale" : "2x"
|
"filename":"Icon-App-20x20@1x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "29x29",
|
"idiom":"ipad",
|
||||||
"idiom" : "ipad",
|
"size":"20x20",
|
||||||
"filename" : "Icon-App-29x29@1x.png",
|
"scale":"2x",
|
||||||
"scale" : "1x"
|
"filename":"Icon-App-20x20@2x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "29x29",
|
"idiom":"ipad",
|
||||||
"idiom" : "ipad",
|
"size":"29x29",
|
||||||
"filename" : "Icon-App-29x29@2x.png",
|
"scale":"1x",
|
||||||
"scale" : "2x"
|
"filename":"Icon-App-29x29@1x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "40x40",
|
"idiom":"ipad",
|
||||||
"idiom" : "ipad",
|
"size":"29x29",
|
||||||
"filename" : "Icon-App-40x40@1x.png",
|
"scale":"2x",
|
||||||
"scale" : "1x"
|
"filename":"Icon-App-29x29@2x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "40x40",
|
"idiom":"ipad",
|
||||||
"idiom" : "ipad",
|
"size":"40x40",
|
||||||
"filename" : "Icon-App-40x40@2x.png",
|
"scale":"1x",
|
||||||
"scale" : "2x"
|
"filename":"Icon-App-40x40@1x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "76x76",
|
"idiom":"ipad",
|
||||||
"idiom" : "ipad",
|
"size":"40x40",
|
||||||
"filename" : "Icon-App-76x76@1x.png",
|
"scale":"2x",
|
||||||
"scale" : "1x"
|
"filename":"Icon-App-40x40@2x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "76x76",
|
"idiom":"ipad",
|
||||||
"idiom" : "ipad",
|
"size":"76x76",
|
||||||
"filename" : "Icon-App-76x76@2x.png",
|
"scale":"1x",
|
||||||
"scale" : "2x"
|
"filename":"Icon-App-76x76@1x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "83.5x83.5",
|
"idiom":"ipad",
|
||||||
"idiom" : "ipad",
|
"size":"76x76",
|
||||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
"scale":"2x",
|
||||||
"scale" : "2x"
|
"filename":"Icon-App-76x76@2x.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "1024x1024",
|
"idiom":"ipad",
|
||||||
"idiom" : "ios-marketing",
|
"size":"83.5x83.5",
|
||||||
"filename" : "Icon-App-1024x1024@1x.png",
|
"scale":"2x",
|
||||||
"scale" : "1x"
|
"filename":"Icon-App-83.5x83.5@2x.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "1024x1024",
|
||||||
|
"idiom" : "ios-marketing",
|
||||||
|
"scale" : "1x",
|
||||||
|
"filename" : "ItunesArtwork@2x.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info":{
|
||||||
|
"version":1,
|
||||||
|
"author":"makeappicon"
|
||||||
}
|
}
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 564 B After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 8 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 115 KiB |
|
@ -2,8 +2,12 @@
|
||||||
<!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>
|
||||||
|
<string>Local Spend Tracker</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
|
@ -26,6 +30,10 @@
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
<string>Main</string>
|
<string>Main</string>
|
||||||
|
<key>UIStatusBarHidden</key>
|
||||||
|
<false/>
|
||||||
|
<key>UIStatusBarStyle</key>
|
||||||
|
<string>UIStatusBarStyleDarkContent</string>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
|
68
lib/common/apifunctions/categories.dart
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:local_spend/common/functions/get_token.dart';
|
||||||
|
|
||||||
|
Future<List<String>> getCategories() async {
|
||||||
|
const url = "https://dev.localspend.co.uk/api/search/category";
|
||||||
|
var token;
|
||||||
|
|
||||||
|
await getToken().then((result) {
|
||||||
|
token = result;
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, String> body = {
|
||||||
|
"session_key": token,
|
||||||
|
};
|
||||||
|
|
||||||
|
final response = await http.post(
|
||||||
|
url,
|
||||||
|
body: json.encode(body),
|
||||||
|
);
|
||||||
|
|
||||||
|
// print(response.body);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
//request successful
|
||||||
|
List<String> categories = new List<String>();
|
||||||
|
Map responseMap = json.decode(response.body);
|
||||||
|
|
||||||
|
var categoriesJSON = responseMap['categories'];
|
||||||
|
|
||||||
|
//nice.
|
||||||
|
// so the response needs to be decoded iteratively, like
|
||||||
|
// categories.add(new Category(name: categoriesJSON[i.toString()], index: i.toString()));
|
||||||
|
|
||||||
|
// print(categoriesJSON['11']); // prints "Banana"
|
||||||
|
|
||||||
|
int i = 1; // starts on 1. that was annoying to debug!
|
||||||
|
while (true) {
|
||||||
|
if (categoriesJSON[i.toString()] != null) {
|
||||||
|
// print("Iteration " + i.toString());
|
||||||
|
// print(categoriesJSON[i.toString()]);
|
||||||
|
categories.add(categoriesJSON[i.toString()]);
|
||||||
|
// print(categories.last);
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
// print("Number of categories: " + (i - 1).toString());
|
||||||
|
// print("categoriesJSON[" + i.toString() + ".toString()] == null");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} // does this until error, which then tells it that there is no more JSON left
|
||||||
|
|
||||||
|
// print(categories[11].name);
|
||||||
|
|
||||||
|
// decodedResponse.forEach((key, value) {
|
||||||
|
//// print(key + ": " + value);
|
||||||
|
// categories.add(new Category(name: value, index: key));
|
||||||
|
// });
|
||||||
|
|
||||||
|
// print(categories[10].name.toString()); // prints "Banana"
|
||||||
|
|
||||||
|
return categories; // categories is List<Category>
|
||||||
|
// Category is defined at the top^^
|
||||||
|
} else {
|
||||||
|
// not successful
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
85
lib/common/apifunctions/find_organisations.dart
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:local_spend/common/functions/get_token.dart';
|
||||||
|
|
||||||
|
class Organisation {
|
||||||
|
Organisation(
|
||||||
|
this.id,
|
||||||
|
this.name,
|
||||||
|
this.postcode,
|
||||||
|
this.streetName,
|
||||||
|
this.town,
|
||||||
|
);
|
||||||
|
|
||||||
|
var id = 0;
|
||||||
|
var name = "";
|
||||||
|
var postcode = "";
|
||||||
|
var streetName = ""; //street_name
|
||||||
|
var town = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
class Organisations {
|
||||||
|
List<Organisation> getTestData() {
|
||||||
|
var numItems = 10;
|
||||||
|
var itemsList = new List<Organisation>();
|
||||||
|
|
||||||
|
for (int i = 0; i < numItems; i++) {
|
||||||
|
itemsList.add(new Organisation(i, "Payee " + (i + 1).toString(),
|
||||||
|
"tee hee hee", "yeet street", "Robloxia"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemsList;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Organisation> _jsonToOrganisations(String json) {
|
||||||
|
Map decoded = jsonDecode(json);
|
||||||
|
List<dynamic> validated = decoded['validated'];
|
||||||
|
List<Map> organisationsMaps = new List<Map>();
|
||||||
|
validated.forEach((element) => organisationsMaps.add(element));
|
||||||
|
List<Organisation> organisations = new List<Organisation>();
|
||||||
|
|
||||||
|
for (var i = 0; i < organisationsMaps.length; i++) {
|
||||||
|
final params = organisationsMaps[i].values.toList();
|
||||||
|
|
||||||
|
var newOrganisation = new Organisation(
|
||||||
|
params[0].toInt(),
|
||||||
|
params[1].toString(),
|
||||||
|
params[2].toString(), // oof
|
||||||
|
params[3].toString(), // this could be improved...
|
||||||
|
params[4].toString(),
|
||||||
|
);
|
||||||
|
|
||||||
|
organisations.add(newOrganisation);
|
||||||
|
}
|
||||||
|
|
||||||
|
return organisations;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Organisation>> findOrganisations(String search) async {
|
||||||
|
final url = "https://dev.localspend.co.uk/api/search";
|
||||||
|
var token;
|
||||||
|
|
||||||
|
await getToken().then((result) {
|
||||||
|
token = result;
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, String> body = {
|
||||||
|
"search_name": search,
|
||||||
|
"session_key": token,
|
||||||
|
};
|
||||||
|
|
||||||
|
final response = await http.post(
|
||||||
|
url,
|
||||||
|
body: json.encode(body),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
//request successful
|
||||||
|
return _jsonToOrganisations(response.body);
|
||||||
|
} else {
|
||||||
|
// not successful
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
270
lib/common/apifunctions/get_graph_data.dart
Normal 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;
|
||||||
|
}
|
87
lib/common/apifunctions/get_map_data.dart
Normal 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
75
lib/common/apifunctions/request_login_api.dart
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:local_spend/common/functions/save_current_login.dart';
|
||||||
|
import 'package:local_spend/model/json/login_model.dart';
|
||||||
|
|
||||||
|
Future<void> _incorrectDialog(BuildContext context, bool isLoginWrong) async {
|
||||||
|
return showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: true,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AnimatedContainer(
|
||||||
|
duration: Duration(seconds: 2),
|
||||||
|
child: AlertDialog(
|
||||||
|
title: Text("Uh-oh!"),
|
||||||
|
content: Text(isLoginWrong
|
||||||
|
? "Incorrect login details. Please try again."
|
||||||
|
: "Our servers are having issues at the moment; sorry for the inconvenience. Please try again later."),
|
||||||
|
actions: <Widget>[
|
||||||
|
FlatButton(
|
||||||
|
child: Text('OK'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<LoginModel> requestLoginAPI(
|
||||||
|
BuildContext context, String email, String password) async {
|
||||||
|
final url = "https://dev.localspend.co.uk/api/login";
|
||||||
|
|
||||||
|
Map<String, String> body = {
|
||||||
|
'email': email,
|
||||||
|
'password': password,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await http
|
||||||
|
.post(
|
||||||
|
url,
|
||||||
|
body: json.encode(body),
|
||||||
|
)
|
||||||
|
.timeout(Duration(seconds: 5));
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final responseJson = json.decode(response.body);
|
||||||
|
|
||||||
|
saveCurrentLogin(responseJson, body["email"]);
|
||||||
|
await Navigator.of(context).pushReplacementNamed('/HomePage');
|
||||||
|
|
||||||
|
return LoginModel.fromJson(responseJson);
|
||||||
|
} else {
|
||||||
|
final responseJson = json.decode(response.body);
|
||||||
|
|
||||||
|
saveCurrentLogin(responseJson, body["email"]);
|
||||||
|
|
||||||
|
await _incorrectDialog(context, true);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} on TimeoutException catch (_) {
|
||||||
|
await _incorrectDialog(context, false);
|
||||||
|
} catch (error) {
|
||||||
|
debugPrint(error.toString());
|
||||||
|
await _incorrectDialog(context, false);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
29
lib/common/apifunctions/request_logout_api.dart
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:local_spend/common/functions/get_token.dart';
|
||||||
|
import 'package:local_spend/common/functions/save_logout.dart';
|
||||||
|
|
||||||
|
Future<bool> requestLogoutAPI() async {
|
||||||
|
saveLogout();
|
||||||
|
|
||||||
|
final url = "https://dev.localspend.co.uk/api/logout";
|
||||||
|
|
||||||
|
var token;
|
||||||
|
|
||||||
|
await getToken().then((result) {
|
||||||
|
token = result;
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, String> body = {
|
||||||
|
"Token": token,
|
||||||
|
};
|
||||||
|
|
||||||
|
await http.post(
|
||||||
|
url,
|
||||||
|
body: json.encode(body),
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
76
lib/common/apifunctions/submit_receipt_api.dart
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:local_spend/common/functions/show_dialog_single_button.dart';
|
||||||
|
import 'package:local_spend/model/json/login_model.dart';
|
||||||
|
|
||||||
|
class Receipt {
|
||||||
|
var amount = "";
|
||||||
|
var time = "";
|
||||||
|
var street = "";
|
||||||
|
var category = "";
|
||||||
|
var organisationName = "";
|
||||||
|
var postcode = "";
|
||||||
|
var recurring = "";
|
||||||
|
var town = "";
|
||||||
|
|
||||||
|
var essential = "false";
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<LoginModel> submitReceiptAPI(
|
||||||
|
BuildContext context, Receipt receipt) async {
|
||||||
|
//var apiUrl = ConfigWrapper.of(context).apiKey;
|
||||||
|
final url = "https://dev.localspend.co.uk/api/upload";
|
||||||
|
|
||||||
|
SharedPreferences preferences = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
|
Map<String, String> body = {
|
||||||
|
'transaction_type': "3",
|
||||||
|
'transaction_value': receipt.amount,
|
||||||
|
'purchase_time': receipt.time,
|
||||||
|
'category': receipt.category,
|
||||||
|
'essential': receipt.essential,
|
||||||
|
'organisation_name': receipt.organisationName,
|
||||||
|
'recurring': receipt.recurring,
|
||||||
|
'street_name': receipt.street,
|
||||||
|
'postcode': receipt.postcode,
|
||||||
|
'town': receipt.town,
|
||||||
|
'session_key': preferences.get('LastToken'),
|
||||||
|
};
|
||||||
|
|
||||||
|
// debugPrint('$body');
|
||||||
|
debugPrint(json.encode(body));
|
||||||
|
|
||||||
|
final response = await http.post(
|
||||||
|
url,
|
||||||
|
body: json.encode(body),
|
||||||
|
);
|
||||||
|
|
||||||
|
// debugPrint(response.body);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final responseJson = json.decode(response.body);
|
||||||
|
|
||||||
|
// print(responseJson[0]);
|
||||||
|
|
||||||
|
showDialogSingleButton(
|
||||||
|
context,
|
||||||
|
responseJson[0] == "" ? responseJson[0] : "Upload Successful",
|
||||||
|
"Transaction successfully submitted to server",
|
||||||
|
"OK");
|
||||||
|
return LoginModel.fromJson(responseJson);
|
||||||
|
} else {
|
||||||
|
final responseJson = json.decode(response.body);
|
||||||
|
|
||||||
|
showDialogSingleButton(
|
||||||
|
context,
|
||||||
|
"Unable to Submit Receipt",
|
||||||
|
// "You may have supplied an invalid 'Email' / 'Password' combination. Please try again or email an administrator.",
|
||||||
|
"Message from server: " + responseJson[1],
|
||||||
|
"OK");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
534
lib/common/functions/customAbout.dart
Normal file
|
@ -0,0 +1,534 @@
|
||||||
|
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
//import 'dart:developer' show Timeline, Flow;
|
||||||
|
import 'dart:developer' as dev;
|
||||||
|
import 'dart:io' show Platform;
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:flutter/widgets.dart' hide Flow;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// A [ListTile] that shows an about box.
|
||||||
|
///
|
||||||
|
/// This widget is often added to an app's [Drawer]. When tapped it shows
|
||||||
|
/// an about box dialog with [showAboutDialog].
|
||||||
|
///
|
||||||
|
/// The about box will include a button that shows licenses for software used by
|
||||||
|
/// the application. The licenses shown are those returned by the
|
||||||
|
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
|
||||||
|
///
|
||||||
|
/// If your application does not have a [Drawer], you should provide an
|
||||||
|
/// affordance to call [showAboutDialog] or (at least) [showLicensePage].
|
||||||
|
class AboutListTile extends StatelessWidget {
|
||||||
|
/// Creates a list tile for showing an about box.
|
||||||
|
///
|
||||||
|
/// The arguments are all optional. The application name, if omitted, will be
|
||||||
|
/// derived from the nearest [Title] widget. The version, icon, and legalese
|
||||||
|
/// values default to the empty string.
|
||||||
|
const AboutListTile({
|
||||||
|
Key key,
|
||||||
|
this.icon = const Icon(null),
|
||||||
|
this.child,
|
||||||
|
this.applicationName,
|
||||||
|
this.applicationVersion,
|
||||||
|
this.applicationIcon,
|
||||||
|
this.applicationLegalese,
|
||||||
|
this.aboutBoxChildren,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
/// The icon to show for this drawer item.
|
||||||
|
///
|
||||||
|
/// By default no icon is shown.
|
||||||
|
///
|
||||||
|
/// This is not necessarily the same as the image shown in the dialog box
|
||||||
|
/// itself; which is controlled by the [applicationIcon] property.
|
||||||
|
final Widget icon;
|
||||||
|
|
||||||
|
/// The label to show on this drawer item.
|
||||||
|
///
|
||||||
|
/// Defaults to a text widget that says "About Foo" where "Foo" is the
|
||||||
|
/// application name specified by [applicationName].
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
/// The name of the application.
|
||||||
|
///
|
||||||
|
/// This string is used in the default label for this drawer item (see
|
||||||
|
/// [child]) and as the caption of the [AboutDialog] that is shown.
|
||||||
|
///
|
||||||
|
/// Defaults to the value of [Title.title], if a [Title] widget can be found.
|
||||||
|
/// Otherwise, defaults to [Platform.resolvedExecutable].
|
||||||
|
final String applicationName;
|
||||||
|
|
||||||
|
/// The version of this build of the application.
|
||||||
|
///
|
||||||
|
/// This string is shown under the application name in the [AboutDialog].
|
||||||
|
///
|
||||||
|
/// Defaults to the empty string.
|
||||||
|
final String applicationVersion;
|
||||||
|
|
||||||
|
/// The icon to show next to the application name in the [AboutDialog].
|
||||||
|
///
|
||||||
|
/// By default no icon is shown.
|
||||||
|
///
|
||||||
|
/// Typically this will be an [ImageIcon] widget. It should honor the
|
||||||
|
/// [IconTheme]'s [IconThemeData.size].
|
||||||
|
///
|
||||||
|
/// This is not necessarily the same as the icon shown on the drawer item
|
||||||
|
/// itself, which is controlled by the [icon] property.
|
||||||
|
final Widget applicationIcon;
|
||||||
|
|
||||||
|
/// A string to show in small print in the [AboutDialog].
|
||||||
|
///
|
||||||
|
/// Typically this is a copyright notice.
|
||||||
|
///
|
||||||
|
/// Defaults to the empty string.
|
||||||
|
final String applicationLegalese;
|
||||||
|
|
||||||
|
/// Widgets to add to the [AboutDialog] after the name, version, and legalese.
|
||||||
|
///
|
||||||
|
/// This could include a link to a Web site, some descriptive text, credits,
|
||||||
|
/// or other information to show in the about box.
|
||||||
|
///
|
||||||
|
/// Defaults to nothing.
|
||||||
|
final List<Widget> aboutBoxChildren;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
assert(debugCheckHasMaterial(context));
|
||||||
|
assert(debugCheckHasMaterialLocalizations(context));
|
||||||
|
return ListTile(
|
||||||
|
leading: icon,
|
||||||
|
title: child ??
|
||||||
|
Text(MaterialLocalizations.of(context).aboutListTileTitle(
|
||||||
|
applicationName ?? _defaultApplicationName(context))),
|
||||||
|
onTap: () {
|
||||||
|
showAboutDialog(
|
||||||
|
context: context,
|
||||||
|
applicationName: applicationName,
|
||||||
|
applicationVersion: applicationVersion,
|
||||||
|
applicationIcon: applicationIcon,
|
||||||
|
applicationLegalese: applicationLegalese,
|
||||||
|
children: aboutBoxChildren,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays an [AboutDialog], which describes the application and provides a
|
||||||
|
/// button to show licenses for software used by the application.
|
||||||
|
///
|
||||||
|
/// The arguments correspond to the properties on [AboutDialog].
|
||||||
|
///
|
||||||
|
/// If the application has a [Drawer], consider using [AboutListTile] instead
|
||||||
|
/// of calling this directly.
|
||||||
|
///
|
||||||
|
/// If you do not need an about box in your application, you should at least
|
||||||
|
/// provide an affordance to call [showLicensePage].
|
||||||
|
///
|
||||||
|
/// The licenses shown on the [LicensePage] are those returned by the
|
||||||
|
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
|
||||||
|
///
|
||||||
|
/// The `context` argument is passed to [showDialog], the documentation for
|
||||||
|
/// which discusses how it is used.
|
||||||
|
void showAboutDialog({
|
||||||
|
@required BuildContext context,
|
||||||
|
String applicationName,
|
||||||
|
String applicationVersion,
|
||||||
|
Widget applicationIcon,
|
||||||
|
String applicationLegalese,
|
||||||
|
List<Widget> children,
|
||||||
|
}) {
|
||||||
|
assert(context != null);
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AboutDialog(
|
||||||
|
applicationName: applicationName,
|
||||||
|
applicationVersion: applicationVersion,
|
||||||
|
applicationIcon: applicationIcon,
|
||||||
|
applicationLegalese: applicationLegalese,
|
||||||
|
children: children,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays a [LicensePage], which shows licenses for software used by the
|
||||||
|
/// application.
|
||||||
|
///
|
||||||
|
/// The arguments correspond to the properties on [LicensePage].
|
||||||
|
///
|
||||||
|
/// If the application has a [Drawer], consider using [AboutListTile] instead
|
||||||
|
/// of calling this directly.
|
||||||
|
///
|
||||||
|
/// The [AboutDialog] shown by [showAboutDialog] includes a button that calls
|
||||||
|
/// [showLicensePage].
|
||||||
|
///
|
||||||
|
/// The licenses shown on the [LicensePage] are those returned by the
|
||||||
|
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
|
||||||
|
void showLicensePage({
|
||||||
|
@required BuildContext context,
|
||||||
|
String applicationName,
|
||||||
|
String applicationVersion,
|
||||||
|
Widget applicationIcon,
|
||||||
|
String applicationLegalese,
|
||||||
|
}) {
|
||||||
|
assert(context != null);
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute<void>(
|
||||||
|
builder: (BuildContext context) => LicensePage(
|
||||||
|
applicationName: applicationName,
|
||||||
|
applicationVersion: applicationVersion,
|
||||||
|
applicationLegalese: applicationLegalese,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An about box. This is a dialog box with the application's icon, name,
|
||||||
|
/// version number, and copyright, plus a button to show licenses for software
|
||||||
|
/// used by the application.
|
||||||
|
///
|
||||||
|
/// To show an [AboutDialog], use [showAboutDialog].
|
||||||
|
///
|
||||||
|
/// If the application has a [Drawer], the [AboutListTile] widget can make the
|
||||||
|
/// process of showing an about dialog simpler.
|
||||||
|
///
|
||||||
|
/// The [AboutDialog] shown by [showAboutDialog] includes a button that calls
|
||||||
|
/// [showLicensePage].
|
||||||
|
///
|
||||||
|
/// The licenses shown on the [LicensePage] are those returned by the
|
||||||
|
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
|
||||||
|
class AboutDialog extends StatelessWidget {
|
||||||
|
/// Creates an about box.
|
||||||
|
///
|
||||||
|
/// The arguments are all optional. The application name, if omitted, will be
|
||||||
|
/// derived from the nearest [Title] widget. The version, icon, and legalese
|
||||||
|
/// values default to the empty string.
|
||||||
|
const AboutDialog({
|
||||||
|
Key key,
|
||||||
|
this.applicationName,
|
||||||
|
this.applicationVersion,
|
||||||
|
this.applicationIcon,
|
||||||
|
this.applicationLegalese,
|
||||||
|
this.children,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
/// The name of the application.
|
||||||
|
///
|
||||||
|
/// Defaults to the value of [Title.title], if a [Title] widget can be found.
|
||||||
|
/// Otherwise, defaults to [Platform.resolvedExecutable].
|
||||||
|
final String applicationName;
|
||||||
|
|
||||||
|
/// The version of this build of the application.
|
||||||
|
///
|
||||||
|
/// This string is shown under the application name.
|
||||||
|
///
|
||||||
|
/// Defaults to the empty string.
|
||||||
|
final String applicationVersion;
|
||||||
|
|
||||||
|
/// The icon to show next to the application name.
|
||||||
|
///
|
||||||
|
/// By default no icon is shown.
|
||||||
|
///
|
||||||
|
/// Typically this will be an [ImageIcon] widget. It should honor the
|
||||||
|
/// [IconTheme]'s [IconThemeData.size].
|
||||||
|
final Widget applicationIcon;
|
||||||
|
|
||||||
|
/// A string to show in small print.
|
||||||
|
///
|
||||||
|
/// Typically this is a copyright notice.
|
||||||
|
///
|
||||||
|
/// Defaults to the empty string.
|
||||||
|
final String applicationLegalese;
|
||||||
|
|
||||||
|
/// Widgets to add to the dialog box after the name, version, and legalese.
|
||||||
|
///
|
||||||
|
/// This could include a link to a Web site, some descriptive text, credits,
|
||||||
|
/// or other information to show in the about box.
|
||||||
|
///
|
||||||
|
/// Defaults to nothing.
|
||||||
|
final List<Widget> children;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
assert(debugCheckHasMaterialLocalizations(context));
|
||||||
|
final String name = applicationName ?? _defaultApplicationName(context);
|
||||||
|
final String version =
|
||||||
|
applicationVersion ?? _defaultApplicationVersion(context);
|
||||||
|
final Widget icon = applicationIcon ?? _defaultApplicationIcon(context);
|
||||||
|
List<Widget> body = <Widget>[];
|
||||||
|
if (icon != null) {
|
||||||
|
body.add(
|
||||||
|
IconTheme(data: const IconThemeData(size: 45.0), child: icon),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.add(Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||||
|
child: ListBody(
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
child: Text(name, style: Theme.of(context).textTheme.title),
|
||||||
|
),
|
||||||
|
Text(version, style: Theme.of(context).textTheme.body1),
|
||||||
|
Text(applicationLegalese ?? '',
|
||||||
|
style: Theme.of(context).textTheme.caption),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
body = <Widget>[
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: body,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
if (children != null) body.addAll(children);
|
||||||
|
return AlertDialog(
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: ListBody(children: body),
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
FlatButton(
|
||||||
|
child:
|
||||||
|
Text(MaterialLocalizations.of(context).viewLicensesButtonLabel),
|
||||||
|
onPressed: () {
|
||||||
|
showLicensePage(
|
||||||
|
context: context,
|
||||||
|
applicationName: applicationName,
|
||||||
|
applicationVersion: applicationVersion,
|
||||||
|
applicationIcon: applicationIcon,
|
||||||
|
applicationLegalese: applicationLegalese,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
FlatButton(
|
||||||
|
child: Text(MaterialLocalizations.of(context).closeButtonLabel),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A page that shows licenses for software used by the application.
|
||||||
|
///
|
||||||
|
/// To show a [LicensePage], use [showLicensePage].
|
||||||
|
///
|
||||||
|
/// The [AboutDialog] shown by [showAboutDialog] and [AboutListTile] includes
|
||||||
|
/// a button that calls [showLicensePage].
|
||||||
|
///
|
||||||
|
/// The licenses shown on the [LicensePage] are those returned by the
|
||||||
|
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
|
||||||
|
class LicensePage extends StatefulWidget {
|
||||||
|
/// Creates a page that shows licenses for software used by the application.
|
||||||
|
///
|
||||||
|
/// The arguments are all optional. The application name, if omitted, will be
|
||||||
|
/// derived from the nearest [Title] widget. The version and legalese values
|
||||||
|
/// default to the empty string.
|
||||||
|
///
|
||||||
|
/// The licenses shown on the [LicensePage] are those returned by the
|
||||||
|
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
|
||||||
|
const LicensePage({
|
||||||
|
Key key,
|
||||||
|
this.applicationName,
|
||||||
|
this.applicationVersion,
|
||||||
|
this.applicationLegalese,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
/// The name of the application.
|
||||||
|
///
|
||||||
|
/// Defaults to the value of [Title.title], if a [Title] widget can be found.
|
||||||
|
/// Otherwise, defaults to [Platform.resolvedExecutable].
|
||||||
|
final String applicationName;
|
||||||
|
|
||||||
|
/// The version of this build of the application.
|
||||||
|
///
|
||||||
|
/// This string is shown under the application name.
|
||||||
|
///
|
||||||
|
/// Defaults to the empty string.
|
||||||
|
final String applicationVersion;
|
||||||
|
|
||||||
|
/// A string to show in small print.
|
||||||
|
///
|
||||||
|
/// Typically this is a copyright notice.
|
||||||
|
///
|
||||||
|
/// Defaults to the empty string.
|
||||||
|
final String applicationLegalese;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_LicensePageState createState() => _LicensePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LicensePageState extends State<LicensePage> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_initLicenses();
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Widget> _licenses = <Widget>[];
|
||||||
|
bool _loaded = false;
|
||||||
|
|
||||||
|
Future<void> _initLicenses() async {
|
||||||
|
int debugFlowId = -1;
|
||||||
|
assert(() {
|
||||||
|
final dev.Flow flow = dev.Flow.begin();
|
||||||
|
dev.Timeline.timeSync('_initLicenses()', () {}, flow: flow);
|
||||||
|
debugFlowId = flow.id;
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
await for (LicenseEntry license in LicenseRegistry.licenses) {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert(() {
|
||||||
|
dev.Timeline.timeSync('_initLicenses()', () {},
|
||||||
|
flow: dev.Flow.step(debugFlowId));
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
final List<LicenseParagraph> paragraphs =
|
||||||
|
await SchedulerBinding.instance.scheduleTask<List<LicenseParagraph>>(
|
||||||
|
license.paragraphs.toList,
|
||||||
|
Priority.animation,
|
||||||
|
debugLabel: 'License',
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
_licenses.add(const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 18.0),
|
||||||
|
child: Text(
|
||||||
|
'🍀', // That's U+1F340. Could also use U+2766 (❦) if U+1F340 doesn't work everywhere.
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
_licenses.add(Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
border: Border(bottom: BorderSide(width: 0.0))),
|
||||||
|
child: Text(
|
||||||
|
license.packages.join(', '),
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
for (LicenseParagraph paragraph in paragraphs) {
|
||||||
|
if (paragraph.indent == LicenseParagraph.centeredIndent) {
|
||||||
|
_licenses.add(Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 16.0),
|
||||||
|
child: Text(
|
||||||
|
paragraph.text,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
assert(paragraph.indent >= 0);
|
||||||
|
_licenses.add(Padding(
|
||||||
|
padding: EdgeInsetsDirectional.only(
|
||||||
|
top: 8.0, start: 16.0 * paragraph.indent),
|
||||||
|
child: Text(paragraph.text),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_loaded = true;
|
||||||
|
});
|
||||||
|
assert(() {
|
||||||
|
dev.Timeline.timeSync('Build scheduled', () {},
|
||||||
|
flow: dev.Flow.end(debugFlowId));
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
assert(debugCheckHasMaterialLocalizations(context));
|
||||||
|
final String name =
|
||||||
|
widget.applicationName ?? _defaultApplicationName(context);
|
||||||
|
final String version =
|
||||||
|
widget.applicationVersion ?? _defaultApplicationVersion(context);
|
||||||
|
final MaterialLocalizations localizations =
|
||||||
|
MaterialLocalizations.of(context);
|
||||||
|
final List<Widget> contents = <Widget>[
|
||||||
|
Text(name,
|
||||||
|
style: Theme.of(context).textTheme.headline,
|
||||||
|
textAlign: TextAlign.center),
|
||||||
|
Text(version,
|
||||||
|
style: Theme.of(context).textTheme.body1,
|
||||||
|
textAlign: TextAlign.center),
|
||||||
|
Container(height: 18.0),
|
||||||
|
Text(widget.applicationLegalese ?? '',
|
||||||
|
style: Theme.of(context).textTheme.caption,
|
||||||
|
textAlign: TextAlign.center),
|
||||||
|
Container(height: 18.0),
|
||||||
|
Text('Powered by Flutter',
|
||||||
|
style: Theme.of(context).textTheme.body1,
|
||||||
|
textAlign: TextAlign.center),
|
||||||
|
Container(height: 24.0),
|
||||||
|
];
|
||||||
|
contents.addAll(_licenses);
|
||||||
|
if (!_loaded) {
|
||||||
|
contents.add(const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 24.0),
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(localizations.licensesPageTitle),
|
||||||
|
),
|
||||||
|
// All of the licenses page text is English. We don't want localized text
|
||||||
|
// or text direction.
|
||||||
|
body: Localizations.override(
|
||||||
|
locale: const Locale('en', 'US'),
|
||||||
|
context: context,
|
||||||
|
child: DefaultTextStyle(
|
||||||
|
style: Theme.of(context).textTheme.caption,
|
||||||
|
child: SafeArea(
|
||||||
|
bottom: false,
|
||||||
|
child: Scrollbar(
|
||||||
|
child: ListView(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 8.0, vertical: 12.0),
|
||||||
|
children: contents,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _defaultApplicationName(BuildContext context) {
|
||||||
|
// This doesn't handle the case of the application's title dynamically
|
||||||
|
// changing. In theory, we should make Title expose the current application
|
||||||
|
// title using an InheritedWidget, and so forth. However, in practice, if
|
||||||
|
// someone really wants their application title to change dynamically, they
|
||||||
|
// can provide an explicit applicationName to the widgets defined in this
|
||||||
|
// file, instead of relying on the default.
|
||||||
|
final Title ancestorTitle = context.ancestorWidgetOfExactType(Title);
|
||||||
|
return ancestorTitle?.title ??
|
||||||
|
Platform.resolvedExecutable.split(Platform.pathSeparator).last;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _defaultApplicationVersion(BuildContext context) {
|
||||||
|
// TODO(ianh): Get this from the embedder somehow.
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _defaultApplicationIcon(BuildContext context) {
|
||||||
|
// TODO(ianh): Get this from the embedder somehow.
|
||||||
|
return null;
|
||||||
|
}
|
58
lib/common/functions/feedback.dart
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
final TextEditingController _feedbackController = TextEditingController();
|
||||||
|
|
||||||
|
void feedback(BuildContext context) {
|
||||||
|
// flutter defined function
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
// return object of type Dialog
|
||||||
|
return AlertDialog(
|
||||||
|
title: new Text("Feedback"),
|
||||||
|
content: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Text("We would love to hear your thoughts."),
|
||||||
|
Text("\nThis section needs to be fixed."),
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.fromLTRB(0, 20, 0, 0),
|
||||||
|
width: 200,
|
||||||
|
height: 70,
|
||||||
|
child: new TextField(
|
||||||
|
controller: _feedbackController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Enter your feedback here',
|
||||||
|
),
|
||||||
|
// obscureText: true,
|
||||||
|
autocorrect: true,
|
||||||
|
maxLines: 20,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.0,
|
||||||
|
color: Colors.grey[800],
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
onSubmitted: (_) {
|
||||||
|
submit(_feedbackController.text);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
new Container(
|
||||||
|
padding: const EdgeInsets.fromLTRB(0, 10, 0, 0),
|
||||||
|
child: new FlatButton(
|
||||||
|
child: new Text("Submit"),
|
||||||
|
onPressed: () {
|
||||||
|
submit(_feedbackController.text);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void submit(String feedback) {
|
||||||
|
debugPrint(feedback);
|
||||||
|
}
|
9
lib/common/functions/get_token.dart
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
Future<String> getToken() async {
|
||||||
|
SharedPreferences preferences = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
|
String getToken = preferences.getString("LastToken");
|
||||||
|
return getToken;
|
||||||
|
}
|
17
lib/common/functions/logout.dart
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:local_spend/common/apifunctions/request_logout_api.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
void logout(context) {
|
||||||
|
_clearLoginDetails().then((_) {
|
||||||
|
requestLogoutAPI();
|
||||||
|
Navigator.of(context).pushReplacementNamed('/LoginPage');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _clearLoginDetails() async {
|
||||||
|
SharedPreferences preferences = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
|
await preferences.setString('username', "");
|
||||||
|
await preferences.setString('password', "");
|
||||||
|
}
|
29
lib/common/functions/save_current_login.dart
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import 'package:local_spend/model/json/login_model.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
void saveCurrentLogin(Map responseJson, loginEmail) async {
|
||||||
|
SharedPreferences preferences = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
|
var user;
|
||||||
|
if ((responseJson != null && responseJson.isNotEmpty)) {
|
||||||
|
user = LoginModel.fromJson(responseJson).userName;
|
||||||
|
} else {
|
||||||
|
user = "";
|
||||||
|
}
|
||||||
|
var token = (responseJson != null && responseJson.isNotEmpty)
|
||||||
|
? LoginModel.fromJson(responseJson).token
|
||||||
|
: "";
|
||||||
|
var email = (loginEmail != null) ? loginEmail : "";
|
||||||
|
var userType = (responseJson != null && responseJson.isNotEmpty)
|
||||||
|
? LoginModel.fromJson(responseJson).userType
|
||||||
|
: "";
|
||||||
|
|
||||||
|
await preferences.setString(
|
||||||
|
'LastUser', (user != null && user.length > 0) ? user : "");
|
||||||
|
await preferences.setString(
|
||||||
|
'LastToken', (token != null && token.isNotEmpty) ? token : "");
|
||||||
|
await preferences.setString(
|
||||||
|
'LastEmail', (email != null && email.length > 0) ? email : "");
|
||||||
|
await preferences.setString('LastUserType',
|
||||||
|
(userType != null && userType.isNotEmpty) ? userType : "");
|
||||||
|
}
|
9
lib/common/functions/save_logout.dart
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
void saveLogout() async {
|
||||||
|
SharedPreferences preferences = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
|
await preferences.setString('LastUser', "");
|
||||||
|
await preferences.setString('LastToken', "");
|
||||||
|
await preferences.setString('LastEmail', "");
|
||||||
|
}
|
34
lib/common/functions/showDialogTwoButtons.dart
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
void showDialogTwoButtons(BuildContext context, String title, String message,
|
||||||
|
String buttonLabel1, String buttonLabel2, Function action) {
|
||||||
|
// flutter defined function
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
// return object of type Dialog
|
||||||
|
return AlertDialog(
|
||||||
|
title: new Text(title),
|
||||||
|
content: new Text(message),
|
||||||
|
actions: <Widget>[
|
||||||
|
// usually buttons at the bottom of the dialog
|
||||||
|
|
||||||
|
new FlatButton(
|
||||||
|
child: new Text(buttonLabel1),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
new FlatButton(
|
||||||
|
child: new Text(buttonLabel2),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
action(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
25
lib/common/functions/show_dialog_single_button.dart
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
void showDialogSingleButton(
|
||||||
|
BuildContext context, String title, String message, String buttonLabel) {
|
||||||
|
// flutter defined function
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
// return object of type Dialog
|
||||||
|
return AlertDialog(
|
||||||
|
title: new Text(title),
|
||||||
|
content: new Text(message),
|
||||||
|
actions: <Widget>[
|
||||||
|
// usually buttons at the bottom of the dialog
|
||||||
|
new FlatButton(
|
||||||
|
child: new Text(buttonLabel),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
73
lib/common/platform/platform_scaffold.dart
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class PlatformScaffold extends StatelessWidget {
|
||||||
|
PlatformScaffold(
|
||||||
|
{this.key,
|
||||||
|
this.appBar,
|
||||||
|
this.body,
|
||||||
|
this.floatingActionButton,
|
||||||
|
this.floatingActionButtonLocation,
|
||||||
|
this.floatingActionButtonAnimator,
|
||||||
|
this.persistentFooterButtons,
|
||||||
|
this.drawer,
|
||||||
|
this.endDrawer,
|
||||||
|
this.bottomNavigationBar,
|
||||||
|
this.backgroundColor,
|
||||||
|
this.resizeToAvoidBottomPadding = true,
|
||||||
|
this.primary = true})
|
||||||
|
: assert(primary != null),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final Key key;
|
||||||
|
final PreferredSizeWidget appBar;
|
||||||
|
final Widget body;
|
||||||
|
final Widget floatingActionButton;
|
||||||
|
final FloatingActionButtonLocation floatingActionButtonLocation;
|
||||||
|
final FloatingActionButtonAnimator floatingActionButtonAnimator;
|
||||||
|
final List<Widget> persistentFooterButtons;
|
||||||
|
final Widget drawer;
|
||||||
|
final Widget endDrawer;
|
||||||
|
final Widget bottomNavigationBar;
|
||||||
|
final Color backgroundColor;
|
||||||
|
final bool resizeToAvoidBottomPadding;
|
||||||
|
final bool primary;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Platform.isIOS
|
||||||
|
? Scaffold(
|
||||||
|
key: key,
|
||||||
|
appBar: appBar,
|
||||||
|
body: body,
|
||||||
|
floatingActionButton: floatingActionButton,
|
||||||
|
persistentFooterButtons: persistentFooterButtons,
|
||||||
|
floatingActionButtonLocation: floatingActionButtonLocation,
|
||||||
|
floatingActionButtonAnimator: floatingActionButtonAnimator,
|
||||||
|
drawer: endDrawer,
|
||||||
|
endDrawer: drawer,
|
||||||
|
bottomNavigationBar: bottomNavigationBar,
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
|
||||||
|
primary: primary,
|
||||||
|
)
|
||||||
|
: Scaffold(
|
||||||
|
key: key,
|
||||||
|
appBar: appBar,
|
||||||
|
body: body,
|
||||||
|
floatingActionButton: floatingActionButton,
|
||||||
|
persistentFooterButtons: persistentFooterButtons,
|
||||||
|
floatingActionButtonLocation: floatingActionButtonLocation,
|
||||||
|
floatingActionButtonAnimator: floatingActionButtonAnimator,
|
||||||
|
drawer: drawer,
|
||||||
|
endDrawer: endDrawer,
|
||||||
|
bottomNavigationBar: bottomNavigationBar,
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
|
||||||
|
primary: primary,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
40
lib/common/widgets/animatedGradientButton.dart
Normal 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])),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
73
lib/common/widgets/charts/auto_label.dart
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/// Donut chart with labels example. This is a simple pie chart with a hole in
|
||||||
|
/// the middle.
|
||||||
|
import 'package:charts_flutter/flutter.dart' as charts;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class DonutAutoLabelChart extends StatelessWidget {
|
||||||
|
DonutAutoLabelChart(this.seriesList, {this.animate});
|
||||||
|
|
||||||
|
/// Creates a [PieChart] with sample data and no transition.
|
||||||
|
factory DonutAutoLabelChart.withSampleData() {
|
||||||
|
return new DonutAutoLabelChart(
|
||||||
|
_createSampleData(),
|
||||||
|
// Disable animations for image tests.
|
||||||
|
animate: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<charts.Series> seriesList;
|
||||||
|
final bool animate;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new charts.PieChart(seriesList,
|
||||||
|
animate: animate,
|
||||||
|
// Configure the width of the pie slices to 60px. The remaining space in
|
||||||
|
// the chart will be left as a hole in the center.
|
||||||
|
//
|
||||||
|
// [ArcLabelDecorator] will automatically position the label inside the
|
||||||
|
// arc if the label will fit. If the label will not fit, it will draw
|
||||||
|
// outside of the arc with a leader line. Labels can always display
|
||||||
|
// inside or outside using [LabelPosition].
|
||||||
|
//
|
||||||
|
// Text style for inside / outside can be controlled independently by
|
||||||
|
// setting [insideLabelStyleSpec] and [outsideLabelStyleSpec].
|
||||||
|
//
|
||||||
|
// Example configuring different styles for inside/outside:
|
||||||
|
// new charts.ArcLabelDecorator(
|
||||||
|
// insideLabelStyleSpec: new charts.TextStyleSpec(...),
|
||||||
|
// outsideLabelStyleSpec: new charts.TextStyleSpec(...)),
|
||||||
|
defaultRenderer: new charts.ArcRendererConfig(
|
||||||
|
arcWidth: 60,
|
||||||
|
arcRendererDecorators: [new charts.ArcLabelDecorator()]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create one series with sample hard coded data.
|
||||||
|
static List<charts.Series<LinearSales, String>> _createSampleData() {
|
||||||
|
final data = [
|
||||||
|
new LinearSales("Other", 17),
|
||||||
|
new LinearSales("Mars", 26),
|
||||||
|
new LinearSales("Morecambe", 35),
|
||||||
|
new LinearSales("Lancaster", 48),
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
new charts.Series<LinearSales, String>(
|
||||||
|
id: 'Sales',
|
||||||
|
domainFn: (LinearSales sales, _) => sales.key,
|
||||||
|
measureFn: (LinearSales sales, _) => sales.sales,
|
||||||
|
data: data,
|
||||||
|
// Set a label accessor to control the text of the arc label.
|
||||||
|
labelAccessorFn: (LinearSales row, _) => '${row.key}: ${row.sales}',
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sample linear data type.
|
||||||
|
class LinearSales {
|
||||||
|
LinearSales(this.key, this.sales);
|
||||||
|
|
||||||
|
final String key;
|
||||||
|
final int sales;
|
||||||
|
}
|
18
lib/common/widgets/charts/chart_builder.dart
Normal 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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
54
lib/common/widgets/charts/donut_chart.dart
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
/// Donut chart example. This is a simple pie chart with a hole in the middle.
|
||||||
|
import 'package:charts_flutter/flutter.dart' as charts;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class DonutPieChart extends StatelessWidget {
|
||||||
|
DonutPieChart(this.seriesList, {this.animate});
|
||||||
|
|
||||||
|
/// Creates a [PieChart] with sample data and no transition.
|
||||||
|
factory DonutPieChart.withSampleData() {
|
||||||
|
return new DonutPieChart(
|
||||||
|
_createSampleData(),
|
||||||
|
animate: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<charts.Series> seriesList;
|
||||||
|
final bool animate;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new charts.PieChart(seriesList,
|
||||||
|
animate: animate,
|
||||||
|
// Configure the width of the pie slices to 60px. The remaining space in
|
||||||
|
// the chart will be left as a hole in the center.
|
||||||
|
defaultRenderer: new charts.ArcRendererConfig(arcWidth: 60));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create one series with sample hard coded data.
|
||||||
|
static List<charts.Series<LinearSales, int>> _createSampleData() {
|
||||||
|
final data = [
|
||||||
|
new LinearSales(0, 100),
|
||||||
|
new LinearSales(1, 75),
|
||||||
|
new LinearSales(2, 25),
|
||||||
|
new LinearSales(3, 5),
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
new charts.Series<LinearSales, int>(
|
||||||
|
id: 'Sales',
|
||||||
|
domainFn: (LinearSales sales, _) => sales.year,
|
||||||
|
measureFn: (LinearSales sales, _) => sales.sales,
|
||||||
|
data: data,
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sample linear data type.
|
||||||
|
class LinearSales {
|
||||||
|
LinearSales(this.year, this.sales);
|
||||||
|
|
||||||
|
final int year;
|
||||||
|
final int sales;
|
||||||
|
}
|
80
lib/common/widgets/charts/grouped_bar_chart.dart
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
/// Bar chart example
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:charts_flutter/flutter.dart' as charts;
|
||||||
|
|
||||||
|
class GroupedBarChart extends StatelessWidget {
|
||||||
|
GroupedBarChart(this.seriesList, {this.animate});
|
||||||
|
|
||||||
|
factory GroupedBarChart.withSampleData() {
|
||||||
|
return new GroupedBarChart(
|
||||||
|
_createSampleData(),
|
||||||
|
// Disable animations for image tests.
|
||||||
|
animate: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<charts.Series> seriesList;
|
||||||
|
final bool animate;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new charts.BarChart(
|
||||||
|
seriesList,
|
||||||
|
animate: animate,
|
||||||
|
barGroupingType: charts.BarGroupingType.grouped,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create series list with multiple series
|
||||||
|
static List<charts.Series<OrdinalSales, String>> _createSampleData() {
|
||||||
|
final desktopSalesData = [
|
||||||
|
new OrdinalSales('2014', 5),
|
||||||
|
new OrdinalSales('2015', 25),
|
||||||
|
new OrdinalSales('2016', 100),
|
||||||
|
new OrdinalSales('2017', 75),
|
||||||
|
];
|
||||||
|
|
||||||
|
final tabletSalesData = [
|
||||||
|
new OrdinalSales('2014', 25),
|
||||||
|
new OrdinalSales('2015', 50),
|
||||||
|
new OrdinalSales('2016', 10),
|
||||||
|
new OrdinalSales('2017', 20),
|
||||||
|
];
|
||||||
|
|
||||||
|
final mobileSalesData = [
|
||||||
|
new OrdinalSales('2014', 10),
|
||||||
|
new OrdinalSales('2015', 15),
|
||||||
|
new OrdinalSales('2016', 50),
|
||||||
|
new OrdinalSales('2017', 45),
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
new charts.Series<OrdinalSales, String>(
|
||||||
|
id: 'Desktop',
|
||||||
|
domainFn: (OrdinalSales sales, _) => sales.year,
|
||||||
|
measureFn: (OrdinalSales sales, _) => sales.sales,
|
||||||
|
data: desktopSalesData,
|
||||||
|
),
|
||||||
|
new charts.Series<OrdinalSales, String>(
|
||||||
|
id: 'Tablet',
|
||||||
|
domainFn: (OrdinalSales sales, _) => sales.year,
|
||||||
|
measureFn: (OrdinalSales sales, _) => sales.sales,
|
||||||
|
data: tabletSalesData,
|
||||||
|
),
|
||||||
|
new charts.Series<OrdinalSales, String>(
|
||||||
|
id: 'Mobile',
|
||||||
|
domainFn: (OrdinalSales sales, _) => sales.year,
|
||||||
|
measureFn: (OrdinalSales sales, _) => sales.sales,
|
||||||
|
data: mobileSalesData,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sample ordinal data type.
|
||||||
|
class OrdinalSales {
|
||||||
|
OrdinalSales(this.year, this.sales);
|
||||||
|
|
||||||
|
final String year;
|
||||||
|
final int sales;
|
||||||
|
}
|
94
lib/common/widgets/charts/numeric_line_bar_combo.dart
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
/// Example of a numeric combo chart with two series rendered as bars, and a
|
||||||
|
/// third rendered as a line.
|
||||||
|
import 'package:charts_flutter/flutter.dart' as charts;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class NumericComboLineBarChart extends StatelessWidget {
|
||||||
|
NumericComboLineBarChart(this.seriesList, {this.animate});
|
||||||
|
|
||||||
|
/// Creates a [LineChart] with sample data and no transition.
|
||||||
|
factory NumericComboLineBarChart.withSampleData() {
|
||||||
|
return new NumericComboLineBarChart(
|
||||||
|
_createSampleData(),
|
||||||
|
// Disable animations for image tests.
|
||||||
|
animate: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<charts.Series> seriesList;
|
||||||
|
final bool animate;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new charts.NumericComboChart(seriesList,
|
||||||
|
animate: animate,
|
||||||
|
// Configure the default renderer as a line renderer. This will be used
|
||||||
|
// for any series that does not define a rendererIdKey.
|
||||||
|
defaultRenderer: new charts.LineRendererConfig(),
|
||||||
|
// Custom renderer configuration for the bar series.
|
||||||
|
customSeriesRenderers: [
|
||||||
|
new charts.BarRendererConfig(
|
||||||
|
// ID used to link series to this renderer.
|
||||||
|
customRendererId: 'customBar')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create one series with sample hard coded data.
|
||||||
|
static List<charts.Series<LinearSales, int>> _createSampleData() {
|
||||||
|
final desktopSalesData = [
|
||||||
|
new LinearSales(0, 5),
|
||||||
|
new LinearSales(1, 25),
|
||||||
|
new LinearSales(2, 100),
|
||||||
|
new LinearSales(3, 75),
|
||||||
|
];
|
||||||
|
|
||||||
|
final tableSalesData = [
|
||||||
|
new LinearSales(0, 5),
|
||||||
|
new LinearSales(1, 25),
|
||||||
|
new LinearSales(2, 100),
|
||||||
|
new LinearSales(3, 75),
|
||||||
|
];
|
||||||
|
|
||||||
|
final mobileSalesData = [
|
||||||
|
new LinearSales(0, 10),
|
||||||
|
new LinearSales(1, 50),
|
||||||
|
new LinearSales(2, 200),
|
||||||
|
new LinearSales(3, 150),
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
new charts.Series<LinearSales, int>(
|
||||||
|
id: 'Desktop',
|
||||||
|
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
|
||||||
|
domainFn: (LinearSales sales, _) => sales.year,
|
||||||
|
measureFn: (LinearSales sales, _) => sales.sales,
|
||||||
|
data: desktopSalesData,
|
||||||
|
)
|
||||||
|
// Configure our custom bar renderer for this series.
|
||||||
|
..setAttribute(charts.rendererIdKey, 'customBar'),
|
||||||
|
new charts.Series<LinearSales, int>(
|
||||||
|
id: 'Tablet',
|
||||||
|
colorFn: (_, __) => charts.MaterialPalette.red.shadeDefault,
|
||||||
|
domainFn: (LinearSales sales, _) => sales.year,
|
||||||
|
measureFn: (LinearSales sales, _) => sales.sales,
|
||||||
|
data: tableSalesData,
|
||||||
|
)
|
||||||
|
// Configure our custom bar renderer for this series.
|
||||||
|
..setAttribute(charts.rendererIdKey, 'customBar'),
|
||||||
|
new charts.Series<LinearSales, int>(
|
||||||
|
id: 'Mobile',
|
||||||
|
colorFn: (_, __) => charts.MaterialPalette.green.shadeDefault,
|
||||||
|
domainFn: (LinearSales sales, _) => sales.year,
|
||||||
|
measureFn: (LinearSales sales, _) => sales.sales,
|
||||||
|
data: mobileSalesData),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sample linear data type.
|
||||||
|
class LinearSales {
|
||||||
|
LinearSales(this.year, this.sales);
|
||||||
|
|
||||||
|
final int year;
|
||||||
|
final int sales;
|
||||||
|
}
|
68
lib/common/widgets/charts/outside_label.dart
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
/// Simple pie chart with outside labels example.
|
||||||
|
import 'package:charts_flutter/flutter.dart' as charts;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class PieOutsideLabelChart extends StatelessWidget {
|
||||||
|
PieOutsideLabelChart(this.seriesList, {this.animate});
|
||||||
|
|
||||||
|
/// Creates a [PieChart] with sample data and no transition.
|
||||||
|
factory PieOutsideLabelChart.withSampleData() {
|
||||||
|
return new PieOutsideLabelChart(
|
||||||
|
_createSampleData(),
|
||||||
|
// Disable animations for image tests.
|
||||||
|
animate: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<charts.Series> seriesList;
|
||||||
|
final bool animate;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new charts.PieChart(seriesList,
|
||||||
|
animate: animate,
|
||||||
|
// Add an [ArcLabelDecorator] configured to render labels outside of the
|
||||||
|
// arc with a leader line.
|
||||||
|
//
|
||||||
|
// Text style for inside / outside can be controlled independently by
|
||||||
|
// setting [insideLabelStyleSpec] and [outsideLabelStyleSpec].
|
||||||
|
//
|
||||||
|
// Example configuring different styles for inside/outside:
|
||||||
|
// new charts.ArcLabelDecorator(
|
||||||
|
// insideLabelStyleSpec: new charts.TextStyleSpec(...),
|
||||||
|
// outsideLabelStyleSpec: new charts.TextStyleSpec(...)),
|
||||||
|
defaultRenderer: new charts.ArcRendererConfig(arcRendererDecorators: [
|
||||||
|
new charts.ArcLabelDecorator(
|
||||||
|
labelPosition: charts.ArcLabelPosition.outside)
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create one series with sample hard coded data.
|
||||||
|
static List<charts.Series<LinearSales, int>> _createSampleData() {
|
||||||
|
final data = [
|
||||||
|
new LinearSales(0, 23),
|
||||||
|
new LinearSales(1, 44),
|
||||||
|
new LinearSales(2, 25),
|
||||||
|
new LinearSales(3, 15),
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
new charts.Series<LinearSales, int>(
|
||||||
|
id: 'Sales',
|
||||||
|
domainFn: (LinearSales sales, _) => sales.year,
|
||||||
|
measureFn: (LinearSales sales, _) => sales.sales,
|
||||||
|
data: data,
|
||||||
|
// Set a label accessor to control the text of the arc label.
|
||||||
|
labelAccessorFn: (LinearSales row, _) => '${row.year}: ${row.sales}',
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sample linear data type.
|
||||||
|
class LinearSales {
|
||||||
|
LinearSales(this.year, this.sales);
|
||||||
|
|
||||||
|
final int year;
|
||||||
|
final int sales;
|
||||||
|
}
|
135
lib/common/widgets/charts/scatter_bucketingAxis_legend.dart
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
/// Example of a scatter plot chart with a bucketing measure axis and a legend.
|
||||||
|
///
|
||||||
|
/// A bucketing measure axis positions all values beneath a certain threshold
|
||||||
|
/// into a reserved space on the axis range. The label for the bucket line will
|
||||||
|
/// be drawn in the middle of the bucket range, rather than aligned with the
|
||||||
|
/// gridline for that value's position on the scale.
|
||||||
|
import 'package:charts_flutter/flutter.dart' as charts;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class BucketingAxisScatterPlotChart extends StatelessWidget {
|
||||||
|
BucketingAxisScatterPlotChart(this.seriesList, {this.animate});
|
||||||
|
|
||||||
|
/// Creates a [ScatterPlotChart] with sample data and no transition.
|
||||||
|
factory BucketingAxisScatterPlotChart.withSampleData() {
|
||||||
|
return new BucketingAxisScatterPlotChart(
|
||||||
|
_createSampleData(),
|
||||||
|
// Disable animations for image tests.
|
||||||
|
animate: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<charts.Series> seriesList;
|
||||||
|
final bool animate;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new charts.ScatterPlotChart(seriesList,
|
||||||
|
// Set up a bucketing axis that will place all values below 0.1 (10%)
|
||||||
|
// into a bucket at the bottom of the chart.
|
||||||
|
//
|
||||||
|
// Configure a tick count of 3 so that we get 100%, 50%, and the
|
||||||
|
// threshold.
|
||||||
|
primaryMeasureAxis: new charts.BucketingAxisSpec(
|
||||||
|
threshold: 0.1,
|
||||||
|
tickProviderSpec: new charts.BucketingNumericTickProviderSpec(
|
||||||
|
desiredTickCount: 3)),
|
||||||
|
// Add a series legend to display the series names.
|
||||||
|
behaviors: [
|
||||||
|
new charts.SeriesLegend(position: charts.BehaviorPosition.end),
|
||||||
|
],
|
||||||
|
animate: animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create one series with sample hard coded data.
|
||||||
|
static List<charts.Series<LinearSales, int>> _createSampleData() {
|
||||||
|
final myFakeDesktopData = [
|
||||||
|
new LinearSales(52, 0.75, 14.0),
|
||||||
|
];
|
||||||
|
|
||||||
|
final myFakeTabletData = [
|
||||||
|
new LinearSales(45, 0.3, 18.0),
|
||||||
|
];
|
||||||
|
|
||||||
|
final myFakeMobileData = [
|
||||||
|
new LinearSales(56, 0.8, 17.0),
|
||||||
|
];
|
||||||
|
|
||||||
|
final myFakeChromebookData = [
|
||||||
|
new LinearSales(25, 0.6, 13.0),
|
||||||
|
];
|
||||||
|
|
||||||
|
final myFakeHomeData = [
|
||||||
|
new LinearSales(34, 0.5, 15.0),
|
||||||
|
];
|
||||||
|
|
||||||
|
final myFakeOtherData = [
|
||||||
|
new LinearSales(10, 0.25, 15.0),
|
||||||
|
new LinearSales(12, 0.075, 14.0),
|
||||||
|
new LinearSales(13, 0.225, 15.0),
|
||||||
|
new LinearSales(16, 0.03, 14.0),
|
||||||
|
new LinearSales(24, 0.04, 13.0),
|
||||||
|
new LinearSales(37, 0.1, 14.5),
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
new charts.Series<LinearSales, int>(
|
||||||
|
id: 'Cheese',
|
||||||
|
colorFn: (LinearSales sales, _) =>
|
||||||
|
charts.MaterialPalette.blue.shadeDefault,
|
||||||
|
domainFn: (LinearSales sales, _) => sales.year,
|
||||||
|
measureFn: (LinearSales sales, _) => sales.revenueShare,
|
||||||
|
radiusPxFn: (LinearSales sales, _) => sales.radius,
|
||||||
|
data: myFakeDesktopData),
|
||||||
|
new charts.Series<LinearSales, int>(
|
||||||
|
id: 'Carrots',
|
||||||
|
colorFn: (LinearSales sales, _) =>
|
||||||
|
charts.MaterialPalette.red.shadeDefault,
|
||||||
|
domainFn: (LinearSales sales, _) => sales.year,
|
||||||
|
measureFn: (LinearSales sales, _) => sales.revenueShare,
|
||||||
|
radiusPxFn: (LinearSales sales, _) => sales.radius,
|
||||||
|
data: myFakeTabletData),
|
||||||
|
new charts.Series<LinearSales, int>(
|
||||||
|
id: 'Cucumbers',
|
||||||
|
colorFn: (LinearSales sales, _) =>
|
||||||
|
charts.MaterialPalette.green.shadeDefault,
|
||||||
|
domainFn: (LinearSales sales, _) => sales.year,
|
||||||
|
measureFn: (LinearSales sales, _) => sales.revenueShare,
|
||||||
|
radiusPxFn: (LinearSales sales, _) => sales.radius,
|
||||||
|
data: myFakeMobileData),
|
||||||
|
new charts.Series<LinearSales, int>(
|
||||||
|
id: 'Crayons',
|
||||||
|
colorFn: (LinearSales sales, _) =>
|
||||||
|
charts.MaterialPalette.purple.shadeDefault,
|
||||||
|
domainFn: (LinearSales sales, _) => sales.year,
|
||||||
|
measureFn: (LinearSales sales, _) => sales.revenueShare,
|
||||||
|
radiusPxFn: (LinearSales sales, _) => sales.radius,
|
||||||
|
data: myFakeChromebookData),
|
||||||
|
new charts.Series<LinearSales, int>(
|
||||||
|
id: 'Celery',
|
||||||
|
colorFn: (LinearSales sales, _) =>
|
||||||
|
charts.MaterialPalette.indigo.shadeDefault,
|
||||||
|
domainFn: (LinearSales sales, _) => sales.year,
|
||||||
|
measureFn: (LinearSales sales, _) => sales.revenueShare,
|
||||||
|
radiusPxFn: (LinearSales sales, _) => sales.radius,
|
||||||
|
data: myFakeHomeData),
|
||||||
|
new charts.Series<LinearSales, int>(
|
||||||
|
id: 'Cauliflower',
|
||||||
|
colorFn: (LinearSales sales, _) =>
|
||||||
|
charts.MaterialPalette.gray.shadeDefault,
|
||||||
|
domainFn: (LinearSales sales, _) => sales.year,
|
||||||
|
measureFn: (LinearSales sales, _) => sales.revenueShare,
|
||||||
|
radiusPxFn: (LinearSales sales, _) => sales.radius,
|
||||||
|
data: myFakeOtherData),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sample linear data type.
|
||||||
|
class LinearSales {
|
||||||
|
LinearSales(this.year, this.revenueShare, this.radius);
|
||||||
|
|
||||||
|
final int year;
|
||||||
|
final double revenueShare;
|
||||||
|
final double radius;
|
||||||
|
}
|
128
lib/common/widgets/charts/series_legend_with_measures.dart
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
/// Bar chart with example of a legend with customized position, justification,
|
||||||
|
/// desired max rows, and padding. These options are shown as an example of how
|
||||||
|
/// to use the customizations, they do not necessary have to be used together in
|
||||||
|
/// this way. Choosing [end] as the position does not require the justification
|
||||||
|
/// to also be [endDrawArea].
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:charts_flutter/flutter.dart' as charts;
|
||||||
|
|
||||||
|
/// Example that shows how to build a series legend that shows measure values
|
||||||
|
/// when a datum is selected.
|
||||||
|
///
|
||||||
|
/// Also shows the option to provide a custom measure formatter.
|
||||||
|
class LegendWithMeasures extends StatelessWidget {
|
||||||
|
LegendWithMeasures(this.seriesList, {this.animate});
|
||||||
|
|
||||||
|
factory LegendWithMeasures.withSampleData() {
|
||||||
|
return new LegendWithMeasures(
|
||||||
|
_createSampleData(),
|
||||||
|
// Disable animations for image tests.
|
||||||
|
animate: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<charts.Series> seriesList;
|
||||||
|
final bool animate;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new charts.BarChart(
|
||||||
|
seriesList,
|
||||||
|
animate: animate,
|
||||||
|
barGroupingType: charts.BarGroupingType.grouped,
|
||||||
|
// Add the legend behavior to the chart to turn on legends.
|
||||||
|
// This example shows how to optionally show measure and provide a custom
|
||||||
|
// formatter.
|
||||||
|
behaviors: [
|
||||||
|
new charts.SeriesLegend(
|
||||||
|
// Positions for "start" and "end" will be left and right respectively
|
||||||
|
// for widgets with a build context that has directionality ltr.
|
||||||
|
// For rtl, "start" and "end" will be right and left respectively.
|
||||||
|
// Since this example has directionality of ltr, the legend is
|
||||||
|
// positioned on the right side of the chart.
|
||||||
|
position: charts.BehaviorPosition.end,
|
||||||
|
// By default, if the position of the chart is on the left or right of
|
||||||
|
// the chart, [horizontalFirst] is set to false. This means that the
|
||||||
|
// legend entries will grow as new rows first instead of a new column.
|
||||||
|
horizontalFirst: false,
|
||||||
|
// This defines the padding around each legend entry.
|
||||||
|
cellPadding: new EdgeInsets.only(right: 4.0, bottom: 4.0),
|
||||||
|
// Set show measures to true to display measures in series legend,
|
||||||
|
// when the datum is selected.
|
||||||
|
showMeasures: true,
|
||||||
|
// Optionally provide a measure formatter to format the measure value.
|
||||||
|
// If none is specified the value is formatted as a decimal.
|
||||||
|
measureFormatter: (num value) {
|
||||||
|
return value == null ? '-' : '${value}k';
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create series list with multiple series
|
||||||
|
static List<charts.Series<OrdinalSales, String>> _createSampleData() {
|
||||||
|
final desktopSalesData = [
|
||||||
|
new OrdinalSales('2014', 5),
|
||||||
|
new OrdinalSales('2015', 25),
|
||||||
|
new OrdinalSales('2016', 100),
|
||||||
|
new OrdinalSales('2017', 75),
|
||||||
|
];
|
||||||
|
|
||||||
|
final tabletSalesData = [
|
||||||
|
new OrdinalSales('2014', 25),
|
||||||
|
new OrdinalSales('2015', 50),
|
||||||
|
// Purposely have a null data for 2016 to show the null value format.
|
||||||
|
new OrdinalSales('2017', 20),
|
||||||
|
];
|
||||||
|
|
||||||
|
final mobileSalesData = [
|
||||||
|
new OrdinalSales('2014', 10),
|
||||||
|
new OrdinalSales('2015', 15),
|
||||||
|
new OrdinalSales('2016', 50),
|
||||||
|
new OrdinalSales('2017', 45),
|
||||||
|
];
|
||||||
|
|
||||||
|
final otherSalesData = [
|
||||||
|
new OrdinalSales('2014', 20),
|
||||||
|
new OrdinalSales('2015', 35),
|
||||||
|
new OrdinalSales('2016', 15),
|
||||||
|
new OrdinalSales('2017', 10),
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
new charts.Series<OrdinalSales, String>(
|
||||||
|
id: 'Lancaster',
|
||||||
|
domainFn: (OrdinalSales sales, _) => sales.year,
|
||||||
|
measureFn: (OrdinalSales sales, _) => sales.sales,
|
||||||
|
data: desktopSalesData,
|
||||||
|
),
|
||||||
|
new charts.Series<OrdinalSales, String>(
|
||||||
|
id: 'Morecambe',
|
||||||
|
domainFn: (OrdinalSales sales, _) => sales.year,
|
||||||
|
measureFn: (OrdinalSales sales, _) => sales.sales,
|
||||||
|
data: tabletSalesData,
|
||||||
|
),
|
||||||
|
new charts.Series<OrdinalSales, String>(
|
||||||
|
id: 'Mars',
|
||||||
|
domainFn: (OrdinalSales sales, _) => sales.year,
|
||||||
|
measureFn: (OrdinalSales sales, _) => sales.sales,
|
||||||
|
data: mobileSalesData,
|
||||||
|
),
|
||||||
|
new charts.Series<OrdinalSales, String>(
|
||||||
|
id: 'Other',
|
||||||
|
domainFn: (OrdinalSales sales, _) => sales.year,
|
||||||
|
measureFn: (OrdinalSales sales, _) => sales.sales,
|
||||||
|
data: otherSalesData,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sample ordinal data type.
|
||||||
|
class OrdinalSales {
|
||||||
|
OrdinalSales(this.year, this.sales);
|
||||||
|
|
||||||
|
final String year;
|
||||||
|
final int sales;
|
||||||
|
}
|
59
lib/common/widgets/charts/time_series_simple.dart
Normal 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;
|
||||||
|
}
|
403
lib/common/widgets/custom_checkbox.dart
Normal file
|
@ -0,0 +1,403 @@
|
||||||
|
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// A material design checkbox.
|
||||||
|
///
|
||||||
|
/// The checkbox itself does not maintain any state. Instead, when the state of
|
||||||
|
/// the checkbox changes, the widget calls the [onChanged] callback. Most
|
||||||
|
/// widgets that use a checkbox will listen for the [onChanged] callback and
|
||||||
|
/// rebuild the checkbox with a new [value] to update the visual appearance of
|
||||||
|
/// the checkbox.
|
||||||
|
///
|
||||||
|
/// The checkbox can optionally display three values - true, false, and null -
|
||||||
|
/// if [tristate] is true. When [value] is null a dash is displayed. By default
|
||||||
|
/// [tristate] is false and the checkbox's [value] must be true or false.
|
||||||
|
///
|
||||||
|
/// Requires one of its ancestors to be a [Material] widget.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [CheckboxListTile], which combines this widget with a [ListTile] so that
|
||||||
|
/// you can give the checkbox a label.
|
||||||
|
/// * [Switch], a widget with semantics similar to [CustomCheckbox].
|
||||||
|
/// * [Radio], for selecting among a set of explicit values.
|
||||||
|
/// * [Slider], for selecting a value in a range.
|
||||||
|
/// * <https://material.io/design/components/selection-controls.html#checkboxes>
|
||||||
|
/// * <https://material.io/design/components/lists.html#types>
|
||||||
|
class CustomCheckbox extends StatefulWidget {
|
||||||
|
/// Creates a material design checkbox.
|
||||||
|
///
|
||||||
|
/// The checkbox itself does not maintain any state. Instead, when the state of
|
||||||
|
/// the checkbox changes, the widget calls the [onChanged] callback. Most
|
||||||
|
/// widgets that use a checkbox will listen for the [onChanged] callback and
|
||||||
|
/// rebuild the checkbox with a new [value] to update the visual appearance of
|
||||||
|
/// the checkbox.
|
||||||
|
///
|
||||||
|
/// The following arguments are required:
|
||||||
|
///
|
||||||
|
/// * [value], which determines whether the checkbox is checked. The [value]
|
||||||
|
/// can only be null if [tristate] is true.
|
||||||
|
/// * [onChanged], which is called when the value of the checkbox should
|
||||||
|
/// change. It can be set to null to disable the checkbox.
|
||||||
|
///
|
||||||
|
/// The value of [tristate] must not be null.
|
||||||
|
const CustomCheckbox({
|
||||||
|
Key key,
|
||||||
|
@required this.value,
|
||||||
|
this.tristate = false,
|
||||||
|
@required this.onChanged,
|
||||||
|
this.activeColor,
|
||||||
|
this.checkColor,
|
||||||
|
this.materialTapTargetSize,
|
||||||
|
this.useTapTarget = true,
|
||||||
|
}) : assert(tristate != null),
|
||||||
|
assert(tristate || value != null),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
final bool useTapTarget;
|
||||||
|
|
||||||
|
/// Whether this checkbox is checked.
|
||||||
|
///
|
||||||
|
/// This property must not be null.
|
||||||
|
final bool value;
|
||||||
|
|
||||||
|
/// Called when the value of the checkbox should change.
|
||||||
|
///
|
||||||
|
/// The checkbox passes the new value to the callback but does not actually
|
||||||
|
/// change state until the parent widget rebuilds the checkbox with the new
|
||||||
|
/// value.
|
||||||
|
///
|
||||||
|
/// If this callback is null, the checkbox will be displayed as disabled
|
||||||
|
/// and will not respond to input gestures.
|
||||||
|
///
|
||||||
|
/// When the checkbox is tapped, if [tristate] is false (the default) then
|
||||||
|
/// the [onChanged] callback will be applied to `!value`. If [tristate] is
|
||||||
|
/// true this callback cycle from false to true to null.
|
||||||
|
///
|
||||||
|
/// The callback provided to [onChanged] should update the state of the parent
|
||||||
|
/// [StatefulWidget] using the [State.setState] method, so that the parent
|
||||||
|
/// gets rebuilt; for example:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// Checkbox(
|
||||||
|
/// value: _throwShotAway,
|
||||||
|
/// onChanged: (bool newValue) {
|
||||||
|
/// setState(() {
|
||||||
|
/// _throwShotAway = newValue;
|
||||||
|
/// });
|
||||||
|
/// },
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
final ValueChanged<bool> onChanged;
|
||||||
|
|
||||||
|
/// The color to use when this checkbox is checked.
|
||||||
|
///
|
||||||
|
/// Defaults to [ThemeData.toggleableActiveColor].
|
||||||
|
final Color activeColor;
|
||||||
|
|
||||||
|
/// The color to use for the check icon when this checkbox is checked
|
||||||
|
///
|
||||||
|
/// Defaults to Color(0xFFFFFFFF)
|
||||||
|
final Color checkColor;
|
||||||
|
|
||||||
|
/// If true the checkbox's [value] can be true, false, or null.
|
||||||
|
///
|
||||||
|
/// Checkbox displays a dash when its value is null.
|
||||||
|
///
|
||||||
|
/// When a tri-state checkbox is tapped its [onChanged] callback will be
|
||||||
|
/// applied to true if the current value is null or false, false otherwise.
|
||||||
|
/// Typically tri-state checkboxes are disabled (the onChanged callback is
|
||||||
|
/// null) so they don't respond to taps.
|
||||||
|
///
|
||||||
|
/// If tristate is false (the default), [value] must not be null.
|
||||||
|
final bool tristate;
|
||||||
|
|
||||||
|
/// Configures the minimum size of the tap target.
|
||||||
|
///
|
||||||
|
/// Defaults to [ThemeData.materialTapTargetSize].
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [MaterialTapTargetSize], for a description of how this affects tap targets.
|
||||||
|
final MaterialTapTargetSize materialTapTargetSize;
|
||||||
|
|
||||||
|
/// The width of a checkbox widget.
|
||||||
|
static const double width = 18.0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_CustomCheckboxState createState() => _CustomCheckboxState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CustomCheckboxState extends State<CustomCheckbox>
|
||||||
|
with TickerProviderStateMixin {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
assert(debugCheckHasMaterial(context));
|
||||||
|
final ThemeData themeData = Theme.of(context);
|
||||||
|
|
||||||
|
Size size;
|
||||||
|
switch (widget.materialTapTargetSize ?? themeData.materialTapTargetSize) {
|
||||||
|
case MaterialTapTargetSize.padded:
|
||||||
|
size = const Size(
|
||||||
|
2 * kRadialReactionRadius + 8.0, 2 * kRadialReactionRadius + 8.0);
|
||||||
|
break;
|
||||||
|
case MaterialTapTargetSize.shrinkWrap:
|
||||||
|
size = const Size(2 * kRadialReactionRadius, 2 * kRadialReactionRadius);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Size noTapTargetSize = Size(CustomCheckbox.width, CustomCheckbox.width);
|
||||||
|
final BoxConstraints additionalConstraints =
|
||||||
|
BoxConstraints.tight(widget.useTapTarget ? size : noTapTargetSize);
|
||||||
|
|
||||||
|
return _CheckboxRenderObjectWidget(
|
||||||
|
value: widget.value,
|
||||||
|
tristate: widget.tristate,
|
||||||
|
activeColor: widget.activeColor ?? themeData.toggleableActiveColor,
|
||||||
|
checkColor: widget.checkColor ?? const Color(0xFFFFFFFF),
|
||||||
|
inactiveColor: widget.onChanged != null
|
||||||
|
? themeData.unselectedWidgetColor
|
||||||
|
: themeData.disabledColor,
|
||||||
|
onChanged: widget.onChanged,
|
||||||
|
additionalConstraints: additionalConstraints,
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
|
||||||
|
const _CheckboxRenderObjectWidget(
|
||||||
|
{Key key,
|
||||||
|
@required this.value,
|
||||||
|
@required this.tristate,
|
||||||
|
@required this.activeColor,
|
||||||
|
@required this.checkColor,
|
||||||
|
@required this.inactiveColor,
|
||||||
|
@required this.onChanged,
|
||||||
|
@required this.vsync,
|
||||||
|
@required this.additionalConstraints,
|
||||||
|
this.useTapTarget = true})
|
||||||
|
: assert(tristate != null),
|
||||||
|
assert(tristate || value != null),
|
||||||
|
assert(activeColor != null),
|
||||||
|
assert(inactiveColor != null),
|
||||||
|
assert(vsync != null),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
final bool value;
|
||||||
|
final bool tristate;
|
||||||
|
final Color activeColor;
|
||||||
|
final Color checkColor;
|
||||||
|
final Color inactiveColor;
|
||||||
|
final ValueChanged<bool> onChanged;
|
||||||
|
final TickerProvider vsync;
|
||||||
|
final BoxConstraints additionalConstraints;
|
||||||
|
final bool useTapTarget;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_RenderCheckbox createRenderObject(BuildContext context) => _RenderCheckbox(
|
||||||
|
value: value,
|
||||||
|
tristate: tristate,
|
||||||
|
activeColor: activeColor,
|
||||||
|
checkColor: checkColor,
|
||||||
|
inactiveColor: inactiveColor,
|
||||||
|
onChanged: onChanged,
|
||||||
|
vsync: vsync,
|
||||||
|
additionalConstraints: additionalConstraints,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void updateRenderObject(BuildContext context, _RenderCheckbox renderObject) {
|
||||||
|
renderObject
|
||||||
|
..value = value
|
||||||
|
..tristate = tristate
|
||||||
|
..activeColor = activeColor
|
||||||
|
..checkColor = checkColor
|
||||||
|
..inactiveColor = inactiveColor
|
||||||
|
..onChanged = onChanged
|
||||||
|
..additionalConstraints = additionalConstraints
|
||||||
|
..vsync = vsync;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const double _kEdgeSize = CustomCheckbox.width;
|
||||||
|
const Radius _kEdgeRadius = Radius.circular(1.0);
|
||||||
|
const double _kStrokeWidth = 2.0;
|
||||||
|
|
||||||
|
class _RenderCheckbox extends RenderToggleable {
|
||||||
|
_RenderCheckbox({
|
||||||
|
bool value,
|
||||||
|
bool tristate,
|
||||||
|
Color activeColor,
|
||||||
|
this.checkColor,
|
||||||
|
Color inactiveColor,
|
||||||
|
BoxConstraints additionalConstraints,
|
||||||
|
ValueChanged<bool> onChanged,
|
||||||
|
@required TickerProvider vsync,
|
||||||
|
}) : _oldValue = value,
|
||||||
|
super(
|
||||||
|
value: value,
|
||||||
|
tristate: tristate,
|
||||||
|
activeColor: activeColor,
|
||||||
|
inactiveColor: inactiveColor,
|
||||||
|
onChanged: onChanged,
|
||||||
|
additionalConstraints: additionalConstraints,
|
||||||
|
vsync: vsync,
|
||||||
|
);
|
||||||
|
|
||||||
|
bool _oldValue;
|
||||||
|
Color checkColor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
set value(bool newValue) {
|
||||||
|
if (newValue == value) return;
|
||||||
|
_oldValue = value;
|
||||||
|
super.value = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||||
|
super.describeSemanticsConfiguration(config);
|
||||||
|
config.isChecked = value == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The square outer bounds of the checkbox at t, with the specified origin.
|
||||||
|
// At t == 0.0, the outer rect's size is _kEdgeSize (Checkbox.width)
|
||||||
|
// At t == 0.5, .. is _kEdgeSize - _kStrokeWidth
|
||||||
|
// At t == 1.0, .. is _kEdgeSize
|
||||||
|
RRect _outerRectAt(Offset origin, double t) {
|
||||||
|
final double inset = 1.0 - (t - 0.5).abs() * 2.0;
|
||||||
|
final double size = _kEdgeSize - inset * _kStrokeWidth;
|
||||||
|
final Rect rect =
|
||||||
|
Rect.fromLTWH(origin.dx + inset, origin.dy + inset, size, size);
|
||||||
|
return RRect.fromRectAndRadius(rect, _kEdgeRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The checkbox's border color if value == false, or its fill color when
|
||||||
|
// value == true or null.
|
||||||
|
Color _colorAt(double t) {
|
||||||
|
// As t goes from 0.0 to 0.25, animate from the inactiveColor to activeColor.
|
||||||
|
return onChanged == null
|
||||||
|
? inactiveColor
|
||||||
|
: (t >= 0.25
|
||||||
|
? activeColor
|
||||||
|
: Color.lerp(inactiveColor, activeColor, t * 4.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// White stroke used to paint the check and dash.
|
||||||
|
void _initStrokePaint(Paint paint) {
|
||||||
|
paint
|
||||||
|
..color = checkColor
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = _kStrokeWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _drawBorder(Canvas canvas, RRect outer, double t, Paint paint) {
|
||||||
|
assert(t >= 0.0 && t <= 0.5);
|
||||||
|
final double size = outer.width;
|
||||||
|
// As t goes from 0.0 to 1.0, gradually fill the outer RRect.
|
||||||
|
final RRect inner =
|
||||||
|
outer.deflate(math.min(size / 2.0, _kStrokeWidth + size * t));
|
||||||
|
canvas.drawDRRect(outer, inner, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _drawCheck(Canvas canvas, Offset origin, double t, Paint paint) {
|
||||||
|
assert(t >= 0.0 && t <= 1.0);
|
||||||
|
// As t goes from 0.0 to 1.0, animate the two check mark strokes from the
|
||||||
|
// short side to the long side.
|
||||||
|
final Path path = Path();
|
||||||
|
const Offset start = Offset(_kEdgeSize * 0.15, _kEdgeSize * 0.45);
|
||||||
|
const Offset mid = Offset(_kEdgeSize * 0.4, _kEdgeSize * 0.7);
|
||||||
|
const Offset end = Offset(_kEdgeSize * 0.85, _kEdgeSize * 0.25);
|
||||||
|
if (t < 0.5) {
|
||||||
|
final double strokeT = t * 2.0;
|
||||||
|
final Offset drawMid = Offset.lerp(start, mid, strokeT);
|
||||||
|
path.moveTo(origin.dx + start.dx, origin.dy + start.dy);
|
||||||
|
path.lineTo(origin.dx + drawMid.dx, origin.dy + drawMid.dy);
|
||||||
|
} else {
|
||||||
|
final double strokeT = (t - 0.5) * 2.0;
|
||||||
|
final Offset drawEnd = Offset.lerp(mid, end, strokeT);
|
||||||
|
path.moveTo(origin.dx + start.dx, origin.dy + start.dy);
|
||||||
|
path.lineTo(origin.dx + mid.dx, origin.dy + mid.dy);
|
||||||
|
path.lineTo(origin.dx + drawEnd.dx, origin.dy + drawEnd.dy);
|
||||||
|
}
|
||||||
|
canvas.drawPath(path, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _drawDash(Canvas canvas, Offset origin, double t, Paint paint) {
|
||||||
|
assert(t >= 0.0 && t <= 1.0);
|
||||||
|
// As t goes from 0.0 to 1.0, animate the horizontal line from the
|
||||||
|
// mid point outwards.
|
||||||
|
const Offset start = Offset(_kEdgeSize * 0.2, _kEdgeSize * 0.5);
|
||||||
|
const Offset mid = Offset(_kEdgeSize * 0.5, _kEdgeSize * 0.5);
|
||||||
|
const Offset end = Offset(_kEdgeSize * 0.8, _kEdgeSize * 0.5);
|
||||||
|
final Offset drawStart = Offset.lerp(start, mid, 1.0 - t);
|
||||||
|
final Offset drawEnd = Offset.lerp(mid, end, t);
|
||||||
|
canvas.drawLine(origin + drawStart, origin + drawEnd, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(PaintingContext context, Offset offset) {
|
||||||
|
final Canvas canvas = context.canvas;
|
||||||
|
paintRadialReaction(canvas, offset, size.center(Offset.zero));
|
||||||
|
|
||||||
|
final Offset origin =
|
||||||
|
offset + (size / 2.0 - const Size.square(_kEdgeSize) / 2.0);
|
||||||
|
final AnimationStatus status = position.status;
|
||||||
|
final double tNormalized =
|
||||||
|
status == AnimationStatus.forward || status == AnimationStatus.completed
|
||||||
|
? position.value
|
||||||
|
: 1.0 - position.value;
|
||||||
|
|
||||||
|
// Four cases: false to null, false to true, null to false, true to false
|
||||||
|
if (_oldValue == false || value == false) {
|
||||||
|
final double t = value == false ? 1.0 - tNormalized : tNormalized;
|
||||||
|
final RRect outer = _outerRectAt(origin, t);
|
||||||
|
final Paint paint = Paint()..color = _colorAt(t);
|
||||||
|
|
||||||
|
if (t <= 0.5) {
|
||||||
|
_drawBorder(canvas, outer, t, paint);
|
||||||
|
} else {
|
||||||
|
canvas.drawRRect(outer, paint);
|
||||||
|
|
||||||
|
_initStrokePaint(paint);
|
||||||
|
final double tShrink = (t - 0.5) * 2.0;
|
||||||
|
if (_oldValue == null || value == null) {
|
||||||
|
_drawDash(canvas, origin, tShrink, paint);
|
||||||
|
} else {
|
||||||
|
_drawCheck(canvas, origin, tShrink, paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Two cases: null to true, true to null
|
||||||
|
final RRect outer = _outerRectAt(origin, 1.0);
|
||||||
|
final Paint paint = Paint()..color = _colorAt(1.0);
|
||||||
|
canvas.drawRRect(outer, paint);
|
||||||
|
|
||||||
|
_initStrokePaint(paint);
|
||||||
|
if (tNormalized <= 0.5) {
|
||||||
|
final double tShrink = 1.0 - tNormalized * 2.0;
|
||||||
|
if (_oldValue == true) {
|
||||||
|
_drawCheck(canvas, origin, tShrink, paint);
|
||||||
|
} else {
|
||||||
|
_drawDash(canvas, origin, tShrink, paint);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final double tExpand = (tNormalized - 0.5) * 2.0;
|
||||||
|
if (value == true) {
|
||||||
|
_drawCheck(canvas, origin, tExpand, paint);
|
||||||
|
} else {
|
||||||
|
_drawDash(canvas, origin, tExpand, paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
133
lib/common/widgets/labeled_checkbox.dart
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:local_spend/common/widgets/custom_checkbox.dart';
|
||||||
|
|
||||||
|
class LabeledCheckboxWithIcon extends StatelessWidget {
|
||||||
|
const LabeledCheckboxWithIcon({
|
||||||
|
this.label,
|
||||||
|
this.textStyle,
|
||||||
|
this.icon,
|
||||||
|
this.iconSize,
|
||||||
|
this.iconColor,
|
||||||
|
this.padding,
|
||||||
|
this.value,
|
||||||
|
this.onChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String label;
|
||||||
|
final TextStyle textStyle;
|
||||||
|
final IconData icon;
|
||||||
|
final double iconSize;
|
||||||
|
final Color iconColor;
|
||||||
|
final EdgeInsets padding;
|
||||||
|
final bool value;
|
||||||
|
final Function onChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: () {
|
||||||
|
onChanged(!value);
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: padding,
|
||||||
|
child: Row(
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.center, //doesn't do anything
|
||||||
|
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(0),
|
||||||
|
width: iconSize,
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
// size: iconSize,
|
||||||
|
color: iconColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: textStyle,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
)),
|
||||||
|
CustomCheckbox(
|
||||||
|
//custom checkbox removes padding so the form looks nice
|
||||||
|
|
||||||
|
value: value,
|
||||||
|
useTapTarget: false,
|
||||||
|
onChanged: (bool newValue) {
|
||||||
|
onChanged(newValue);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LabeledCheckbox extends StatelessWidget {
|
||||||
|
const LabeledCheckbox({
|
||||||
|
this.label,
|
||||||
|
this.textStyle,
|
||||||
|
this.padding,
|
||||||
|
this.value,
|
||||||
|
this.onChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String label;
|
||||||
|
final TextStyle textStyle;
|
||||||
|
final EdgeInsets padding;
|
||||||
|
final bool value;
|
||||||
|
final Function onChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: () {
|
||||||
|
onChanged(!value);
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: padding,
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(child: Text(label, style: textStyle)),
|
||||||
|
CustomCheckbox(
|
||||||
|
//custom checkbox removes padding so the form looks nice
|
||||||
|
|
||||||
|
value: value,
|
||||||
|
useTapTarget: false,
|
||||||
|
onChanged: (bool newValue) {
|
||||||
|
onChanged(newValue);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
//USAGE:
|
||||||
|
|
||||||
|
bool _isSelected = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: LabeledCheckbox(
|
||||||
|
label: 'Label Text Here',
|
||||||
|
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
|
||||||
|
value: _isSelected,
|
||||||
|
|
||||||
|
onChanged: (bool newValue) {
|
||||||
|
setState(() {
|
||||||
|
_isSelected = newValue;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
*/
|
241
lib/common/widgets/organisations_dialog.dart
Normal 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
|
||||||
|
],
|
||||||
|
// ),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
36
lib/common/widgets/popupListView.dart
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
class PopupListView {
|
||||||
|
Future<dynamic> dialog(context, List<String> options, String title) {
|
||||||
|
return showDialog<dynamic>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: true,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return SimpleDialog(
|
||||||
|
title: Text(title),
|
||||||
|
children: getDialogOptions(context, options),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> getDialogOptions(
|
||||||
|
context, List<String> options /*, Function onPressed*/) {
|
||||||
|
var dialogOptionsList = new List<SimpleDialogOption>();
|
||||||
|
|
||||||
|
for (var i = 0; i < options.length; i++) {
|
||||||
|
dialogOptionsList.add(
|
||||||
|
new SimpleDialogOption(
|
||||||
|
// print each iteration to see if any are null
|
||||||
|
child: Text(options[i]),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(options[i]);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dialogOptionsList;
|
||||||
|
}
|
||||||
|
}
|
46
lib/config.dart
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
part 'config.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable(createToJson: false)
|
||||||
|
class Config {
|
||||||
|
Config({this.env, this.production, this.apiKey});
|
||||||
|
|
||||||
|
factory Config.fromJson(Map<String, dynamic> json) => _$ConfigFromJson(json);
|
||||||
|
|
||||||
|
final String env;
|
||||||
|
final bool production;
|
||||||
|
final String apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConfigWrapper extends StatelessWidget {
|
||||||
|
ConfigWrapper({Key key, this.config, this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new _InheritedConfig(config: this.config, child: this.child);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Config of(BuildContext context) {
|
||||||
|
final _InheritedConfig inheritedConfig =
|
||||||
|
context.inheritFromWidgetOfExactType(_InheritedConfig);
|
||||||
|
return inheritedConfig.config;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Config config;
|
||||||
|
final Widget child;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InheritedConfig extends InheritedWidget {
|
||||||
|
const _InheritedConfig(
|
||||||
|
{Key key, @required this.config, @required Widget child})
|
||||||
|
: assert(config != null),
|
||||||
|
assert(child != null),
|
||||||
|
super(key: key, child: child);
|
||||||
|
final Config config;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool updateShouldNotify(_InheritedConfig oldWidget) =>
|
||||||
|
config != oldWidget.config;
|
||||||
|
}
|
14
lib/config.g.dart
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'config.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
Config _$ConfigFromJson(Map<String, dynamic> json) {
|
||||||
|
return Config(
|
||||||
|
env: json['env'] as String,
|
||||||
|
production: json['production'] as bool,
|
||||||
|
apiKey: json['apiKey'] as String);
|
||||||
|
}
|
6
lib/env/dev.dart
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
part 'dev.g.dart';
|
||||||
|
|
||||||
|
@JsonLiteral('dev.json', asConst: true)
|
||||||
|
Map<String, dynamic> get config => _$configJsonLiteral;
|
13
lib/env/dev.g.dart
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'dev.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonLiteralGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
const _$configJsonLiteral = {
|
||||||
|
'env': 'DEV',
|
||||||
|
'production': false,
|
||||||
|
'apiUrl': 'https://dev.localspend.co.uk/api'
|
||||||
|
};
|
5
lib/env/dev.json
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"env": "DEV",
|
||||||
|
"production": false,
|
||||||
|
"apiUrl": "https://dev.localspend.co.uk/api"
|
||||||
|
}
|
6
lib/env/prod.dart
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
part 'prod.g.dart';
|
||||||
|
|
||||||
|
@JsonLiteral('prod.json', asConst: true)
|
||||||
|
Map<String, dynamic> get config => _$configJsonLiteral;
|
13
lib/env/prod.g.dart
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'prod.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonLiteralGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
const _$configJsonLiteral = {
|
||||||
|
'env': 'PROD',
|
||||||
|
'production': true,
|
||||||
|
'apiUrl': 'https://www.localspend.co.uk/api'
|
||||||
|
};
|
5
lib/env/prod.json
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"env": "PROD",
|
||||||
|
"production": true,
|
||||||
|
"apiUrl": "https://www.localspend.co.uk/api"
|
||||||
|
}
|
141
lib/main.dart
|
@ -1,111 +1,50 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:local_spend/pages/home_page.dart';
|
||||||
|
import 'package:local_spend/pages/login_page.dart';
|
||||||
|
import 'package:local_spend/pages/map_page.dart';
|
||||||
|
import 'package:local_spend/pages/receipt_page_2.dart';
|
||||||
|
import 'package:local_spend/pages/spash_screen.dart';
|
||||||
|
import 'package:local_spend/pages/more_page.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
import 'package:local_spend/common/apifunctions/get_graph_data.dart';
|
||||||
|
|
||||||
void main() => runApp(MyApp());
|
void main() {
|
||||||
|
runApp(MyApp());
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadGraphs() {}
|
||||||
|
|
||||||
|
class GraphWithTitle {
|
||||||
|
GraphWithTitle({this.graph, this.title});
|
||||||
|
|
||||||
|
GraphData graph;
|
||||||
|
String title;
|
||||||
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
// This widget is the root of your application.
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
// TODO: load graphs on app login and send to graph widgets
|
||||||
title: 'Flutter Demo',
|
|
||||||
theme: ThemeData(
|
return new MaterialApp(
|
||||||
// This is the theme of your application.
|
debugShowCheckedModeBanner: false,
|
||||||
//
|
localizationsDelegates: [
|
||||||
// Try running your application with "flutter run". You'll see the
|
GlobalMaterialLocalizations.delegate,
|
||||||
// application has a blue toolbar. Then, without quitting the app, try
|
GlobalWidgetsLocalizations.delegate,
|
||||||
// changing the primarySwatch below to Colors.green and then invoke
|
],
|
||||||
// "hot reload" (press "r" in the console where you ran "flutter run",
|
supportedLocales: [Locale("en")],
|
||||||
// or simply save your changes to "hot reload" in a Flutter IDE).
|
title: "Local Spend Tracker",
|
||||||
// Notice that the counter didn't reset back to zero; the application
|
theme: new ThemeData(
|
||||||
// is not restarted.
|
primarySwatch: Colors.blueGrey,
|
||||||
primarySwatch: Colors.grey,
|
|
||||||
),
|
),
|
||||||
home: MyHomePage(title: 'Flutter Demo Home Page'),
|
routes: <String, WidgetBuilder>{
|
||||||
);
|
"/HomePage": (BuildContext context) => HomePage(),
|
||||||
}
|
"/LoginPage": (BuildContext context) => LoginPage(),
|
||||||
}
|
'/MapPage': (BuildContext context) => MapPage(),
|
||||||
|
"/ReceiptPage": (BuildContext context) => ReceiptPage2(),
|
||||||
class MyHomePage extends StatefulWidget {
|
"/MorePage": (BuildContext context) => MorePage(),
|
||||||
MyHomePage({Key key, this.title}) : super(key: key);
|
},
|
||||||
|
home: SplashScreen(),
|
||||||
// This widget is the home page of your application. It is stateful, meaning
|
|
||||||
// that it has a State object (defined below) that contains fields that affect
|
|
||||||
// how it looks.
|
|
||||||
|
|
||||||
// This class is the configuration for the state. It holds the values (in this
|
|
||||||
// case the title) provided by the parent (in this case the App widget) and
|
|
||||||
// used by the build method of the State. Fields in a Widget subclass are
|
|
||||||
// always marked "final".
|
|
||||||
|
|
||||||
final String title;
|
|
||||||
|
|
||||||
@override
|
|
||||||
_MyHomePageState createState() => _MyHomePageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MyHomePageState extends State<MyHomePage> {
|
|
||||||
int _counter = 0;
|
|
||||||
|
|
||||||
void _incrementCounter() {
|
|
||||||
setState(() {
|
|
||||||
// This call to setState tells the Flutter framework that something has
|
|
||||||
// changed in this State, which causes it to rerun the build method below
|
|
||||||
// so that the display can reflect the updated values. If we changed
|
|
||||||
// _counter without calling setState(), then the build method would not be
|
|
||||||
// called again, and so nothing would appear to happen.
|
|
||||||
_counter++;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
// This method is rerun every time setState is called, for instance as done
|
|
||||||
// by the _incrementCounter method above.
|
|
||||||
//
|
|
||||||
// The Flutter framework has been optimized to make rerunning build methods
|
|
||||||
// fast, so that you can just rebuild anything that needs updating rather
|
|
||||||
// than having to individually change instances of widgets.
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
// Here we take the value from the MyHomePage object that was created by
|
|
||||||
// the App.build method, and use it to set our appbar title.
|
|
||||||
title: Text(widget.title),
|
|
||||||
),
|
|
||||||
body: Center(
|
|
||||||
// Center is a layout widget. It takes a single child and positions it
|
|
||||||
// in the middle of the parent.
|
|
||||||
child: Column(
|
|
||||||
// Column is also layout widget. It takes a list of children and
|
|
||||||
// arranges them vertically. By default, it sizes itself to fit its
|
|
||||||
// children horizontally, and tries to be as tall as its parent.
|
|
||||||
//
|
|
||||||
// Invoke "debug painting" (press "p" in the console, choose the
|
|
||||||
// "Toggle Debug Paint" action from the Flutter Inspector in Android
|
|
||||||
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
|
|
||||||
// to see the wireframe for each widget.
|
|
||||||
//
|
|
||||||
// Column has various properties to control how it sizes itself and
|
|
||||||
// how it positions its children. Here we use mainAxisAlignment to
|
|
||||||
// center the children vertically; the main axis here is the vertical
|
|
||||||
// axis because Columns are vertical (the cross axis would be
|
|
||||||
// horizontal).
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
'You have pushed the button this many times:',
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'$_counter',
|
|
||||||
style: Theme.of(context).textTheme.display1,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
floatingActionButton: FloatingActionButton(
|
|
||||||
onPressed: _incrementCounter,
|
|
||||||
tooltip: 'Increment',
|
|
||||||
child: Icon(Icons.add),
|
|
||||||
), // This trailing comma makes auto-formatting nicer for build methods.
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
7
lib/main_dev.dart
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:local_spend/config.dart';
|
||||||
|
import 'package:local_spend/env/dev.dart';
|
||||||
|
import 'package:local_spend/main.dart';
|
||||||
|
|
||||||
|
void main() => runApp(
|
||||||
|
new ConfigWrapper(config: Config.fromJson(config), child: new MyApp()));
|
18
lib/model/json/login_model.dart
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
class LoginModel {
|
||||||
|
LoginModel(this.userName, this.token, this.userType);
|
||||||
|
|
||||||
|
LoginModel.fromJson(Map<String, dynamic> json)
|
||||||
|
: userName = json['display_name'],
|
||||||
|
userType = json['user_type'],
|
||||||
|
token = json['session_key'];
|
||||||
|
|
||||||
|
final String userName;
|
||||||
|
final String token;
|
||||||
|
final String userType;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'name': userName,
|
||||||
|
'user_type': userType,
|
||||||
|
'token': token,
|
||||||
|
};
|
||||||
|
}
|
163
lib/pages/customerGraphs.dart
Normal 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>>
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
76
lib/pages/home_page.dart
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:local_spend/pages/receipt_page_2.dart';
|
||||||
|
import 'package:local_spend/pages/more_page.dart';
|
||||||
|
import 'package:local_spend/pages/stats_page.dart';
|
||||||
|
import 'package:local_spend/pages/map_page.dart';
|
||||||
|
|
||||||
|
class HomePage extends StatelessWidget {
|
||||||
|
static String _title = 'SpendTracker';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
|
title: _title,
|
||||||
|
home: HomePageWidget(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HomePageWidget extends StatefulWidget {
|
||||||
|
HomePageWidget({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_HomePageState createState() => _HomePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HomePageState extends State<HomePageWidget> {
|
||||||
|
int _selectedIndex = 0;
|
||||||
|
|
||||||
|
static const TextStyle optionStyle =
|
||||||
|
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
|
||||||
|
static List<Widget> _widgetOptions = <Widget>[
|
||||||
|
ReceiptPage2(),
|
||||||
|
StatsPage(),
|
||||||
|
MapPage(),
|
||||||
|
MorePage()
|
||||||
|
];
|
||||||
|
|
||||||
|
void _onItemTapped(int index) {
|
||||||
|
setState(() {
|
||||||
|
_selectedIndex = index;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: _widgetOptions.elementAt(_selectedIndex),
|
||||||
|
),
|
||||||
|
bottomNavigationBar: BottomNavigationBar(
|
||||||
|
items: const <BottomNavigationBarItem>[
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.receipt),
|
||||||
|
title: Text('Submit Receipt'),
|
||||||
|
),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.show_chart),
|
||||||
|
title: Text('Statistics'),
|
||||||
|
),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.map),
|
||||||
|
title: Text('Locations'),
|
||||||
|
),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.more_horiz),
|
||||||
|
title: Text('More'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
currentIndex: _selectedIndex,
|
||||||
|
unselectedItemColor: Colors.grey[400],
|
||||||
|
selectedItemColor: Colors.blue[400],
|
||||||
|
onTap: _onItemTapped,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
226
lib/pages/login_page.dart
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:local_spend/common/apifunctions/request_login_api.dart';
|
||||||
|
import 'package:local_spend/common/functions/show_dialog_single_button.dart';
|
||||||
|
import 'package:local_spend/common/platform/platform_scaffold.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
import 'package:local_spend/common/widgets/labeled_checkbox.dart';
|
||||||
|
import 'package:local_spend/common/widgets/animatedGradientButton.dart';
|
||||||
|
|
||||||
|
const url = "https://flutter.io/";
|
||||||
|
|
||||||
|
class LoginPage extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() {
|
||||||
|
return new LoginPageState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoginPageState extends State<LoginPage> {
|
||||||
|
bool _isLoggingIn = false;
|
||||||
|
final TextEditingController _emailController = TextEditingController();
|
||||||
|
final TextEditingController _passwordController = TextEditingController();
|
||||||
|
bool _saveLoginDetails = true;
|
||||||
|
|
||||||
|
FocusNode focusNode; // added so focus can move automatically
|
||||||
|
|
||||||
|
Future launchURL(String url) async {
|
||||||
|
if (await canLaunch(url)) {
|
||||||
|
await launch(url, forceSafariVC: true, forceWebView: true);
|
||||||
|
} else {
|
||||||
|
showDialogSingleButton(
|
||||||
|
context,
|
||||||
|
"Unable to reach your website.",
|
||||||
|
"Currently unable to reach the website $url. Please try again at a later time.",
|
||||||
|
"OK");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_saveCurrentRoute("/LoginPage");
|
||||||
|
|
||||||
|
focusNode = FocusNode();
|
||||||
|
|
||||||
|
_fillLoginDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
focusNode.dispose(); //disposes focus node when form disposed
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _fillLoginDetails() async {
|
||||||
|
SharedPreferences preferences = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
|
var username = await preferences.get('username');
|
||||||
|
var password = await preferences.get('password');
|
||||||
|
|
||||||
|
_emailController.text = await username;
|
||||||
|
_passwordController.text = await password;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _saveCurrentRoute(String lastRoute) async {
|
||||||
|
SharedPreferences preferences = await SharedPreferences.getInstance();
|
||||||
|
await preferences.setString('LastPageRoute', lastRoute);
|
||||||
|
}
|
||||||
|
|
||||||
|
void login(String username, String password) async {
|
||||||
|
_isLoggingIn = true;
|
||||||
|
await SystemChannels.textInput.invokeMethod('TextInput.hide');
|
||||||
|
SharedPreferences preferences = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
|
if (_saveLoginDetails) {
|
||||||
|
await preferences.setString('username', username);
|
||||||
|
await preferences.setString('password', password);
|
||||||
|
} else {
|
||||||
|
await preferences.setString('username', "");
|
||||||
|
await preferences.setString('password', "");
|
||||||
|
}
|
||||||
|
|
||||||
|
await requestLoginAPI(context, username, password).then((value) {
|
||||||
|
_isLoggingIn = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return WillPopScope(
|
||||||
|
onWillPop: () {
|
||||||
|
if (Navigator.canPop(context)) {
|
||||||
|
Navigator.of(context).pushNamedAndRemoveUntil(
|
||||||
|
'/HomePage', (Route<dynamic> route) => false);
|
||||||
|
} else {
|
||||||
|
Navigator.of(context).pushReplacementNamed('/HomePage');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
child: PlatformScaffold(
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
AnimatedBackground([Colors.lightBlue[50], Colors.lightBlue[50]],
|
||||||
|
Colors.white, Alignment.topRight, Alignment.bottomLeft, 3),
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.fromLTRB(60, 30, 60, 0),
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: Duration(seconds: 2),
|
||||||
|
margin: EdgeInsets.fromLTRB(15, 0, 15, 0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
image:
|
||||||
|
AssetImage('assets/images/launch_image.png')),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 0.0),
|
||||||
|
child: TextField(
|
||||||
|
autocorrect: false,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
controller: _emailController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "EMAIL",
|
||||||
|
hintStyle: TextStyle(fontSize: 15),
|
||||||
|
),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.0,
|
||||||
|
color: Colors.grey[800],
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
onSubmitted: (_) {
|
||||||
|
FocusScope.of(context).requestFocus(focusNode);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(0.0, 15.0, 0.0, 0.0),
|
||||||
|
child: TextField(
|
||||||
|
autocorrect: false,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
controller: _passwordController,
|
||||||
|
focusNode: focusNode,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'PASSWORD',
|
||||||
|
hintStyle: TextStyle(fontSize: 15),
|
||||||
|
),
|
||||||
|
obscureText: true,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.0,
|
||||||
|
color: Colors.grey[800],
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
onSubmitted: (_) {
|
||||||
|
login(_emailController.text, _passwordController.text);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.fromLTRB(0.0, 40.0, 0.0, 30.0),
|
||||||
|
width: 100,
|
||||||
|
height: 50,
|
||||||
|
child: Opacity(
|
||||||
|
opacity: _isLoggingIn ? 0.5 : 1,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(2),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
AnimatedBackground(
|
||||||
|
[Colors.blue, Colors.lightBlue[300]],
|
||||||
|
Colors.lightBlue,
|
||||||
|
Alignment.bottomRight,
|
||||||
|
Alignment.topLeft,
|
||||||
|
3),
|
||||||
|
Material(
|
||||||
|
type: MaterialType.transparency,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: _isLoggingIn
|
||||||
|
? null
|
||||||
|
: () => login(_emailController.text,
|
||||||
|
_passwordController.text),
|
||||||
|
child: new Center(
|
||||||
|
child: new Text(
|
||||||
|
'GO',
|
||||||
|
style: new TextStyle(
|
||||||
|
fontSize: 18, color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(0, 10, 0, 50),
|
||||||
|
child: LabeledCheckbox(
|
||||||
|
label: "SAVE LOGIN",
|
||||||
|
textStyle: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
color: Colors.black54,
|
||||||
|
fontWeight: FontWeight.bold),
|
||||||
|
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
|
||||||
|
value: _saveLoginDetails,
|
||||||
|
onChanged: (bool newValue) {
|
||||||
|
setState(() {
|
||||||
|
_saveLoginDetails = newValue;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
62
lib/pages/map_page.dart
Normal file
|
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
191
lib/pages/more_page.dart
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:local_spend/common/platform/platform_scaffold.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:local_spend/common/functions/logout.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
import 'package:local_spend/common/functions/customAbout.dart' as custom;
|
||||||
|
import 'package:local_spend/common/functions/showDialogTwoButtons.dart';
|
||||||
|
|
||||||
|
const url = "https://flutter.io/";
|
||||||
|
const demonstration = false;
|
||||||
|
|
||||||
|
class MorePage extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() {
|
||||||
|
return new MorePageState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MorePageState extends State<MorePage> {
|
||||||
|
FocusNode focusNode; // added so focus can move automatically
|
||||||
|
|
||||||
|
DateTime date;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_saveCurrentRoute("/MorePageState");
|
||||||
|
|
||||||
|
focusNode = FocusNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
|
||||||
|
focusNode.dispose(); //disposes focus node when form disposed
|
||||||
|
}
|
||||||
|
|
||||||
|
void _saveCurrentRoute(String lastRoute) async {
|
||||||
|
SharedPreferences preferences = await SharedPreferences.getInstance();
|
||||||
|
await preferences.setString('LastPageRoute', lastRoute);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return WillPopScope(
|
||||||
|
onWillPop: () {
|
||||||
|
if (Navigator.canPop(context)) {
|
||||||
|
Navigator.of(context).pushNamedAndRemoveUntil(
|
||||||
|
'/LoginPage', (Route<dynamic> route) => false);
|
||||||
|
} else {
|
||||||
|
Navigator.of(context).pushReplacementNamed('/LoginPage');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
child: PlatformScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Colors.blue[400],
|
||||||
|
title: Text(
|
||||||
|
"More",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// leading: BackButton(),
|
||||||
|
centerTitle: true,
|
||||||
|
iconTheme: IconThemeData(color: Colors.black),
|
||||||
|
),
|
||||||
|
body: Container(
|
||||||
|
child: ListView(
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.fromLTRB(30.0, 25, 30.0, 0.0),
|
||||||
|
child: Text(
|
||||||
|
"Local Spend Tracker",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22.0,
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(30.0, 25.0, 30.0, 0.0),
|
||||||
|
child: Container(
|
||||||
|
height: 65.0,
|
||||||
|
child: RaisedButton(
|
||||||
|
onPressed: () {
|
||||||
|
custom.showAboutDialog(
|
||||||
|
context: context,
|
||||||
|
applicationIcon: new Icon(Icons.receipt),
|
||||||
|
applicationName: "Local Spend Tracker",
|
||||||
|
children: <Widget>[
|
||||||
|
Text("Pear Trading is a commerce company designed to register and monitor money circulating in the local economy.\n"),
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 10),
|
||||||
|
height: 35,
|
||||||
|
child: RaisedButton(
|
||||||
|
onPressed: () => launch('http://www.peartrade.org'),
|
||||||
|
child: Text("Pear Trading",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white, fontSize: 18.0)),
|
||||||
|
color: Colors.green,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 0.0),
|
||||||
|
height: 40.0,
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(3),
|
||||||
|
onTap: () => launch('https://shadow.cat'),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
child: Text("Developed by"),
|
||||||
|
alignment: Alignment.centerLeft),
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.all(0),
|
||||||
|
child : Text(
|
||||||
|
"Shadowcat Systems",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Text("ABOUT",
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 22.0)),
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(30.0, 20.0, 30.0, 0.0),
|
||||||
|
child: Container(
|
||||||
|
height: 65.0,
|
||||||
|
child: RaisedButton(
|
||||||
|
onPressed: () {
|
||||||
|
showDialogTwoButtons(
|
||||||
|
context,
|
||||||
|
"Logout",
|
||||||
|
"Are you sure you want to log out?",
|
||||||
|
"Cancel",
|
||||||
|
"Logout",
|
||||||
|
logout);
|
||||||
|
},
|
||||||
|
child: Text("LOGOUT",
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 22.0)),
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Padding(
|
||||||
|
// padding: EdgeInsets.fromLTRB(30.0, 20.0, 30.0, 0.0),
|
||||||
|
// child: Container(
|
||||||
|
// height: 65.0,
|
||||||
|
// child: RaisedButton(
|
||||||
|
// onPressed: () {
|
||||||
|
// feedback(context);
|
||||||
|
// },
|
||||||
|
// child: Text("FEEDBACK",
|
||||||
|
// style:
|
||||||
|
// TextStyle(color: Colors.white, fontSize: 22.0)),
|
||||||
|
// color: Colors.green,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
57
lib/pages/new_stats_page.dart
Normal 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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|