Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c51c678e99 | |||
|
|
23c48a068f | ||
|
|
c95471987e | ||
|
|
10664ee9fa | ||
|
|
6600766724 | ||
|
|
398294df42 | ||
|
|
ad3276c898 | ||
|
|
12b385d7e0 | ||
|
|
085a7648f3 | ||
| c183d84a86 | |||
| fec555991d | |||
| 9c73361a2f | |||
| 3df1be39a8 | |||
| 503f66756e | |||
| a927f3ce0e | |||
| f7cebd9371 | |||
| 8b515a3c27 | |||
|
|
774da7ad6c | ||
|
|
2d645274a9 | ||
|
|
b3d927662c | ||
|
|
190cbcc0ea | ||
|
|
dfec3fd9e2 | ||
|
|
c23ce74bc4 | ||
|
|
3ab962f00a |
@ -1,3 +0,0 @@
|
||||
APPWRITE_PROJECT_ID=
|
||||
APPWRITE_PROJECT_NAME=
|
||||
APPWRITE_PUBLIC_ENDPOINT=
|
||||
7
.envExample
Normal file
@ -0,0 +1,7 @@
|
||||
APPWRITE_PROJECT_ID=<APPWRITE_PROJECT_ID>
|
||||
APPWRITE_PROJECT_NAME=<Flutter Projects Name>
|
||||
APPWRITE_PUBLIC_ENDPOINT=<YOUR_API_ENDPOINT/v1>
|
||||
PTV_GEOLINK_API_KEY=<YOUR_GEOLINK_API_KEY>
|
||||
APPWRITE_DATABASE_ID=<DatabaseID>
|
||||
APPWRITE_COLLECTION_ID=<Collection_ID>
|
||||
TANKSTOPS_BASE_URL=https://api.e-control.at/sprit/1.0/search/gas-stations/by-address?latitude=<lat>&longitude=<lon>&fuelType=DIE<SUPSuperOrDIEDiesel>&includeClosed=false
|
||||
@ -14,7 +14,7 @@ This guide will help you quickly set up, customize, and build your Flutter app.
|
||||
Clone this repository to your local machine using Git or directly from `Android Studio`:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/appwrite/starter-for-flutter
|
||||
git clone https://gitea.joshihomeserver.ipv64.net/josiadmin/MyNewAppWriteTankApp.git
|
||||
```
|
||||
|
||||
Alternatively, open the repository URL in `Android Studio` to clone it directly.
|
||||
|
||||
@ -7,6 +7,11 @@
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
analyzer:
|
||||
errors:
|
||||
unused_field: ignore
|
||||
avoid_print: ignore
|
||||
unnecessary_overrides: ignore
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
|
||||
@ -5,8 +5,8 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
ndkVersion = "25.1.8937393"
|
||||
namespace = "io.appwrite.flutter"
|
||||
ndkVersion = "27.0.12077973"
|
||||
namespace = "com.example.flutter_new_tank_app310725"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
@ -19,7 +19,7 @@ android {
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "io.appwrite.flutter"
|
||||
applicationId = "com.example.flutter_new_tank_app310725"
|
||||
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
|
||||
@ -1,11 +1,18 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<application
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="AppwriteStarterKit">
|
||||
android:label="Tank Guru">
|
||||
<service android:name="com.baseflow.geolocator.GeolocatorLocationService"
|
||||
android:foregroundServiceType="location" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package io.appwrite.flutter
|
||||
package com.example.flutter_new_tank_app310725
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 69 B |
@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
||||
</item>
|
||||
<item android:bottom="24dp">
|
||||
<bitmap android:gravity="bottom" android:src="@drawable/branding"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
Before Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 69 B |
@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
||||
</item>
|
||||
<item android:bottom="24dp">
|
||||
<bitmap android:gravity="bottom" android:src="@drawable/branding"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
Before Width: | Height: | Size: 69 B |
@ -1,12 +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>
|
||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
||||
</item>
|
||||
<item android:bottom="24dp">
|
||||
<bitmap android:gravity="bottom" android:src="@drawable/branding"/>
|
||||
</item>
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- 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: 8.9 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 69 B |
@ -1,12 +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>
|
||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
||||
</item>
|
||||
<item android:bottom="24dp">
|
||||
<bitmap android:gravity="bottom" android:src="@drawable/branding"/>
|
||||
</item>
|
||||
<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: 2.1 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 44 KiB |
@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:windowSplashScreenBackground">#19191D</item>
|
||||
<item name="android:windowSplashScreenIconBackgroundColor">#19191D</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@ -5,10 +5,6 @@
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:windowSplashScreenBackground">#EDEDF0</item>
|
||||
<item name="android:windowSplashScreenIconBackgroundColor">#EDEDF0</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@ -2,14 +2,16 @@
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowTranslucentStatus">true</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
|
||||
@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
||||
|
||||
33
build.sh
Normal file → Executable file
@ -1,4 +1,29 @@
|
||||
flutter build web \
|
||||
--dart-define=APPWRITE_PROJECT_ID=$APPWRITE_PROJECT_ID \
|
||||
--dart-define=APPWRITE_PROJECT_NAME=$APPWRITE_PROJECT_NAME \
|
||||
--dart-define=APPWRITE_PUBLIC_ENDPOINT=$APPWRITE_PUBLIC_ENDPOINT
|
||||
#!/bin/bash
|
||||
|
||||
# Check if .env file exists
|
||||
if [ ! -f .env ]; then
|
||||
{
|
||||
echo "APPWRITE_PROJECT_ID=$APPWRITE_PROJECT_ID"
|
||||
echo "APPWRITE_PROJECT_NAME=$APPWRITE_PROJECT_NAME"
|
||||
echo "APPWRITE_PUBLIC_ENDPOINT=$APPWRITE_PUBLIC_ENDPOINT"
|
||||
} >> .env
|
||||
fi
|
||||
|
||||
# Read .env file and convert it to --dart-define arguments
|
||||
ARGS=""
|
||||
while IFS='=' read -r key value || [ -n "$key" ]; do
|
||||
# Ignore empty lines and comments
|
||||
if [[ -n "$key" && ! "$key" =~ ^# ]]; then
|
||||
ARGS+=" --dart-define=${key}=\"${value}\""
|
||||
fi
|
||||
done < .env
|
||||
|
||||
# Build Flutter web
|
||||
eval flutter build web "$ARGS"
|
||||
|
||||
# If --preview flag is provided, run a local preview server
|
||||
if [ "$1" == "--preview" ]; then
|
||||
echo "Starting preview server at http://localhost:3000..."
|
||||
cd build/web || exit 1
|
||||
python3 -m http.server 3000
|
||||
fi
|
||||
|
||||
@ -21,6 +21,6 @@
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>12.0</string>
|
||||
<string>13.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
# platform :ios, '12.0'
|
||||
# platform :ios, '13.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
@ -6,11 +6,16 @@ PODS:
|
||||
- Flutter
|
||||
- flutter_web_auth_2 (3.0.0):
|
||||
- Flutter
|
||||
- geolocator_apple (1.2.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- printing (1.0.0):
|
||||
- Flutter
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
|
||||
@ -19,8 +24,10 @@ DEPENDENCIES:
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||
- flutter_web_auth_2 (from `.symlinks/plugins/flutter_web_auth_2/ios`)
|
||||
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- printing (from `.symlinks/plugins/printing/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
@ -32,22 +39,28 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||
flutter_web_auth_2:
|
||||
:path: ".symlinks/plugins/flutter_web_auth_2/ios"
|
||||
geolocator_apple:
|
||||
:path: ".symlinks/plugins/geolocator_apple/darwin"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
printing:
|
||||
:path: ".symlinks/plugins/printing/ios"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_native_splash: f71420956eb811e6d310720fee915f1d42852e7a
|
||||
flutter_web_auth_2: 06d500582775790a0d4c323222fcb6d7990f9603
|
||||
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||
device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
flutter_native_splash: 6cad9122ea0fad137d23137dd14b937f3e90b145
|
||||
flutter_web_auth_2: 5c8d9dcd7848b5a9efb086d24e7a9adcae979c80
|
||||
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
|
||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||
printing: 54ff03f28fe9ba3aa93358afb80a8595a071dd07
|
||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||
|
||||
PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
|
||||
PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@ -454,10 +454,11 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
@ -465,21 +466,21 @@
|
||||
};
|
||||
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
baseConfigurationReference = 9AAEF3FADF29177722D56E5C /* Pods-Runner.profile.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = Y9G8UWM9SH;
|
||||
DEVELOPMENT_TEAM = NTNRKTMT87;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.appwrite.flutter;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutter_new_tank_app310725;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@ -497,7 +498,7 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.appwrite.flutter.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutter_new_tank_app310725.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
@ -515,7 +516,7 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.appwrite.flutter.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutter_new_tank_app310725.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
@ -531,7 +532,7 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.appwrite.flutter.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutter_new_tank_app310725.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
@ -587,7 +588,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@ -638,10 +639,11 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@ -658,14 +660,14 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = Y9G8UWM9SH;
|
||||
DEVELOPMENT_TEAM = NTNRKTMT87;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.appwrite.flutter;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutter_new_tank_app310725;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@ -677,21 +679,21 @@
|
||||
};
|
||||
97C147071CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
baseConfigurationReference = 6CF6DACC519FF36038FF3CFC /* Pods-Runner.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = Y9G8UWM9SH;
|
||||
DEVELOPMENT_TEAM = NTNRKTMT87;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.appwrite.flutter;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutter_new_tank_app310725;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@ -711,7 +713,7 @@
|
||||
331C808A294A63A400263BE5 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
defaultConfigurationName = Profile;
|
||||
};
|
||||
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
@ -721,7 +723,7 @@
|
||||
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
defaultConfigurationName = Profile;
|
||||
};
|
||||
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
@ -731,7 +733,7 @@
|
||||
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
defaultConfigurationName = Profile;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
@ -54,11 +55,13 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUValidationMode = "1"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
|
||||
@ -2,6 +2,14 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>This app needs access to your location to find nearby gas stations and services.</string>
|
||||
<key>NSLocationAlwaysUsageDescription</key>
|
||||
<string>This app needs access to your location to track your position even when the app is in the background.</string>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>location</string>
|
||||
</array>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
|
||||
15
lib/app.dart
@ -1,19 +1,24 @@
|
||||
import 'package:appwrite_flutter_starter_kit/home.dart';
|
||||
//import 'pages/appwritetest/home.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import './utils/extensions/sample_routes.dart';
|
||||
import './pages/login/login_view.dart';
|
||||
|
||||
class AppwriteApp extends StatelessWidget {
|
||||
const AppwriteApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Appwrite StarterKit',
|
||||
return GetMaterialApp(
|
||||
title: 'Tank Guru AppWrite',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||
colorScheme: ColorScheme.dark(),
|
||||
),
|
||||
home: const AppwriteStarterKit(),
|
||||
initialRoute: LoginPage.namedRoute,
|
||||
getPages: SampleRouts.samplePages,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
36
lib/data/models/chart_model.dart
Normal file
@ -0,0 +1,36 @@
|
||||
class ChartData {
|
||||
final double dateInMilliseconds;
|
||||
final double liters;
|
||||
final double pricePerLiter;
|
||||
final double totalPrice;
|
||||
|
||||
ChartData({
|
||||
required this.dateInMilliseconds,
|
||||
required this.liters,
|
||||
required this.pricePerLiter,
|
||||
required this.totalPrice,
|
||||
});
|
||||
}
|
||||
|
||||
class PricePoints {
|
||||
final double x;
|
||||
final double y;
|
||||
|
||||
PricePoints({required this.x, required this.y});
|
||||
}
|
||||
|
||||
class SumDataModel {
|
||||
late String? szMonth;
|
||||
late String? szSumme;
|
||||
late String? szVerbrauch;
|
||||
late int? mnTankungen;
|
||||
|
||||
SumDataModel(this.szMonth, this.szSumme, this.szVerbrauch, this.mnTankungen);
|
||||
}
|
||||
|
||||
class YearModel {
|
||||
final String szDescription;
|
||||
final int mnYear;
|
||||
|
||||
YearModel(this.szDescription, this.mnYear);
|
||||
}
|
||||
294
lib/data/models/gas_model.dart
Normal file
@ -0,0 +1,294 @@
|
||||
// ignore_for_file: unnecessary_this
|
||||
|
||||
class GasModel {
|
||||
int? id;
|
||||
String? name;
|
||||
Location? location;
|
||||
Contact? contact;
|
||||
List<OpeningHours>? openingHours;
|
||||
OfferInformation? offerInformation;
|
||||
PaymentMethods? paymentMethods;
|
||||
PaymentArrangements? paymentArrangements;
|
||||
int? position;
|
||||
bool? open;
|
||||
double? distance;
|
||||
List<Prices>? prices;
|
||||
|
||||
GasModel(
|
||||
{int? id,
|
||||
String? name,
|
||||
Location? location,
|
||||
Contact? contact,
|
||||
List<OpeningHours>? openingHours,
|
||||
OfferInformation? offerInformation,
|
||||
PaymentMethods? paymentMethods,
|
||||
PaymentArrangements? paymentArrangements,
|
||||
int? position,
|
||||
bool? open,
|
||||
double? distance,
|
||||
List<Prices>? prices}) {
|
||||
if (id != null) {
|
||||
this.id = id;
|
||||
}
|
||||
if (name != null) {
|
||||
this.name = name;
|
||||
}
|
||||
if (location != null) {
|
||||
this.location = location;
|
||||
}
|
||||
if (contact != null) {
|
||||
this.contact = contact;
|
||||
}
|
||||
if (openingHours != null) {
|
||||
this.openingHours = openingHours;
|
||||
}
|
||||
if (offerInformation != null) {
|
||||
this.offerInformation = offerInformation;
|
||||
}
|
||||
if (paymentMethods != null) {
|
||||
this.paymentMethods = paymentMethods;
|
||||
}
|
||||
if (paymentArrangements != null) {
|
||||
this.paymentArrangements = paymentArrangements;
|
||||
}
|
||||
if (position != null) {
|
||||
this.position = position;
|
||||
|
||||
if (open != null) {
|
||||
this.open = open;
|
||||
}
|
||||
if (distance != null) {
|
||||
this.distance = distance;
|
||||
}
|
||||
if (prices != null) {
|
||||
this.prices = prices;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GasModel.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
name = json['name'];
|
||||
location =
|
||||
json['location'] != null ? Location.fromJson(json['location']) : null;
|
||||
contact =
|
||||
json['contact'] != null ? Contact.fromJson(json['contact']) : null;
|
||||
if (json['openingHours'] != null) {
|
||||
openingHours = <OpeningHours>[];
|
||||
json['openingHours'].forEach((v) {
|
||||
openingHours!.add(OpeningHours.fromJson(v));
|
||||
});
|
||||
}
|
||||
offerInformation = json['offerInformation'] != null
|
||||
? OfferInformation.fromJson(json['offerInformation'])
|
||||
: null;
|
||||
paymentMethods = json['paymentMethods'] != null
|
||||
? PaymentMethods.fromJson(json['paymentMethods'])
|
||||
: null;
|
||||
paymentArrangements = json['paymentArrangements'] != null
|
||||
? PaymentArrangements.fromJson(json['paymentArrangements'])
|
||||
: null;
|
||||
position = json['position'];
|
||||
open = json['open'];
|
||||
distance = json['distance'];
|
||||
if (json['prices'] != null) {
|
||||
prices = <Prices>[];
|
||||
json['prices'].forEach((v) {
|
||||
prices!.add(Prices.fromJson(v));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Location {
|
||||
String? address;
|
||||
String? postalCode;
|
||||
String? city;
|
||||
double? latitude;
|
||||
double? longitude;
|
||||
|
||||
Location(
|
||||
{String? address,
|
||||
String? postalCode,
|
||||
String? city,
|
||||
double? latitude,
|
||||
double? longitude}) {
|
||||
if (address != null) {
|
||||
this.address = address;
|
||||
}
|
||||
if (postalCode != null) {
|
||||
this.postalCode = postalCode;
|
||||
}
|
||||
if (city != null) {
|
||||
this.city = city;
|
||||
}
|
||||
if (latitude != null) {
|
||||
this.latitude = latitude;
|
||||
}
|
||||
if (longitude != null) {
|
||||
this.longitude = longitude;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Location.fromJson(Map<String, dynamic> json) {
|
||||
address = json['address'];
|
||||
postalCode = json['postalCode'];
|
||||
city = json['city'];
|
||||
latitude = json['latitude'];
|
||||
longitude = json['longitude'];
|
||||
}
|
||||
}
|
||||
|
||||
class Contact {
|
||||
String? telephone;
|
||||
String? website;
|
||||
|
||||
Contact({String? telephone, String? website}) {
|
||||
if (telephone != null) {
|
||||
this.telephone = telephone;
|
||||
}
|
||||
if (website != null) {
|
||||
this.website = website;
|
||||
}
|
||||
}
|
||||
|
||||
Contact.fromJson(Map<String, dynamic> json) {
|
||||
telephone = json['telephone'];
|
||||
website = json['website'];
|
||||
}
|
||||
}
|
||||
|
||||
class OpeningHours {
|
||||
String? day;
|
||||
String? label;
|
||||
int? order;
|
||||
String? from;
|
||||
String? to;
|
||||
|
||||
OpeningHours(
|
||||
{String? day, String? label, int? order, String? from, String? to}) {
|
||||
if (day != null) {
|
||||
this.day = day;
|
||||
}
|
||||
if (label != null) {
|
||||
this.label = label;
|
||||
}
|
||||
if (order != null) {
|
||||
this.order = order;
|
||||
}
|
||||
if (from != null) {
|
||||
this.from = from;
|
||||
}
|
||||
if (to != null) {
|
||||
this.to = to;
|
||||
}
|
||||
}
|
||||
|
||||
OpeningHours.fromJson(Map<String, dynamic> json) {
|
||||
day = json['day'];
|
||||
label = json['label'];
|
||||
order = json['order'];
|
||||
from = json['from'];
|
||||
to = json['to'];
|
||||
}
|
||||
}
|
||||
|
||||
class OfferInformation {
|
||||
bool? service;
|
||||
bool? selfService;
|
||||
bool? unattended;
|
||||
|
||||
OfferInformation({bool? service, bool? selfService, bool? unattended}) {
|
||||
if (service != null) {
|
||||
this.service = service;
|
||||
}
|
||||
if (selfService != null) {
|
||||
this.selfService = selfService;
|
||||
}
|
||||
if (unattended != null) {
|
||||
this.unattended = unattended;
|
||||
}
|
||||
}
|
||||
|
||||
OfferInformation.fromJson(Map<String, dynamic> json) {
|
||||
service = json['service'];
|
||||
selfService = json['selfService'];
|
||||
unattended = json['unattended'];
|
||||
}
|
||||
}
|
||||
|
||||
class PaymentMethods {
|
||||
bool? cash;
|
||||
bool? debitCard;
|
||||
bool? creditCard;
|
||||
String? others;
|
||||
|
||||
PaymentMethods( {bool? cash, bool? debitCard, bool? creditCard, String? others}) {
|
||||
if (cash != null) {
|
||||
this.cash = cash;
|
||||
}
|
||||
if (debitCard != null) {
|
||||
this.debitCard = debitCard;
|
||||
}
|
||||
if (creditCard != null) {
|
||||
this.creditCard = creditCard;
|
||||
}
|
||||
if (others != null) {
|
||||
this.others = others;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PaymentMethods.fromJson(Map<String, dynamic> json) {
|
||||
cash = json['cash'];
|
||||
debitCard = json['debitCard'];
|
||||
creditCard = json['creditCard'];
|
||||
others = json['others'];
|
||||
}
|
||||
}
|
||||
|
||||
class PaymentArrangements {
|
||||
bool? cooperative;
|
||||
bool? clubCard;
|
||||
|
||||
PaymentArrangements({bool? cooperative, bool? clubCard}) {
|
||||
if (cooperative != null) {
|
||||
this.cooperative = cooperative;
|
||||
}
|
||||
if (clubCard != null) {
|
||||
this.clubCard = clubCard;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PaymentArrangements.fromJson(Map<String, dynamic> json) {
|
||||
cooperative = json['cooperative'];
|
||||
clubCard = json['clubCard'];
|
||||
}
|
||||
}
|
||||
|
||||
class Prices {
|
||||
String? fuelType;
|
||||
double? amount;
|
||||
String? label;
|
||||
|
||||
Prices({String? fuelType, double? amount, String? label}) {
|
||||
if (fuelType != null) {
|
||||
this.fuelType = fuelType;
|
||||
}
|
||||
if (amount != null) {
|
||||
this.amount = amount;
|
||||
}
|
||||
if (label != null) {
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
|
||||
Prices.fromJson(Map<String, dynamic> json) {
|
||||
fuelType = json['fuelType'];
|
||||
amount = json['amount'];
|
||||
label = json['label'];
|
||||
}
|
||||
}
|
||||
10
lib/data/models/login_model.dart
Normal file
@ -0,0 +1,10 @@
|
||||
class AppWriteLoginModel {
|
||||
String clientName;
|
||||
String clientId;
|
||||
|
||||
AppWriteLoginModel({
|
||||
this.clientName ='',
|
||||
this.clientId='',
|
||||
});
|
||||
|
||||
}
|
||||
15
lib/data/models/map_model.dart
Normal file
@ -0,0 +1,15 @@
|
||||
class AppWriteMapTrackPointsModel {
|
||||
String szUserId;
|
||||
String szAppWriteTrackUniqueId;
|
||||
double mnLongitudeTrackPoint;
|
||||
double mnLatitudeTrackPoint;
|
||||
String szDateTimeTrackPoint;
|
||||
|
||||
AppWriteMapTrackPointsModel({
|
||||
required this.szUserId,
|
||||
required this.szAppWriteTrackUniqueId,
|
||||
required this.mnLongitudeTrackPoint,
|
||||
required this.mnLatitudeTrackPoint,
|
||||
required this.szDateTimeTrackPoint,
|
||||
});
|
||||
}
|
||||
58
lib/data/models/tank_model.dart
Normal file
@ -0,0 +1,58 @@
|
||||
class AppWriteTankModel {
|
||||
String documentId;
|
||||
String userId;
|
||||
String date;
|
||||
String odometer;
|
||||
String liters;
|
||||
String pricePerLiter;
|
||||
String location;
|
||||
String? imageFileId;
|
||||
String? imageFileName;
|
||||
String? imageFileUrl;
|
||||
int? mnIndexCount;
|
||||
String? szSummePreis;
|
||||
|
||||
AppWriteTankModel({
|
||||
required this.documentId,
|
||||
required this.userId,
|
||||
required this.date,
|
||||
required this.odometer,
|
||||
required this.liters,
|
||||
required this.pricePerLiter,
|
||||
required this.location,
|
||||
this.imageFileId,
|
||||
this.imageFileName,
|
||||
this.imageFileUrl,
|
||||
}):szSummePreis = (double.tryParse(liters) != null && double.tryParse(pricePerLiter) != null)
|
||||
? (double.parse(liters) * double.parse(pricePerLiter)).toStringAsFixed(2)
|
||||
: null;
|
||||
|
||||
factory AppWriteTankModel.fromMap(Map<String, dynamic> map) {
|
||||
return AppWriteTankModel(
|
||||
documentId: map['\$id'] ?? '',
|
||||
userId: map['userId'] ?? '',
|
||||
date: map['date'] ?? '',
|
||||
odometer: map['odometer']?.toString() ?? '',
|
||||
liters: map['liters']?.toString() ?? '',
|
||||
pricePerLiter: map['pricePerLiter']?.toString() ?? '',
|
||||
location: map['location'] ?? '',
|
||||
imageFileId: map['imageFileId'],
|
||||
imageFileName: map['imageFileName'],
|
||||
imageFileUrl: map['imageFileUrl'],
|
||||
);
|
||||
}
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'\$id': documentId,
|
||||
'userId': userId,
|
||||
'date': date,
|
||||
'odometer': odometer,
|
||||
'liters': liters,
|
||||
'pricePerLiter': pricePerLiter,
|
||||
'location': location,
|
||||
'imageFileId': imageFileId,
|
||||
'imageFileName': imageFileName,
|
||||
'imageFileUrl': imageFileUrl,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,20 +1,22 @@
|
||||
import 'package:appwrite/models.dart' as models;
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:appwrite/appwrite.dart';
|
||||
import 'package:appwrite_flutter_starter_kit/data/models/log.dart';
|
||||
import 'package:appwrite_flutter_starter_kit/data/models/project_info.dart';
|
||||
import '../../data/models/log.dart';
|
||||
import '../../data/models/project_info.dart';
|
||||
|
||||
/// A repository responsible for handling network interactions with the Appwrite server.
|
||||
///
|
||||
/// It provides a helper method to ping the server.
|
||||
class AppwriteRepository {
|
||||
static const String pingPath = "/ping";
|
||||
static const String appwriteProjectId = String.fromEnvironment('APPWRITE_PROJECT_ID');
|
||||
static const String appwriteProjectName = String.fromEnvironment('APPWRITE_PROJECT_NAME');
|
||||
static const String appwritePublicEndpoint = String.fromEnvironment('APPWRITE_PUBLIC_ENDPOINT');
|
||||
// static const String appwriteProjectId = String.fromEnvironment('APPWRITE_PROJECT_ID');
|
||||
// static const String appwriteProjectName = String.fromEnvironment('APPWRITE_PROJECT_NAME');
|
||||
// static const String appwritePublicEndpoint = String.fromEnvironment('APPWRITE_PUBLIC_ENDPOINT');
|
||||
|
||||
final Client _client = Client()
|
||||
.setProject(appwriteProjectId)
|
||||
.setEndpoint(appwritePublicEndpoint);
|
||||
.setProject(dotenv.get('APPWRITE_PROJECT_ID'))
|
||||
.setEndpoint(dotenv.get('APPWRITE_PUBLIC_ENDPOINT'));
|
||||
|
||||
late final Account _account;
|
||||
late final Databases _databases;
|
||||
@ -31,9 +33,9 @@ class AppwriteRepository {
|
||||
|
||||
ProjectInfo getProjectInfo() {
|
||||
return ProjectInfo(
|
||||
endpoint: appwritePublicEndpoint,
|
||||
projectId: appwriteProjectId,
|
||||
projectName: appwriteProjectName,
|
||||
endpoint: dotenv.get('APPWRITE_PUBLIC_ENDPOINT'),
|
||||
projectId: dotenv.get('APPWRITE_PROJECT_ID'),
|
||||
projectName: dotenv.get('APPWRITE_PROJECT_NAME'),
|
||||
);
|
||||
}
|
||||
|
||||
@ -68,4 +70,64 @@ class AppwriteRepository {
|
||||
String _getCurrentDate() {
|
||||
return DateFormat("MMM dd, HH:mm").format(DateTime.now());
|
||||
}
|
||||
|
||||
Future<dynamic> logout() async =>
|
||||
await _account.deleteSession(sessionId: 'current');
|
||||
|
||||
Future<models.Session> login(Map map) async =>
|
||||
await _account.createEmailPasswordSession(
|
||||
email: map['email'],
|
||||
password: map['password'],
|
||||
);
|
||||
|
||||
Future<models.Session> signUpAnonymus() async =>
|
||||
await _account.createAnonymousSession();
|
||||
|
||||
Future<models.User> signup(Map map) async => _account.create(
|
||||
userId: ID.unique(),
|
||||
email: map['email'],
|
||||
password: map['password'],
|
||||
name: map['name'],
|
||||
);
|
||||
|
||||
Future<models.User> get getCurrentUser => _account.get();
|
||||
|
||||
// Tank Stop CRUD operations
|
||||
// Create, Update, Get, List Tank Stops
|
||||
Future<models.Document> createTankStop(Map map) async {
|
||||
final response = await _databases.createDocument(
|
||||
databaseId: dotenv.get('APPWRITE_DATABASE_ID'),
|
||||
collectionId: dotenv.get('APPWRITE_COLLECTION_ID'),
|
||||
documentId: ID.unique(),
|
||||
data: map,
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<models.DocumentList> listTankStops(String userId) async {
|
||||
final response = await _databases.listDocuments(
|
||||
databaseId: dotenv.get('APPWRITE_DATABASE_ID'),
|
||||
collectionId: dotenv.get('APPWRITE_COLLECTION_ID'),
|
||||
queries: [Query.equal('userId', userId)],
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<models.Document> updateTankStop(String documentId, Map<String, dynamic> map) async {
|
||||
final response = await _databases.updateDocument(
|
||||
databaseId: dotenv.get('APPWRITE_DATABASE_ID'),
|
||||
collectionId: dotenv.get('APPWRITE_COLLECTION_ID'),
|
||||
documentId: documentId,
|
||||
data: map,
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<dynamic> deleteTankStop(String documentId) async {
|
||||
return await _databases.deleteDocument(
|
||||
databaseId: dotenv.get('APPWRITE_DATABASE_ID'),
|
||||
collectionId: dotenv.get('APPWRITE_COLLECTION_ID'),
|
||||
documentId: documentId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
40
lib/data/repository/gasstation_repository.dart
Normal file
@ -0,0 +1,40 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
class GasStationRepository {
|
||||
static final GasStationRepository _instance =
|
||||
GasStationRepository._internal();
|
||||
|
||||
/// Singleton instance getter
|
||||
factory GasStationRepository() => _instance;
|
||||
|
||||
//Constructor???
|
||||
GasStationRepository._internal() {
|
||||
//init for something
|
||||
}
|
||||
|
||||
Future<dynamic> getGasStationsLocations(Map map) async {
|
||||
List<dynamic> data = [];
|
||||
var lat = map['lat'];
|
||||
var lng = map['lng'];
|
||||
var gas = map['gas'];
|
||||
// Hier kannst du die Logik hinzufügen, um den Standort zu verwenden, z.B.
|
||||
String baseUrl = dotenv.get('TANKSTOPS_BASE_URL');
|
||||
String getGasLocationLink ='$baseUrl?latitude=$lat&longitude=$lng&fuelType=$gas&includeClosed=false';
|
||||
final client = http.Client();
|
||||
var response = await client.get(Uri.parse(getGasLocationLink),headers: {'Content-Type': 'application/json', 'charset': 'utf-8'});
|
||||
//Response Data status
|
||||
if (response.statusCode == 200) {
|
||||
//Response is succsessful
|
||||
data = json.decode(utf8.decode(response.bodyBytes)); //get response data
|
||||
} else {
|
||||
debugPrint(response.statusCode.toString());
|
||||
}
|
||||
client.close();
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
97
lib/data/repository/location_repository.dart
Normal file
@ -0,0 +1,97 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
|
||||
class LocationRepository {
|
||||
|
||||
static final LocationRepository _instance = LocationRepository._internal();
|
||||
|
||||
/// Singleton instance getter
|
||||
factory LocationRepository() => _instance;
|
||||
|
||||
//Constructor???
|
||||
LocationRepository._internal() {
|
||||
//init for something
|
||||
}
|
||||
|
||||
|
||||
/// Überprüft, ob der Standortdienst aktiviert ist.
|
||||
Future<bool> isLocationServiceEnabled() async {
|
||||
return await Geolocator.isLocationServiceEnabled();
|
||||
}
|
||||
|
||||
/// Fragt die Berechtigung für den Standort ab.
|
||||
Future<LocationPermission> checkPermission() async {
|
||||
return await Geolocator.checkPermission();
|
||||
}
|
||||
|
||||
/// Fordert die Berechtigung für den Standort an.
|
||||
Future<LocationPermission> requestPermission() async {
|
||||
return await Geolocator.requestPermission();
|
||||
}
|
||||
|
||||
/// Liefert die aktuelle Position des Geräts.
|
||||
/// Wirft eine Exception, wenn der Dienst nicht aktiviert ist oder keine Berechtigung vorliegt.
|
||||
Future<Position> getCurrentPosition() async {
|
||||
bool serviceEnabled = await isLocationServiceEnabled();
|
||||
if (!serviceEnabled) {
|
||||
// Standortdienste sind nicht aktiviert.
|
||||
return Future.error('Location services are disabled.');
|
||||
}
|
||||
|
||||
LocationPermission permission = await checkPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
permission = await requestPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
// Berechtigungen sind verweigert.
|
||||
return Future.error('Location permissions are denied');
|
||||
}
|
||||
}
|
||||
|
||||
if (permission == LocationPermission.deniedForever) {
|
||||
// Berechtigungen sind dauerhaft verweigert.
|
||||
return Future.error(
|
||||
'Location permissions are permanently denied, we cannot request permissions.',
|
||||
);
|
||||
}
|
||||
|
||||
// Wenn alles in Ordnung ist, die Position zurückgeben.
|
||||
return await Geolocator.getCurrentPosition();
|
||||
}
|
||||
|
||||
Future<String> getNearbyLocation(Map map) async {
|
||||
String locationOrt = '?';
|
||||
var lat = map['lat'];
|
||||
var lng = map['lng'];
|
||||
// Hier kannst du die Logik hinzufügen, um den Standort zu verwenden, z.B.
|
||||
String ptvGeoLink =
|
||||
'https://api.myptv.com/geocoding/v1/locations/by-position/$lat/$lng?language=de&apiKey=${dotenv.get('PTV_GEOLINK_API_KEY')}';
|
||||
final client = http.Client();
|
||||
var response = await client.get(
|
||||
Uri.parse(ptvGeoLink),
|
||||
headers: {'Content-Type': 'application/json', 'charset': 'utf-8'},
|
||||
);
|
||||
//Response Data status
|
||||
if (response.statusCode == 200) {
|
||||
//Response is succsessful
|
||||
Map<String, dynamic> data = json.decode(
|
||||
utf8.decode(response.bodyBytes),
|
||||
); //get response data
|
||||
Map<String, dynamic> mapOfAddressfromPosition =
|
||||
data['locations'][0]['address']; //get response address of position
|
||||
if (mapOfAddressfromPosition.isNotEmpty) {
|
||||
locationOrt =
|
||||
'${mapOfAddressfromPosition['street'].toString()} ${mapOfAddressfromPosition['houseNumber'].toString()}, ${mapOfAddressfromPosition['postalCode'].toString()} ${mapOfAddressfromPosition['city'].toString()}';
|
||||
}
|
||||
} else {
|
||||
debugPrint(response.statusCode.toString());
|
||||
}
|
||||
client.close();
|
||||
|
||||
return locationOrt;
|
||||
}
|
||||
}
|
||||
BIN
lib/icons/gasolineCyberpunk.png
Normal file
|
After Width: | Height: | Size: 613 KiB |
BIN
lib/icons/kilometer.png
Normal file
|
After Width: | Height: | Size: 695 B |
BIN
lib/images/backgroundPitstopBlack.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
lib/images/gasolineGuru.jpg
Normal file
|
After Width: | Height: | Size: 228 KiB |
@ -1,6 +1,6 @@
|
||||
import 'package:appwrite_flutter_starter_kit/app.dart';
|
||||
import 'package:appwrite_flutter_starter_kit/utils/app_initializer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import './app.dart';
|
||||
import './utils/app_initializer.dart';
|
||||
|
||||
void main() async {
|
||||
await AppInitializer.initialize();
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import 'package:appwrite_flutter_starter_kit/data/models/log.dart';
|
||||
import 'package:appwrite_flutter_starter_kit/data/models/status.dart';
|
||||
import 'package:appwrite_flutter_starter_kit/data/repository/appwrite_repository.dart';
|
||||
import 'package:appwrite_flutter_starter_kit/ui/components/checkered_background.dart';
|
||||
import 'package:appwrite_flutter_starter_kit/ui/components/collapsible_bottomsheet.dart';
|
||||
import 'package:appwrite_flutter_starter_kit/ui/components/connection_status_view.dart';
|
||||
import 'package:appwrite_flutter_starter_kit/ui/components/getting_started_cards.dart';
|
||||
import 'package:appwrite_flutter_starter_kit/ui/components/top_platform_view.dart';
|
||||
import 'package:appwrite_flutter_starter_kit/utils/extensions/build_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../data/models/log.dart';
|
||||
import '../../data/models/status.dart';
|
||||
import '../../data/repository/appwrite_repository.dart';
|
||||
import '../../ui/components/checkered_background.dart';
|
||||
import '../../ui/components/collapsible_bottomsheet.dart';
|
||||
import '../../ui/components/connection_status_view.dart';
|
||||
import '../../ui/components/getting_started_cards.dart';
|
||||
import '../../ui/components/top_platform_view.dart';
|
||||
import '../../utils/extensions/build_context.dart';
|
||||
|
||||
class AppwriteStarterKit extends StatefulWidget {
|
||||
const AppwriteStarterKit({super.key});
|
||||
@ -26,7 +26,12 @@ class _AppwriteStarterKit extends State<AppwriteStarterKit> {
|
||||
return Scaffold(
|
||||
body: CheckeredBackground(
|
||||
child: SafeArea(
|
||||
minimum: EdgeInsets.only(top: context.isLargeScreen ? 24 : 16),
|
||||
minimum: EdgeInsets.only(
|
||||
top: context.isExtraWideScreen
|
||||
? 156
|
||||
: context.isLargeScreen
|
||||
? 24
|
||||
: 32),
|
||||
child: Stack(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
86
lib/pages/gaslist/gaslist_controller.dart
Normal file
@ -0,0 +1,86 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
import '../../data/models/gas_model.dart';
|
||||
import '../../data/repository/appwrite_repository.dart';
|
||||
import '../../pages/login/login_view.dart';
|
||||
import '../../pages/tanklist/tanklist_view.dart';
|
||||
|
||||
import './widgets/map_view.dart' show MapDialogView;
|
||||
import '../../data/repository/gasstation_repository.dart';
|
||||
|
||||
class GaslistController extends GetxController {
|
||||
final _dataBox = GetStorage('MyUserStorage');
|
||||
final szRxGasArt = 'DIE'.obs;
|
||||
//Gas Station Repository
|
||||
final GasStationRepository _gasStationRepository = GasStationRepository();
|
||||
final AppwriteRepository _authRepository = AppwriteRepository();
|
||||
final isLoadingList = false.obs;
|
||||
var gasStationsList = <GasModel>[].obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
loadListData();
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
@override
|
||||
void onReady() {}
|
||||
|
||||
@override
|
||||
void onClose() {}
|
||||
|
||||
Future<void> loadListData() async {
|
||||
isLoadingList(true);
|
||||
var lat = _dataBox.read('lastLatitude');
|
||||
var lng = _dataBox.read('lastLongitude');
|
||||
var gas = szRxGasArt.value;
|
||||
var result =
|
||||
await getGasStationsFromApi({'lat': lat, 'lng': lng, 'gas': gas});
|
||||
print('Gas Stations from API: $result');
|
||||
//Hier die Logik zum Laden der Daten einfügen
|
||||
gasStationsList.clear();
|
||||
gasStationsList.refresh();
|
||||
// add Map to GasModelList
|
||||
for (var element in result) {
|
||||
Map<String, dynamic> gasModelMap = (element as Map<String, dynamic>);
|
||||
var gasModelItem = GasModel.fromJson(gasModelMap);
|
||||
gasStationsList.add(gasModelItem);
|
||||
}
|
||||
//Simulate a delay for loading data
|
||||
|
||||
isLoadingList(false);
|
||||
update();
|
||||
}
|
||||
|
||||
Future<dynamic> getGasStationsFromApi(Map map) async {
|
||||
var result = await _gasStationRepository.getGasStationsLocations(map);
|
||||
return result;
|
||||
}
|
||||
|
||||
void goToListView() {
|
||||
Get.offAndToNamed(TanklistPage.namedRoute);
|
||||
}
|
||||
|
||||
Future<void> logoutSessionAndGoToLoginPage() async {
|
||||
// Handle logout logic here
|
||||
print('Logout session and go to login page');
|
||||
// Clear GetStorage session ID
|
||||
_dataBox.remove('sessionId');
|
||||
_dataBox.remove('userId');
|
||||
_dataBox.remove('userName');
|
||||
_dataBox.remove('userEmail');
|
||||
print('Session ID removed from GetStorage');
|
||||
await _authRepository.logout();
|
||||
Get.offAndToNamed(LoginPage.namedRoute);
|
||||
}
|
||||
|
||||
Future<void> openDirectionMaps(double lat, double lng) async {
|
||||
Get.dialog(
|
||||
MapDialogView(
|
||||
latitude: lat,
|
||||
longitude: lng,
|
||||
),
|
||||
barrierDismissible: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
146
lib/pages/gaslist/gaslist_view.dart
Normal file
@ -0,0 +1,146 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'gaslist_controller.dart';
|
||||
|
||||
class GaslistPage extends GetView<GaslistController> {
|
||||
static const namedRoute = '/gas-stations-list-page';
|
||||
const GaslistPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var gasCtrl = controller;
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
shadowColor: Colors.grey,
|
||||
title: const Text('Gas Stations'),
|
||||
centerTitle: true,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.list, color: Colors.grey.shade300),
|
||||
onPressed: () async {
|
||||
// Handle go to Chart View
|
||||
gasCtrl.goToListView();
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.logout, color: Colors.grey.shade300),
|
||||
onPressed: () async {
|
||||
// Handle logout logic here
|
||||
gasCtrl.logoutSessionAndGoToLoginPage();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Obx(
|
||||
() => gasCtrl.isLoadingList.value == true
|
||||
? Center(
|
||||
child: Text('GasStations'),
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 50,
|
||||
children: [
|
||||
Divider(
|
||||
color: Colors.grey.shade300,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
gasCtrl.szRxGasArt.value = 'DIE';
|
||||
await gasCtrl.loadListData();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.grey.shade800,
|
||||
foregroundColor: Colors
|
||||
.orange, // Hintergrundfarbe des Buttons
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text('Diesel'),
|
||||
Text('DIE'),
|
||||
],
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
gasCtrl.szRxGasArt.value = 'SUP';
|
||||
await gasCtrl.loadListData();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.grey.shade800,
|
||||
foregroundColor: Colors
|
||||
.orange, // Hintergrundfarbe des Buttons
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text('Benzin'),
|
||||
Text('SUP'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
color: Colors.grey.shade300,
|
||||
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: 5,
|
||||
itemBuilder: (context, index) {
|
||||
var gasStation = gasCtrl.gasStationsList[index];
|
||||
return ListTile(
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: Colors.grey, // Border color
|
||||
width: 1.0, // Border thickness
|
||||
),
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
),
|
||||
onTap: () {
|
||||
// Handle item tap if needed
|
||||
gasCtrl.openDirectionMaps(
|
||||
gasStation.location!.latitude!,
|
||||
gasStation.location!.longitude!);
|
||||
},
|
||||
title: Text(gasStation.name ?? 'No Name'),
|
||||
subtitle: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(gasStation.location?.address ??
|
||||
'No Address'),
|
||||
Text(gasStation.distance != null
|
||||
? '${gasStation.distance?.toStringAsFixed(2)} km'
|
||||
: 'No Distance'),
|
||||
],
|
||||
),
|
||||
trailing: gasStation.prices != null &&
|
||||
gasStation.prices!.isNotEmpty
|
||||
? Column(
|
||||
children: [
|
||||
Text(gasStation.prices?[0].fuelType ??
|
||||
'N/A'),
|
||||
Text(
|
||||
'${gasStation.prices?[0].amount?.toStringAsPrecision(4) ?? 'N/A'} €'),
|
||||
],
|
||||
)
|
||||
: const Text('N/A'),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
76
lib/pages/gaslist/widgets/map_view.dart
Normal file
@ -0,0 +1,76 @@
|
||||
// map_dialog_view.dart
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class MapDialogView extends StatelessWidget {
|
||||
final double latitude;
|
||||
final double longitude;
|
||||
|
||||
const MapDialogView({
|
||||
super.key,
|
||||
required this.latitude,
|
||||
required this.longitude,
|
||||
});
|
||||
|
||||
// Funktion zum Öffnen der Karten-URL
|
||||
void _openMap() async {
|
||||
String url = '';
|
||||
final coords = '$latitude,$longitude';
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
// Android: Startet die Google Maps Navigation
|
||||
url =
|
||||
'https://www.google.com/maps/dir/?api=1&destination=$coords'; // mode=d für Fahren
|
||||
} else if (Platform.isIOS) {
|
||||
// iOS: Startet die Apple Maps Navigation
|
||||
url =
|
||||
'https://maps.apple.com/?daddr=$coords&dirflg=d'; // daddr für destination, dirflg=d für driving
|
||||
} else {
|
||||
// Fallback-URL für die Google Maps Website mit Wegbeschreibung
|
||||
url = 'https://www.google.com/maps/dir/?api=1&destination=$coords';
|
||||
}
|
||||
// Hier die URL-Logik einfügen, die Sie bereits kennen
|
||||
//final url = 'http://googleusercontent.com/maps.google.com/8';
|
||||
final uri = Uri.parse(url);
|
||||
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri);
|
||||
} else {
|
||||
// Eine Snackbar oder ein Dialog, um den Fehler zu melden
|
||||
print('Fehler: Konnte Karten-App nicht öffnen.');
|
||||
}
|
||||
Get.back();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Karte öffnen'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start, // Passt die Höhe an den Inhalt an
|
||||
children: [
|
||||
Text('Koordinaten'),
|
||||
Text('Latitude: $latitude'),
|
||||
Text('Longitude: $longitude'),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: _openMap, // Ruft die Methode zum Öffnen der Karte auf
|
||||
child: const Text('Karten-App öffnen'),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(), // Schließt den Dialog
|
||||
child: const Text('Schließen'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
264
lib/pages/graph/graph_controller.dart
Normal file
@ -0,0 +1,264 @@
|
||||
import 'package:appwrite/appwrite.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
|
||||
import '../../data/models/chart_model.dart';
|
||||
import '../../data/models/tank_model.dart';
|
||||
import '../../data/repository/appwrite_repository.dart';
|
||||
import '../../utils/extensions/static_helper.dart';
|
||||
|
||||
|
||||
|
||||
class GraphController extends GetxController {
|
||||
|
||||
//AppWrite API-REST get Data
|
||||
final AppwriteRepository _authRepository = AppwriteRepository();
|
||||
|
||||
final _dataBox = GetStorage('MyUserStorage');
|
||||
final szRxUserId = 'NoUser'.obs;
|
||||
final szBarTitle = ''.obs;
|
||||
final listYearModel = <YearModel>[].obs;
|
||||
late List<AppWriteTankModel> tankListOriginal;
|
||||
final tankList = <AppWriteTankModel>[].obs;
|
||||
final yearValue = DateTime.now().year.obs;
|
||||
final blIsLoading = false.obs;
|
||||
final dataPointsEuro = <double>[].obs;
|
||||
final dataPointsGasoline = <double>[].obs;
|
||||
final pointsEuro = <PricePoints>[].obs;
|
||||
final pointsGasoline = <PricePoints>[].obs;
|
||||
final sumListData = <SumDataModel>[].obs;
|
||||
final mnCurrentAveragePerLiter = 0.00.obs;
|
||||
final pointsPerLiter = <PricePoints>[].obs;
|
||||
final dataPointsPerLiter = <double>[].obs;
|
||||
final mnCurrentSummEuroYear = 0.0.obs;
|
||||
final mnCurrentSummLiterYear = 0.0.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
szRxUserId(_dataBox.read('userId'));
|
||||
_getTankList();
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {}
|
||||
|
||||
void _getTankList() async {
|
||||
blIsLoading(true);
|
||||
bool isErrorByLoading = false;
|
||||
String message = '';
|
||||
try {
|
||||
await _authRepository
|
||||
.listTankStops(szRxUserId.value)
|
||||
.then((tankListData) {
|
||||
if (tankListData.documents.isEmpty) {
|
||||
blIsLoading(false);
|
||||
isErrorByLoading = true;
|
||||
message = 'Leere Liste keine Daten vorhanden';
|
||||
return;
|
||||
}
|
||||
tankList.clear();
|
||||
var data = tankListData.toMap();
|
||||
List d = data['documents'].toList();
|
||||
tankList.value = d
|
||||
.map((e) => AppWriteTankModel.fromMap(e['data']))
|
||||
.toList();
|
||||
tankList.sort((a, b) {
|
||||
final DateTime dateA = DateTime.parse(a.date);
|
||||
final DateTime dateB = DateTime.parse(b.date);
|
||||
return dateB.compareTo(dateA);
|
||||
});
|
||||
message = 'Liste wurde erfolgreich geladen';
|
||||
})
|
||||
.catchError((error) {
|
||||
blIsLoading(true);
|
||||
isErrorByLoading = true;
|
||||
if (error is AppwriteException) {
|
||||
message = error.message!;
|
||||
} else {
|
||||
message = 'Uuups da ist was schief gelaufen';
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
blIsLoading(true);
|
||||
isErrorByLoading = true;
|
||||
message = 'Fehler beim Laden der Tankliste';
|
||||
print('Error fetching tank list: $e');
|
||||
}
|
||||
String title = isErrorByLoading ? 'Fehler' : 'Erfolg';
|
||||
Get.snackbar(
|
||||
title,
|
||||
message,
|
||||
backgroundColor: isErrorByLoading ? Colors.red : Colors.green,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
duration: const Duration(seconds: 4),
|
||||
);
|
||||
tankListOriginal = tankList;
|
||||
update();
|
||||
setListMapYear();
|
||||
getTankListPerYear();
|
||||
}
|
||||
|
||||
List<String> getHeadDescription() {
|
||||
List<String> localListString = [];
|
||||
tankList.sort((a, b) {
|
||||
final DateTime dateA = DateTime.parse(a.date);
|
||||
final DateTime dateB = DateTime.parse(b.date);
|
||||
return dateB.compareTo(dateA);
|
||||
});
|
||||
if (tankList.isNotEmpty) {
|
||||
for (var benzinItem in tankList) {
|
||||
String szDay = '';
|
||||
var dateDay = DateTime.parse(benzinItem.date);
|
||||
if (dateDay.day <= 9) {
|
||||
szDay = '0${dateDay.day}';
|
||||
} else {
|
||||
szDay = '${dateDay.day}';
|
||||
}
|
||||
String szMonth = '';
|
||||
if (dateDay.month <= 9) {
|
||||
szMonth = '0${dateDay.month}';
|
||||
} else {
|
||||
szMonth = '${dateDay.month}';
|
||||
}
|
||||
String szData = '$szDay$szMonth';
|
||||
localListString.add(szData);
|
||||
}
|
||||
}
|
||||
update();
|
||||
return localListString;
|
||||
}
|
||||
|
||||
void setListMapYear() {
|
||||
int year = 2022;
|
||||
for (var i = year; i <= DateTime.now().year; i++) {
|
||||
listYearModel.add(YearModel('Jahr $year', year));
|
||||
year = year + 1;
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void getTankListPerYear() {
|
||||
blIsLoading.value = true;
|
||||
tankList
|
||||
.where((e) => DateTime.parse(e.date).year == yearValue.value)
|
||||
.toList();
|
||||
getDataPointsForGraph();
|
||||
getMonthListData();
|
||||
blIsLoading.value = false;
|
||||
update();
|
||||
}
|
||||
|
||||
void getDataPointsForGraph() {
|
||||
//daten ermitteln für Jahr xxxx
|
||||
//debugPrint('${yearValue.value}');
|
||||
List<double> mnDataPointsEuro = [];
|
||||
List<double> mnDataPointsGasoline = [];
|
||||
// 08.08.23 Mod. add Price per Liter
|
||||
List<double> mnDataPointsPerLiter = [];
|
||||
var mnSummPerLiter = 0.0;
|
||||
var mnTankListCount = tankList.length;
|
||||
for (var item in tankList) {
|
||||
var mnLiterGesamt = double.parse(item.liters);
|
||||
var mnEuroGesamt =
|
||||
double.parse(item.liters) * double.parse(item.pricePerLiter);
|
||||
var mnPerLiter = double.parse(item.pricePerLiter);
|
||||
mnDataPointsEuro.add(mnEuroGesamt);
|
||||
mnDataPointsGasoline.add(mnLiterGesamt);
|
||||
mnDataPointsPerLiter.add(mnPerLiter.toPrecision(2));
|
||||
mnSummPerLiter += mnPerLiter;
|
||||
}
|
||||
if (mnSummPerLiter > 0 && mnTankListCount > 0) {
|
||||
mnCurrentAveragePerLiter.value = (mnSummPerLiter / mnTankListCount)
|
||||
.toPrecision(2);
|
||||
}
|
||||
dataPointsEuro.value = mnDataPointsEuro;
|
||||
dataPointsGasoline.value = mnDataPointsGasoline;
|
||||
dataPointsPerLiter.value = mnDataPointsPerLiter;
|
||||
getPricePointsEuro();
|
||||
getPricePointsGasoline();
|
||||
getPricePointsPerLiter();
|
||||
update();
|
||||
}
|
||||
|
||||
void getMonthListData() {
|
||||
if (tankList.isNotEmpty) {
|
||||
sumListData.clear();
|
||||
List<AppWriteTankModel> tankListPerYear = tankList;
|
||||
List<dynamic> monthList = StaticHelper.listMonth;
|
||||
for (var monthMap in monthList) {
|
||||
var szMonth = monthMap['month'].toString();
|
||||
var szValueMonth = monthMap['value'];
|
||||
var result = StaticHelper.staticListGetDaysInBetween(
|
||||
tankListPerYear,
|
||||
szValueMonth,
|
||||
yearValue.value,
|
||||
);
|
||||
var tankListPerMon = result as List<AppWriteTankModel>;
|
||||
if (tankListPerMon.isNotEmpty) {
|
||||
SumDataModel sumDataModel = SumDataModel('', '', '', 0);
|
||||
sumDataModel.szMonth = szMonth;
|
||||
sumDataModel.mnTankungen = tankListPerMon.length;
|
||||
double mnSumEuro = 0.0;
|
||||
double mnSumBenzin = 0.0;
|
||||
for (var tankungModel in tankListPerMon) {
|
||||
var mnEuroGesamt =
|
||||
double.parse(tankungModel.liters) *
|
||||
double.parse(tankungModel.pricePerLiter);
|
||||
mnSumBenzin += double.parse(tankungModel.liters);
|
||||
mnSumEuro += mnEuroGesamt;
|
||||
}
|
||||
sumDataModel.szVerbrauch = mnSumBenzin.toStringAsFixed(2);
|
||||
sumDataModel.szSumme = mnSumEuro.toStringAsFixed(2);
|
||||
sumListData.add(sumDataModel);
|
||||
}
|
||||
}
|
||||
_currentSumForYear();
|
||||
}
|
||||
}
|
||||
|
||||
void getPricePointsEuro() {
|
||||
var listPoints = dataPointsEuro.indexed
|
||||
.map((e) => PricePoints(x: e.$1.toDouble(), y: e.$2))
|
||||
.toList();
|
||||
pointsEuro.value = listPoints;
|
||||
update();
|
||||
}
|
||||
|
||||
void getPricePointsGasoline() {
|
||||
var listPoints = dataPointsGasoline.indexed
|
||||
.map((e) => PricePoints(x: e.$1.toDouble(), y: e.$2))
|
||||
.toList();
|
||||
pointsGasoline.value = listPoints;
|
||||
update();
|
||||
}
|
||||
|
||||
void getPricePointsPerLiter() {
|
||||
var listPoints = dataPointsPerLiter.indexed
|
||||
.map((e) => PricePoints(x: e.$1.toDouble(), y: e.$2))
|
||||
.toList();
|
||||
pointsPerLiter.value = listPoints;
|
||||
update();
|
||||
}
|
||||
|
||||
void _currentSumForYear() {
|
||||
mnCurrentSummEuroYear.value = 0.0;
|
||||
mnCurrentSummLiterYear.value = 0.0;
|
||||
if (sumListData.isNotEmpty) {
|
||||
for (var rxListItem in sumListData) {
|
||||
mnCurrentSummEuroYear.value =
|
||||
mnCurrentSummEuroYear.value + double.parse(rxListItem.szSumme!);
|
||||
mnCurrentSummLiterYear.value =
|
||||
mnCurrentSummLiterYear.value +
|
||||
double.parse(rxListItem.szVerbrauch!);
|
||||
}
|
||||
}
|
||||
update();
|
||||
}
|
||||
}
|
||||
148
lib/pages/graph/graph_view.dart
Normal file
@ -0,0 +1,148 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../utils/extensions/constants.dart';
|
||||
import './graph_controller.dart';
|
||||
import './widgets/chart_desc.dart';
|
||||
import './widgets/chart_lines.dart';
|
||||
import './widgets/chart_single_lines.dart';
|
||||
import './widgets/my_list_tile_card.dart';
|
||||
import '../../pages/tanklist/tanklist_view.dart';
|
||||
|
||||
class GraphPage extends GetView<GraphController> {
|
||||
static const namedRoute = '/graph-statistic-page';
|
||||
const GraphPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Size queryDisplaySize = MediaQuery.of(context).size;
|
||||
var graphCtrl = controller;
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 50,
|
||||
child: IconButton(
|
||||
onPressed: () =>
|
||||
Get.offAndToNamed(TanklistPage.namedRoute),
|
||||
icon: const Icon(Icons.list),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: queryDisplaySize.width - 70,
|
||||
child: Obx(() => _displayDropDownMenue(graphCtrl)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
Obx(
|
||||
() => Container(
|
||||
color: Colors.grey.shade900,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Column(
|
||||
children: [
|
||||
ChartDescription(
|
||||
backgroundColor: kColorEuroChart,
|
||||
mnWidth: double.infinity,
|
||||
szDescText: '€ Jahressumme',
|
||||
szCurrentSumData:
|
||||
graphCtrl.mnCurrentSummEuroYear.toStringAsFixed(2),
|
||||
),
|
||||
ChartDescription(
|
||||
backgroundColor: kColorBenzinChart,
|
||||
mnWidth: double.infinity,
|
||||
szDescText: 'L Jahresverbrauch',
|
||||
szCurrentSumData:
|
||||
graphCtrl.mnCurrentSummLiterYear.toStringAsFixed(2),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
LineChartLines(
|
||||
firstLineColor: kColorEuroChart,
|
||||
secondLineColor: kColorBenzinChart,
|
||||
),
|
||||
Obx(
|
||||
() => graphCtrl.blIsLoading.value
|
||||
? const Center(
|
||||
child: CircularProgressIndicator(color: Colors.cyan),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
const Divider(),
|
||||
Obx(
|
||||
() => Container(
|
||||
color: Colors.grey.shade900,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: ChartDescription(
|
||||
backgroundColor: kColorPerLiterChart,
|
||||
mnWidth: queryDisplaySize.width,
|
||||
szDescText: '€/L Durchschnitt',
|
||||
szCurrentSumData: graphCtrl.mnCurrentAveragePerLiter.value
|
||||
.toStringAsFixed(2),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
LineChartSingleLine(singleLineColor: kColorPerLiterChart),
|
||||
const Divider(),
|
||||
Obx(
|
||||
() => graphCtrl.blIsLoading.value
|
||||
? const Center(
|
||||
child: CircularProgressIndicator(color: Colors.cyan),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
Obx(
|
||||
() => Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: graphCtrl.sumListData.length,
|
||||
itemBuilder: (context, index) {
|
||||
return MyListTileCard(graphCtrl: graphCtrl, index: index);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
DropdownButtonFormField _displayDropDownMenue(GraphController controller) {
|
||||
var graphCtrl = controller;
|
||||
var list = graphCtrl.listYearModel;
|
||||
var valueOfList = graphCtrl.yearValue.value;
|
||||
return DropdownButtonFormField(
|
||||
decoration: kInputDecorationDropDownMenueYear,
|
||||
items: list.map((map) {
|
||||
return DropdownMenuItem(
|
||||
value: map.mnYear,
|
||||
child: Text(map.szDescription),
|
||||
);
|
||||
}).toList(),
|
||||
isDense: true,
|
||||
isExpanded: true,
|
||||
initialValue: valueOfList,
|
||||
onChanged: ((value) {
|
||||
valueOfList = value;
|
||||
graphCtrl.yearValue.value = valueOfList;
|
||||
if (graphCtrl.yearValue.value > 0) {
|
||||
graphCtrl.getTankListPerYear();
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
124
lib/pages/graph/widgets/bar_chart_widget.dart
Normal file
@ -0,0 +1,124 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../utils/extensions/constants.dart';
|
||||
import '../graph_controller.dart';
|
||||
|
||||
class BarChartWidget extends StatelessWidget {
|
||||
final GraphController graphCtrl;
|
||||
const BarChartWidget({super.key, required this.graphCtrl});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 2,
|
||||
child: BarChart(
|
||||
BarChartData(
|
||||
//alignment: BarChartAlignment.center,
|
||||
maxY: 50,
|
||||
baselineY: 0,
|
||||
barTouchData: barTouchData,
|
||||
titlesData: titlesData,
|
||||
borderData: borderData,
|
||||
barGroups: barGroups,
|
||||
gridData: const FlGridData(show: false),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
BarTouchData get barTouchData => BarTouchData(
|
||||
enabled: false,
|
||||
touchTooltipData: BarTouchTooltipData(
|
||||
//tooltipBgColor: Colors.transparent,
|
||||
tooltipPadding: EdgeInsets.zero,
|
||||
tooltipMargin: 8,
|
||||
getTooltipItem:
|
||||
(
|
||||
BarChartGroupData group,
|
||||
int groupIndex,
|
||||
BarChartRodData rod,
|
||||
int rodIndex,
|
||||
) {
|
||||
return BarTooltipItem(
|
||||
rod.toY.round().toString(),
|
||||
const TextStyle(color: Colors.cyan, fontWeight: FontWeight.bold),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Widget getTitles(double value, TitleMeta meta) {
|
||||
List<String> dayMonListString = graphCtrl.getHeadDescription();
|
||||
for (var i = 0; i < dayMonListString.length; i++) {
|
||||
if (i == value.toInt()) {
|
||||
graphCtrl.szBarTitle.value = dayMonListString[i];
|
||||
}
|
||||
}
|
||||
return SideTitleWidget(
|
||||
fitInside: SideTitleFitInsideData.fromTitleMeta(meta),
|
||||
space: 4,
|
||||
meta: meta,
|
||||
child: Text(graphCtrl.szBarTitle.value, style: kBarTitleStyle),
|
||||
);
|
||||
}
|
||||
|
||||
FlTitlesData get titlesData => FlTitlesData(
|
||||
show: true,
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 30,
|
||||
getTitlesWidget: getTitles,
|
||||
),
|
||||
),
|
||||
leftTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||
topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||
rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||
);
|
||||
|
||||
FlBorderData get borderData => FlBorderData(show: false);
|
||||
|
||||
LinearGradient get _barsGradient => LinearGradient(
|
||||
colors: [Colors.blue.shade400, Colors.red.shade300],
|
||||
begin: Alignment.bottomCenter,
|
||||
end: Alignment.topCenter,
|
||||
);
|
||||
|
||||
List<BarChartGroupData> get barGroups => [
|
||||
BarChartGroupData(
|
||||
x: 0,
|
||||
barRods: [BarChartRodData(toY: 8, gradient: _barsGradient)],
|
||||
showingTooltipIndicators: [0],
|
||||
),
|
||||
BarChartGroupData(
|
||||
x: 1,
|
||||
barRods: [BarChartRodData(toY: 10, gradient: _barsGradient)],
|
||||
showingTooltipIndicators: [0],
|
||||
),
|
||||
BarChartGroupData(
|
||||
x: 2,
|
||||
barRods: [BarChartRodData(toY: 14, gradient: _barsGradient)],
|
||||
showingTooltipIndicators: [0],
|
||||
),
|
||||
BarChartGroupData(
|
||||
x: 3,
|
||||
barRods: [BarChartRodData(toY: 15, gradient: _barsGradient)],
|
||||
showingTooltipIndicators: [0],
|
||||
),
|
||||
BarChartGroupData(
|
||||
x: 4,
|
||||
barRods: [BarChartRodData(toY: 13, gradient: _barsGradient)],
|
||||
showingTooltipIndicators: [0],
|
||||
),
|
||||
BarChartGroupData(
|
||||
x: 5,
|
||||
barRods: [BarChartRodData(toY: 10, gradient: _barsGradient)],
|
||||
showingTooltipIndicators: [0],
|
||||
),
|
||||
BarChartGroupData(
|
||||
x: 6,
|
||||
barRods: [BarChartRodData(toY: 16, gradient: _barsGradient)],
|
||||
showingTooltipIndicators: [0],
|
||||
),
|
||||
];
|
||||
}
|
||||
43
lib/pages/graph/widgets/chart_desc.dart
Normal file
@ -0,0 +1,43 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../utils/extensions/constants.dart';
|
||||
|
||||
|
||||
|
||||
class ChartDescription extends StatelessWidget {
|
||||
final double mnWidth;
|
||||
final Color backgroundColor;
|
||||
final String szDescText;
|
||||
final String szCurrentSumData;
|
||||
const ChartDescription({
|
||||
super.key,
|
||||
required this.mnWidth,
|
||||
required this.backgroundColor,
|
||||
required this.szDescText,
|
||||
required this.szCurrentSumData,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: mnWidth,
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
width: 70,
|
||||
height: 20,
|
||||
child: Center(
|
||||
child: Text(szCurrentSumData, style: kChartDescriptionFontStyle),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(szDescText, style: kTextStyle),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
49
lib/pages/graph/widgets/chart_lines.dart
Normal file
@ -0,0 +1,49 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../graph_controller.dart';
|
||||
|
||||
// 08.08.23 Mod. Line Chart with tree Lines
|
||||
class LineChartLines extends GetView<GraphController> {
|
||||
final Color firstLineColor;
|
||||
final Color secondLineColor;
|
||||
|
||||
const LineChartLines({
|
||||
super.key,
|
||||
required this.firstLineColor,
|
||||
required this.secondLineColor,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final GraphController gCtrl = controller;
|
||||
return AspectRatio(
|
||||
aspectRatio: 3,
|
||||
child: Obx(
|
||||
() => LineChart(
|
||||
LineChartData(
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
color: firstLineColor,
|
||||
spots: gCtrl.pointsEuro
|
||||
.map((point) => FlSpot(point.x, point.y))
|
||||
.toList(),
|
||||
isCurved: false,
|
||||
dotData: const FlDotData(show: true),
|
||||
),
|
||||
LineChartBarData(
|
||||
isStepLineChart: false,
|
||||
color: secondLineColor,
|
||||
spots: gCtrl.pointsGasoline
|
||||
.map((point) => FlSpot(point.x, point.y))
|
||||
.toList(),
|
||||
isCurved: false,
|
||||
dotData: const FlDotData(show: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
36
lib/pages/graph/widgets/chart_single_lines.dart
Normal file
@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../graph_controller.dart';
|
||||
|
||||
// 08.08.23 Mod. Line Chart with tree Lines
|
||||
class LineChartSingleLine extends GetView<GraphController> {
|
||||
final Color singleLineColor;
|
||||
|
||||
const LineChartSingleLine({super.key, required this.singleLineColor});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final GraphController gCtrl = controller;
|
||||
return AspectRatio(
|
||||
aspectRatio: 3.55,
|
||||
child: Obx(
|
||||
() => LineChart(
|
||||
LineChartData(
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
color: singleLineColor,
|
||||
spots: gCtrl.pointsPerLiter
|
||||
.map((point) => FlSpot(point.x, point.y))
|
||||
.toList(),
|
||||
isCurved: false,
|
||||
dotData: const FlDotData(show: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
46
lib/pages/graph/widgets/my_list_tile_card.dart
Normal file
@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../utils/extensions/constants.dart';
|
||||
import '../graph_controller.dart';
|
||||
|
||||
|
||||
class MyListTileCard extends StatelessWidget {
|
||||
const MyListTileCard({
|
||||
super.key,
|
||||
required this.graphCtrl,
|
||||
required this.index,
|
||||
});
|
||||
|
||||
final GraphController graphCtrl;
|
||||
final int index;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
shadowColor: Colors.grey.shade200,
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
'${graphCtrl.sumListData[index].szMonth}',
|
||||
style: kTextStyle,
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Monatsausgaben: ${graphCtrl.sumListData[index].szSumme} €',
|
||||
style: kTextStyleSub,
|
||||
),
|
||||
Text(
|
||||
'Monatsverbrauch: ${graphCtrl.sumListData[index].szVerbrauch} L',
|
||||
style: kTextStyleSub,
|
||||
),
|
||||
Text(
|
||||
'Tankungen: ${graphCtrl.sumListData[index].mnTankungen}',
|
||||
style: kTextStyleSub,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
232
lib/pages/login/login_controller.dart
Normal file
@ -0,0 +1,232 @@
|
||||
import 'package:appwrite/appwrite.dart';
|
||||
import 'package:appwrite/models.dart' as models;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
|
||||
import '../../data/repository/appwrite_repository.dart';
|
||||
import '../../utils/extensions/static_helper.dart';
|
||||
import '../tank/tank_view.dart';
|
||||
|
||||
class LoginController extends GetxController {
|
||||
final AppwriteRepository _authRepository = AppwriteRepository();
|
||||
|
||||
final isVisible = false.obs;
|
||||
//Form Key
|
||||
final formKey = GlobalKey<FormState>();
|
||||
bool isFormValid = false;
|
||||
|
||||
final _dataBox = GetStorage('MyUserStorage');
|
||||
|
||||
final emailController = TextEditingController();
|
||||
final passwordController = TextEditingController();
|
||||
final nameController = TextEditingController();
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
print('LoginController initialized');
|
||||
}
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
// Initialize any necessary data or state here
|
||||
var isSessionId = _dataBox.hasData('sessionId');
|
||||
if (isSessionId) {
|
||||
// If session ID exists, navigate to TankPage
|
||||
print('Session ID found, navigating to TankPage');
|
||||
goToTankPage();
|
||||
} else {
|
||||
// If no session ID, initialize the login controller
|
||||
print('No session ID found, initializing LoginController');
|
||||
}
|
||||
// This method is called when the controller is ready
|
||||
print('LoginController is ready');
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
// Clean up any resources or listeners here
|
||||
emailController.dispose();
|
||||
passwordController.dispose();
|
||||
nameController.dispose();
|
||||
// emailFocusNode.dispose();
|
||||
// passwordFocusNode.dispose();
|
||||
// nameFocusNode.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
void clearTextEditingController() {
|
||||
emailController.clear();
|
||||
passwordController.clear();
|
||||
nameController.clear();
|
||||
}
|
||||
|
||||
String? validateEmail(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Bitte geben Sie eine E-Mail-Adresse ein';
|
||||
}
|
||||
if (!GetUtils.isEmail(value)) {
|
||||
return 'Bitte geben Sie eine gültige E-Mail-Adresse ein';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? validatePassword(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Bitte geben Sie ein Passwort ein';
|
||||
}
|
||||
if (value.length < 8) {
|
||||
return 'Das Passwort muss mindestens 8 Zeichen lang sein';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? validateName(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Bitte geben Sie Ihren Namen ein';
|
||||
}
|
||||
if (value.length < 3) {
|
||||
return 'Der Name muss mindestens 3 Zeichen lang sein';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var message = 'NoMessage!';
|
||||
var isError = false;
|
||||
|
||||
// Registrierung
|
||||
Future<void> register() async {
|
||||
isError = false;
|
||||
message = 'NoMessage!';
|
||||
isFormValid = formKey.currentState!.validate();
|
||||
if (!isFormValid) {
|
||||
print('Formular ist ungültig');
|
||||
return;
|
||||
} else {
|
||||
print('Formular ist gültig');
|
||||
formKey.currentState!.save();
|
||||
try {
|
||||
await _authRepository.signup({
|
||||
'email': emailController.text,
|
||||
'password': passwordController.text,
|
||||
'name': nameController.text,
|
||||
}).then((models.User userValue) {
|
||||
// GetStorage data storage
|
||||
isVisible(false);
|
||||
print(
|
||||
'User was stored and Loggedin: ${userValue.name}, ${userValue.$id}, ${userValue.email}',
|
||||
);
|
||||
// Store session ID in GetStorage
|
||||
_dataBox.write('userId', userValue.$id);
|
||||
_dataBox.write('userName', userValue.name);
|
||||
_dataBox.write('userEmail', userValue.email);
|
||||
// Go to TankPage
|
||||
goToTankPage();
|
||||
message = 'Sie wurden registriert und Angemeldet!!';
|
||||
StaticHelper.getMySnackeBar('Erfolg', message, Colors.green);
|
||||
}).catchError((error) {
|
||||
if (error is AppwriteException) {
|
||||
// Handle specific Appwrite exceptions
|
||||
print('Appwrite Fehler: ${error.message}');
|
||||
message = 'Appwrite Fehler: ${error.message}';
|
||||
isError = true;
|
||||
} else {
|
||||
// Handle other types of errors
|
||||
message = 'Allgemeiner Fehler: $error';
|
||||
print('Allgemeiner Fehler: $error');
|
||||
isError = true;
|
||||
}
|
||||
message = 'Fehler bei der Registrierung: $error';
|
||||
print('Fehler bei der Registrierung: $error');
|
||||
//Clear GetStorage session ID
|
||||
StaticHelper.removeFromStorage();
|
||||
isError = true;
|
||||
});
|
||||
} catch (e) {
|
||||
message = 'Fehler bei Registrierung: $e';
|
||||
print('Fehler bei Registrierung: $e');
|
||||
//Clear GetStorage session ID
|
||||
StaticHelper.removeFromStorage();
|
||||
isError = true;
|
||||
}
|
||||
if (isError) StaticHelper.getMySnackeBar('Fehler', message, Colors.red);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _getCurrentLoggedinUser() async {
|
||||
await _authRepository.getCurrentUser.then((models.User userValue) {
|
||||
// GetStorage data storage
|
||||
print(
|
||||
'User was stored and Loggedin: ${userValue.name}, ${userValue.$id}, ${userValue.email}',
|
||||
);
|
||||
// Store session ID in GetStorage
|
||||
_dataBox.write('userId', userValue.$id);
|
||||
_dataBox.write('userName', userValue.name);
|
||||
_dataBox.write('userEmail', userValue.email);
|
||||
}).catchError((error) {
|
||||
print('Fehler beim Abrufen des Benutzers: $error');
|
||||
});
|
||||
}
|
||||
|
||||
// Login
|
||||
Future<void> login() async {
|
||||
message = 'NoMessage!';
|
||||
isError = false;
|
||||
|
||||
isFormValid = formKey.currentState!.validate();
|
||||
if (!isFormValid) {
|
||||
print('Formular ist ungültig');
|
||||
return;
|
||||
} else {
|
||||
print('Formular ist gültig');
|
||||
formKey.currentState!.save();
|
||||
try {
|
||||
await _authRepository.login({
|
||||
'email': emailController.text,
|
||||
'password': passwordController.text,
|
||||
}).then((models.Session session) {
|
||||
// Store session ID in GetStorage
|
||||
_dataBox.write('sessionId', session.$id);
|
||||
print('Session ID stored: ${_dataBox.read('sessionId')}');
|
||||
print(
|
||||
'Erfolgreich eingeloggt: ${session.$id}\n${session.providerUid}',
|
||||
);
|
||||
_getCurrentLoggedinUser().then((_) {
|
||||
// Go to TankPage
|
||||
goToTankPage();
|
||||
StaticHelper.getMySnackeBar(
|
||||
'Erfolg', 'Sie wurden Angemeldet!!', Colors.green);
|
||||
});
|
||||
}).catchError((error) {
|
||||
if (error is AppwriteException) {
|
||||
// Handle specific Appwrite exceptions
|
||||
isError = true;
|
||||
message = 'Appwrite Fehler: ${error.message}';
|
||||
print('Appwrite Fehler: ${error.message}');
|
||||
} else {
|
||||
// Handle other types of errors
|
||||
print('Allgemeiner Fehler: $error');
|
||||
isError = true;
|
||||
message = 'Allgemeiner Fehler: $error';
|
||||
}
|
||||
print('Fehler beim Login: $error');
|
||||
isError = true;
|
||||
message = 'Fehler beim Login: $error';
|
||||
_authRepository.logout();
|
||||
});
|
||||
} catch (e) {
|
||||
print('Fehler beim Login: $e');
|
||||
isError = true;
|
||||
message = 'Fehler beim Login: $e';
|
||||
_authRepository.logout();
|
||||
}
|
||||
if (isError) StaticHelper.getMySnackeBar('Fehler', message, Colors.red);
|
||||
}
|
||||
}
|
||||
|
||||
void goToTankPage() {
|
||||
Get.offAndToNamed(TankPage.namedRoute, arguments: {'from': 'Input'});
|
||||
}
|
||||
}
|
||||
162
lib/pages/login/login_view.dart
Normal file
@ -0,0 +1,162 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'login_controller.dart';
|
||||
|
||||
class LoginPage extends GetView<LoginController> {
|
||||
static const namedRoute = '/login-page';
|
||||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
headerTextTankGuru(),
|
||||
const SizedBox(height: 5),
|
||||
headerTextDescription(),
|
||||
const SizedBox(height: 10),
|
||||
loadingImage(),
|
||||
inputFields(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
//the structure of the login page
|
||||
SizedBox loadingImage() {
|
||||
return SizedBox(
|
||||
width: 300,
|
||||
height: 300,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
child: Image.asset('lib/images/gasolineGuru.jpg', fit: BoxFit.cover),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Text headerTextTankGuru() {
|
||||
return Text(
|
||||
'Tank GURU',
|
||||
style: TextStyle(
|
||||
fontSize: 35,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.teal[600],
|
||||
letterSpacing: 2,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Text headerTextDescription() {
|
||||
return Text(
|
||||
'Das ultimative Tanken',
|
||||
style: TextStyle(fontSize: 20, color: Colors.grey[300]),
|
||||
);
|
||||
}
|
||||
|
||||
Widget inputFields() {
|
||||
return Obx(
|
||||
() => SizedBox(
|
||||
width: 300,
|
||||
child: Form(
|
||||
key: controller.formKey,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: 20),
|
||||
TextFormField(
|
||||
validator: (value) => controller.validateEmail(value),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
controller: controller.emailController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Email',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
TextFormField(
|
||||
validator: (value) => controller.validatePassword(value),
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
controller: controller.passwordController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Password',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
obscureText: true,
|
||||
),
|
||||
if (controller.isVisible.value) ...[
|
||||
SizedBox(height: 20),
|
||||
TextFormField(
|
||||
validator: (value) => controller.validateName(value),
|
||||
keyboardType: TextInputType.name,
|
||||
controller: controller.nameController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Name',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
],
|
||||
SizedBox(height: 20),
|
||||
SizedBox(
|
||||
width: 300,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
controller.isVisible.value == false
|
||||
? controller.login()
|
||||
: controller.register();
|
||||
},
|
||||
child: controller.isVisible.value == false
|
||||
? Text('Login')
|
||||
: Text('Registrieren'),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('Noch kein Konto? '),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
if (!controller.isVisible.value) {
|
||||
controller.isVisible.value = true;
|
||||
} else {
|
||||
controller.isVisible.value = false;
|
||||
}
|
||||
},
|
||||
child: controller.isVisible.value == false
|
||||
? Text(
|
||||
'Sign Up',
|
||||
style: TextStyle(
|
||||
color: Colors.teal,
|
||||
fontWeight: FontWeight.bold,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
'Sign In',
|
||||
style: TextStyle(
|
||||
color: Colors.teal,
|
||||
fontWeight: FontWeight.bold,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
251
lib/pages/print/print_controller.dart
Normal file
@ -0,0 +1,251 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:pdf/pdf.dart';
|
||||
import 'package:pdf/widgets.dart' as pw;
|
||||
import 'package:printing/printing.dart';
|
||||
|
||||
import '../../data/models/tank_model.dart';
|
||||
import '../tanklist/tanklist_view.dart';
|
||||
|
||||
class PrintController extends GetxController {
|
||||
final argunments = Get.arguments;
|
||||
final List<dynamic> tankList = Get.arguments['tankList'] ?? [];
|
||||
final String year = Get.arguments['year'] ?? 'NoYear';
|
||||
final String summeLiter = Get.arguments['summeLiter'] ?? '0.0';
|
||||
final String summePreis = Get.arguments['summePreis'] ?? '0.0';
|
||||
|
||||
Future<void> printPdfReport() async {
|
||||
final doc = pw.Document();
|
||||
final font = await PdfGoogleFonts.robotoRegular();
|
||||
final List<AppWriteTankModel> tankungen =
|
||||
tankList.map((e) => AppWriteTankModel.fromMap(e)).toList();
|
||||
|
||||
// Daten nach Monat gruppieren
|
||||
final dFormat = DateFormat('MMMM yyyy', 'de');
|
||||
final Map<String, List<AppWriteTankModel>> tankungenByMonth = {};
|
||||
for (var tankung in tankungen) {
|
||||
final month = dFormat.format(DateTime.parse(tankung.date));
|
||||
if (!tankungenByMonth.containsKey(month)) {
|
||||
tankungenByMonth[month] = [];
|
||||
}
|
||||
tankungenByMonth[month]!.add(tankung);
|
||||
}
|
||||
|
||||
doc.addPage(
|
||||
pw.Page(
|
||||
pageFormat: PdfPageFormat.a4.copyWith(
|
||||
marginBottom: 1.0 * PdfPageFormat.cm,
|
||||
marginLeft: 1.0 * PdfPageFormat.cm,
|
||||
marginRight: 1.0 * PdfPageFormat.cm,
|
||||
marginTop: 1.0 * PdfPageFormat.cm),
|
||||
build: (pw.Context context) {
|
||||
return pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
//Header
|
||||
// PDF-Design hier
|
||||
pw.Text('Tankbericht $year',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
color: PdfColors.blue900,
|
||||
font: font)),
|
||||
pw.SizedBox(height: 10),
|
||||
pw.Text('Alle Tankungen im Überblick',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 16, color: PdfColors.grey700, font: font)),
|
||||
pw.Divider(color: PdfColors.grey400),
|
||||
pw.SizedBox(height: 20),
|
||||
// Data
|
||||
// Dynamische Erstellung der monatlichen Abschnitte
|
||||
...tankungenByMonth.entries.map((entry) {
|
||||
final monthName = entry.key;
|
||||
final monthlyData = entry.value;
|
||||
|
||||
// Monatliche Summen berechnen
|
||||
final double monthlyLiters = monthlyData.fold(
|
||||
0.0, (sum, item) => sum + double.parse(item.liters));
|
||||
final double monthlyPrice = monthlyData.fold(
|
||||
0.0,
|
||||
(sum, item) =>
|
||||
sum + double.parse(item.szSummePreis ?? '0.0'));
|
||||
final double monthlyAvrLiterPrice =
|
||||
monthlyPrice / monthlyLiters;
|
||||
// Monatlicher Abschnitt
|
||||
// Erstelle die Datenzeilen
|
||||
final List<List<String>> tableData = monthlyData.map((item) {
|
||||
var modDate = item.date.substring(5);
|
||||
var modPreisPerLiter = item.pricePerLiter.padRight(5, '0');
|
||||
return [
|
||||
modDate,
|
||||
item.location,
|
||||
item.liters,
|
||||
modPreisPerLiter,
|
||||
item.szSummePreis ?? '0.0',
|
||||
item.odometer
|
||||
];
|
||||
}).toList();
|
||||
var monthAvrPricePerLiter = monthlyAvrLiterPrice.toStringAsFixed(3);
|
||||
// Füge die Summenzeile hinzu
|
||||
tableData.add([
|
||||
'Summe',
|
||||
'', // Leere Zelle für Ort
|
||||
(monthlyLiters.toStringAsFixed(2)),
|
||||
'$monthAvrPricePerLiter Ø', // Leere Zelle für Preis/L
|
||||
(monthlyPrice.toStringAsFixed(2)),
|
||||
''
|
||||
]);
|
||||
|
||||
return pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Monatsüberschrift
|
||||
pw.Text(
|
||||
monthName,
|
||||
style: pw.TextStyle(
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
fontSize: 18,
|
||||
font: font,
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 10),
|
||||
// Tabelle für den jeweiligen Monat
|
||||
// pw.TableHelper.fromTextArray(
|
||||
// headers: [
|
||||
// 'Datum',
|
||||
// 'Ort',
|
||||
// 'Menge (L)',
|
||||
// 'Preis/L (€)',
|
||||
// 'Summe (€)',
|
||||
// 'KM-Stand'
|
||||
// ],
|
||||
// cellAlignment: pw.Alignment.centerLeft,
|
||||
// border: pw.TableBorder.all(color: PdfColors.grey200),
|
||||
// headerStyle: pw.TextStyle(
|
||||
// fontWeight: pw.FontWeight.bold, font: font),
|
||||
// data: tableData,
|
||||
// cellStyle: pw.TextStyle(font: font),
|
||||
// ),
|
||||
|
||||
// Manuelle Erstellung der Tabelle mit individueller Formatierung
|
||||
pw.Table(
|
||||
border: pw.TableBorder.all(color: PdfColors.grey200),
|
||||
columnWidths: {
|
||||
0: const pw.FlexColumnWidth(1.0), // Datum
|
||||
1: const pw.FlexColumnWidth(4.0), // Ort
|
||||
2: const pw.FlexColumnWidth(1.3), // Menge
|
||||
3: const pw.FlexColumnWidth(1.3), // Preis/L
|
||||
4: const pw.FlexColumnWidth(1.4), // Summe
|
||||
5: const pw.FlexColumnWidth(1.3), // KM-Stand
|
||||
},
|
||||
children: [
|
||||
// Kopfzeile mit grauem Hintergrund
|
||||
pw.TableRow(
|
||||
decoration:
|
||||
const pw.BoxDecoration(color: PdfColors.grey200),
|
||||
children: [
|
||||
_buildHeaderCell('Datum', font),
|
||||
_buildHeaderCell('Ort', font),
|
||||
_buildHeaderCell('Menge(L)', font),
|
||||
_buildHeaderCell('Preis/L(€)', font),
|
||||
_buildHeaderCell('Summe(€)', font),
|
||||
_buildHeaderCell('Km-Stand', font),
|
||||
],
|
||||
),
|
||||
// Datenzeilen
|
||||
...monthlyData.map((item) {
|
||||
var modDate = item.date.substring(5);
|
||||
var modPreisPerLiter =
|
||||
item.pricePerLiter.padRight(5, '0');
|
||||
return pw.TableRow(
|
||||
children: [
|
||||
_buildDataCell(modDate, font),
|
||||
_buildDataCell(item.location, font),
|
||||
_buildDataCell(item.liters, font),
|
||||
_buildDataCell(modPreisPerLiter, font),
|
||||
_buildDataCell(item.szSummePreis!, font),
|
||||
_buildDataCell(item.odometer, font),
|
||||
],
|
||||
);
|
||||
}),
|
||||
// Summenzeile mit ANDERER grauem Hintergrund
|
||||
pw.TableRow(
|
||||
decoration:
|
||||
const pw.BoxDecoration(color: PdfColors.grey300),
|
||||
children: [
|
||||
_buildDataCell('', font),
|
||||
_buildDataCell('Gesamt Summe Monat', font),
|
||||
_buildDataCell('${monthlyLiters.toStringAsFixed(2)} L', font),
|
||||
_buildDataCell('$monthAvrPricePerLiter Ø', font),
|
||||
_buildDataCell('${monthlyPrice.toStringAsFixed(2)} €', font),
|
||||
_buildDataCell('', font)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
pw.SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
}),
|
||||
//footer
|
||||
pw.Spacer(),
|
||||
pw.Divider(color: PdfColors.grey400),
|
||||
pw.Container(
|
||||
alignment: pw.Alignment.centerRight,
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.end,
|
||||
children: [
|
||||
pw.Text('Jahres-Gesamtmenge: $summeLiter L',
|
||||
style: pw.TextStyle(
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
fontSize: 16,
|
||||
font: font)),
|
||||
pw.Text('Jahres-Gesamtsumme: $summePreis €',
|
||||
style: pw.TextStyle(
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
fontSize: 16,
|
||||
color: PdfColors.blue700,
|
||||
font: font)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Druckansicht öffnen
|
||||
await Printing.layoutPdf(
|
||||
onLayout: (PdfPageFormat format) async => doc.save(),
|
||||
);
|
||||
}
|
||||
|
||||
void goToTankListPage() async {
|
||||
await Get.offAndToNamed(TanklistPage.namedRoute);
|
||||
}
|
||||
|
||||
// Hilfs-Widgets für die Zellen
|
||||
pw.Widget _buildHeaderCell(String text, pw.Font font) {
|
||||
return pw.Container(
|
||||
alignment: pw.Alignment.centerLeft,
|
||||
padding: const pw.EdgeInsets.symmetric(vertical: 5, horizontal: 8),
|
||||
child: pw.Text(
|
||||
text,
|
||||
style: pw.TextStyle(fontWeight: pw.FontWeight.bold, font: font),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pw.Widget _buildDataCell(String text, pw.Font font, {bool alignRight = false}) {
|
||||
return pw.Container(
|
||||
alignment: alignRight ? pw.Alignment.centerRight : pw.Alignment.centerLeft,
|
||||
padding: const pw.EdgeInsets.symmetric(vertical: 5, horizontal: 8),
|
||||
child: pw.Text(
|
||||
text,
|
||||
style: pw.TextStyle(font: font),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
40
lib/pages/print/print_view.dart
Normal file
@ -0,0 +1,40 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import './print_controller.dart';
|
||||
|
||||
class PrintPage extends GetView<PrintController> {
|
||||
static const namedRoute = '/print-pdf-page';
|
||||
const PrintPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var printCtrl = controller;
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('PDF-Bericht'),
|
||||
centerTitle: true,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.list, color: Colors.white),
|
||||
onPressed: () {
|
||||
// Handle logout logic here
|
||||
printCtrl.goToTankListPage();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Center(
|
||||
child: ElevatedButton(
|
||||
// Ruft die Controller-Methode auf
|
||||
onPressed: () => printCtrl.printPdfReport(),
|
||||
child: const Text('PDF erstellen & drucken'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
337
lib/pages/tank/tank_controller.dart
Normal file
@ -0,0 +1,337 @@
|
||||
import 'package:appwrite/appwrite.dart';
|
||||
import 'package:appwrite/models.dart' as models;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../utils/extensions/static_helper.dart';
|
||||
import '../../data/repository/location_repository.dart';
|
||||
import '../../data/repository/appwrite_repository.dart';
|
||||
import '../login/login_view.dart';
|
||||
import '../tanklist/tanklist_view.dart';
|
||||
|
||||
class TankController extends GetxController {
|
||||
final _dataBox = GetStorage('MyUserStorage');
|
||||
//AppWrite API-REST get Data
|
||||
final AppwriteRepository _authRepository = AppwriteRepository();
|
||||
// GEOLOCATING Services
|
||||
final LocationRepository _locationRepository = LocationRepository();
|
||||
|
||||
// Rx-Variablen für die UI, um auf Änderungen zu reagieren
|
||||
final circleAvatarUserChar = 'A'.obs;
|
||||
final userNameToDisplay = 'Test User'.obs;
|
||||
final Rx<Position?> currentPosition = Rx<Position?>(null);
|
||||
final Rx<bool> isLoading = false.obs;
|
||||
final Rx<String?> errorMessage = Rx<String?>(null);
|
||||
//final rxOrtString = '?'.obs;
|
||||
final rxSessionIdString = '?'.obs;
|
||||
final rxSummePreisString = '0.00'.obs;
|
||||
// TextEditingController für die Formulareingaben
|
||||
final formKeyTank = GlobalKey<FormState>();
|
||||
bool isFormValid = false;
|
||||
DateTime? _selectedDateTime;
|
||||
final dateController = TextEditingController();
|
||||
final f = DateFormat('yyyy-MM-dd');
|
||||
final kilometerStandEdittingController = TextEditingController();
|
||||
final mengeController = TextEditingController();
|
||||
final pricePerLiterController = TextEditingController();
|
||||
final ortController = TextEditingController();
|
||||
|
||||
final FocusNode firstFocusNode = FocusNode(); // Deklariere den FocusNode
|
||||
|
||||
final isEditMode = false.obs;
|
||||
String documentId = '';
|
||||
|
||||
// Methode für das ausgewählte Datum
|
||||
Future<void> selectDateTime(BuildContext context) async {
|
||||
// 1. Datum auswählen
|
||||
final DateTime? pickedDate = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: _selectedDateTime ?? DateTime.now(),
|
||||
firstDate: DateTime(2000),
|
||||
lastDate: DateTime(2101),
|
||||
);
|
||||
_selectedDateTime = pickedDate;
|
||||
if (_selectedDateTime != null) {
|
||||
dateController.text = _selectedDateTime!.toIso8601String().substring(
|
||||
0,
|
||||
10,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Methode zum Abrufen des Standorts.
|
||||
Future<void> fetchCurrentLocation() async {
|
||||
isLoading.value = true;
|
||||
errorMessage.value = null;
|
||||
try {
|
||||
final Position position = await _locationRepository.getCurrentPosition();
|
||||
currentPosition.value = position;
|
||||
final double latitude = position.latitude;
|
||||
final double longitude = position.longitude;
|
||||
// Hier kannst du die Logik hinzufügen, um den Standort zu verwenden, z.B.
|
||||
// den Standort in der UI anzuzeigen oder an einen Server zu senden.
|
||||
var map = {'lat': latitude, 'lng': longitude};
|
||||
//save to local Storage lat and long
|
||||
_dataBox.write('lastLatitude', latitude);
|
||||
_dataBox.write('lastLongitude', longitude);
|
||||
var loc = await _locationRepository.getNearbyLocation(map);
|
||||
ortController.text = loc;
|
||||
// Print Standortinformationen in der Konsole
|
||||
print('Nearby Location: $loc');
|
||||
print('Current Position: Latitude: $latitude, Longitude: $longitude');
|
||||
} catch (e) {
|
||||
// Hier fängst du die Fehler aus dem Repository auf
|
||||
errorMessage.value = e.toString();
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void clearTextEditingController() {
|
||||
formKeyTank.currentState!.reset();
|
||||
// Den Fokus wieder auf das erste Feld legen
|
||||
FocusScope.of(Get.context!).requestFocus(firstFocusNode);
|
||||
// TextEditingController zurücksetzen
|
||||
_selectedDateTime = null; // Datum zurücksetzen
|
||||
dateController.clear();
|
||||
kilometerStandEdittingController.clear();
|
||||
mengeController.clear();
|
||||
pricePerLiterController.clear();
|
||||
ortController.clear();
|
||||
}
|
||||
|
||||
String? validateKilometerStand(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Bitte Kilometerstand eingeben';
|
||||
}
|
||||
if (!GetUtils.isNum(value)) {
|
||||
return 'Bitte geben Sie eine gültige Zahl ein';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? validateMenge(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Bitte Menge eingeben';
|
||||
}
|
||||
if (!GetUtils.isNum(value)) {
|
||||
return 'Bitte geben Sie eine gültige Zahl ein';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? validatePricePerLiter(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Bitte Preis pro Liter eingeben';
|
||||
}
|
||||
if (!GetUtils.isNum(value)) {
|
||||
return 'Bitte geben Sie eine gültige Zahl ein';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? validateOrt(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Bitte Ort eingeben...';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//Go to Login Page
|
||||
void logoutSessionAndGoToLoginPage() async {
|
||||
// Handle logout logic here
|
||||
print('Logout session and go to login page');
|
||||
// Clear GetStorage session ID
|
||||
StaticHelper.removeFromStorage();
|
||||
print('Session ID removed from GetStorage');
|
||||
await _authRepository.logout();
|
||||
Get.offAndToNamed(LoginPage.namedRoute);
|
||||
StaticHelper.getMySnackeBar(
|
||||
'Logout', 'Sie wurden abgemeldet!', Colors.blue);
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
handleInputOrUpdate();
|
||||
}
|
||||
|
||||
void handleInputOrUpdate() {
|
||||
var mapFromArguments = Get.arguments as Map<String, dynamic>?;
|
||||
if (mapFromArguments != null && mapFromArguments['from'] != null) {
|
||||
isEditMode.value = mapFromArguments['from'] == 'Input' ? true : false;
|
||||
}
|
||||
//bei true ein insert bei false ein update
|
||||
if (isEditMode.value) {
|
||||
fetchCurrentLocation();
|
||||
} else {
|
||||
// Im Editiermodus, keine Standortabfrage durchführen
|
||||
print('Im Editiermodus, keine Standortabfrage durchführen');
|
||||
// Den Fokus auf das erste Eingabefeld setzen
|
||||
FocusScope.of(Get.context!).requestFocus(firstFocusNode);
|
||||
print('TankController is in Edit Mode');
|
||||
if (mapFromArguments != null && mapFromArguments['data'] != null) {
|
||||
var dataMap = mapFromArguments['data'] as Map<String, dynamic>;
|
||||
print('Data Map for Edit Mode: $dataMap');
|
||||
// Setze die documentId für spätere Updates
|
||||
documentId = dataMap['\$id'] ?? '';
|
||||
// Setze die Werte in die TextEditingController
|
||||
dateController.text = dataMap['date'] ?? '';
|
||||
kilometerStandEdittingController.text = dataMap['odometer'] ?? '';
|
||||
mengeController.text = dataMap['liters'] ?? '';
|
||||
pricePerLiterController.text = dataMap['pricePerLiter'] ?? '';
|
||||
ortController.text = dataMap['location'] ?? '';
|
||||
// Berechne den Gesamtpreis
|
||||
updateSumPrice();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onReady() async {
|
||||
super.onReady();
|
||||
if (_dataBox.hasData('sessionId')) {
|
||||
rxSessionIdString(_dataBox.read('sessionId').toString());
|
||||
}
|
||||
await getCurrentLoggedinUser();
|
||||
FocusScope.of(Get.context!).requestFocus(firstFocusNode);
|
||||
print('TankController is ready');
|
||||
}
|
||||
|
||||
Future<void> getCurrentLoggedinUser() async {
|
||||
await _authRepository.getCurrentUser.then((models.User user) {
|
||||
// Hier kannst du den Benutzernamen und das Avatar-Zeichen setzen
|
||||
userNameToDisplay.value = user.name;
|
||||
circleAvatarUserChar.value = user.name.substring(0, 1).toUpperCase();
|
||||
}).catchError((error) {
|
||||
print('Fehler beim Abrufen des Benutzers: $error');
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
dateController.dispose();
|
||||
kilometerStandEdittingController.dispose();
|
||||
mengeController.dispose();
|
||||
pricePerLiterController.dispose();
|
||||
ortController.dispose();
|
||||
firstFocusNode.dispose(); // Dispose den FocusNode
|
||||
}
|
||||
|
||||
void updateSumPrice() {
|
||||
final double menge = double.tryParse(mengeController.text) ?? 0.0;
|
||||
final double pricePerLiter =
|
||||
double.tryParse(pricePerLiterController.text) ?? 0.0;
|
||||
final double sumPrice = menge * pricePerLiter;
|
||||
rxSummePreisString.value = sumPrice.toStringAsFixed(
|
||||
2,
|
||||
); // Formatieren auf 2 Dezimalstellen
|
||||
}
|
||||
|
||||
void saveTankstopp() async {
|
||||
bool isErrorBySaving = false;
|
||||
String messageToUser = '';
|
||||
isFormValid = formKeyTank.currentState!.validate();
|
||||
if (!isFormValid) {
|
||||
print('Formular ist ungültig');
|
||||
messageToUser = 'Bitte überprüfen Sie Ihre Eingaben.';
|
||||
Get.snackbar(
|
||||
'Fehler',
|
||||
messageToUser,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
duration: const Duration(seconds: 2),
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
print('Formular ist gültig');
|
||||
formKeyTank.currentState!.save();
|
||||
try {
|
||||
//false update a tank stop
|
||||
if (isEditMode.value == false) {
|
||||
await _authRepository.updateTankStop(documentId, {
|
||||
'userId': _dataBox.read('userId'),
|
||||
'date': f.format(DateTime.parse(dateController.text)),
|
||||
'odometer': kilometerStandEdittingController.text,
|
||||
'liters': mengeController.text,
|
||||
'pricePerLiter': pricePerLiterController.text,
|
||||
'location': ortController.text,
|
||||
}).then((models.Document document) {
|
||||
print('Tankstopp erfolgreich aktualisiert: ${document.data}');
|
||||
messageToUser = 'Tankstopp erfolgreich aktualisiert!';
|
||||
isErrorBySaving = false;
|
||||
})
|
||||
// Handle specific Appwrite exceptions
|
||||
.catchError((error) {
|
||||
isErrorBySaving = true;
|
||||
if (error is AppwriteException) {
|
||||
// Handle specific Appwrite exceptions
|
||||
messageToUser = 'Appwrite Fehler: ${error.message}';
|
||||
print('Appwrite Fehler: ${error.message}');
|
||||
} else {
|
||||
// Handle other types of errors
|
||||
messageToUser = 'Allgemeiner Fehler: $error';
|
||||
print('Allgemeiner Fehler: $error');
|
||||
}
|
||||
print('Fehler bei der speicherung: $error');
|
||||
});
|
||||
} else {
|
||||
//true creating a tank stop
|
||||
await _authRepository.createTankStop({
|
||||
'userId': _dataBox.read('userId'),
|
||||
'date': f.format(DateTime.parse(dateController.text)),
|
||||
'odometer': kilometerStandEdittingController.text,
|
||||
'liters': mengeController.text,
|
||||
'pricePerLiter': pricePerLiterController.text,
|
||||
'location': ortController.text,
|
||||
}).then((models.Document document) {
|
||||
print('Tankstopp erfolgreich gespeichert: ${document.data}');
|
||||
messageToUser = 'Tankstopp erfolgreich gespeichert!';
|
||||
isErrorBySaving = false;
|
||||
})
|
||||
// Handle specific Appwrite exceptions
|
||||
.catchError((error) {
|
||||
isErrorBySaving = true;
|
||||
if (error is AppwriteException) {
|
||||
// Handle specific Appwrite exceptions
|
||||
messageToUser = 'Appwrite Fehler: ${error.message}';
|
||||
print('Appwrite Fehler: ${error.message}');
|
||||
} else {
|
||||
// Handle other types of errors
|
||||
messageToUser = 'Allgemeiner Fehler: $error';
|
||||
print('Allgemeiner Fehler: $error');
|
||||
}
|
||||
print('Fehler bei der speicherung: $error');
|
||||
});
|
||||
// bei true ein insert
|
||||
}
|
||||
}
|
||||
// Handle any other exceptions that might occur
|
||||
catch (e) {
|
||||
isErrorBySaving = true;
|
||||
messageToUser = 'Fehler beim Speichern des Tankstopps: $e';
|
||||
print('Fehler beim speichern: $e');
|
||||
}
|
||||
if (!isErrorBySaving) {
|
||||
clearTextEditingController();
|
||||
}
|
||||
// Handle button press logic here
|
||||
String title = isErrorBySaving ? 'Fehler' : 'Erfolg';
|
||||
Get.snackbar(
|
||||
title,
|
||||
messageToUser,
|
||||
backgroundColor: isErrorBySaving ? Colors.red : Colors.green,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
duration: const Duration(seconds: 4),
|
||||
);
|
||||
goToTankStopsView();
|
||||
}
|
||||
}
|
||||
|
||||
goToTankStopsView() async {
|
||||
clearTextEditingController();
|
||||
await Get.offAndToNamed(TanklistPage.namedRoute);
|
||||
}
|
||||
}
|
||||
304
lib/pages/tank/tank_view.dart
Normal file
@ -0,0 +1,304 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import './tank_controller.dart';
|
||||
|
||||
class TankPage extends GetView<TankController> {
|
||||
static const namedRoute = '/tank-stop-page';
|
||||
const TankPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var tankCtrl = controller;
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
'Tankstopp erfassen',
|
||||
style: TextStyle(color: Colors.grey.shade300),
|
||||
),
|
||||
backgroundColor: Colors.transparent, // Mach die AppBar transparent
|
||||
elevation: 0, // Entferne den Schatten unter der AppBar
|
||||
centerTitle: true,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.list, color: Colors.grey.shade300),
|
||||
onPressed: () async {
|
||||
// Handle logout logic here
|
||||
controller.goToTankStopsView();
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.logout, color: Colors.grey.shade300),
|
||||
onPressed: () async {
|
||||
// Handle logout logic here
|
||||
controller.logoutSessionAndGoToLoginPage();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
extendBodyBehindAppBar: true, // Erweitere den Body hinter die AppBar
|
||||
body: Stack(
|
||||
children: [
|
||||
Container(
|
||||
height: double.infinity,
|
||||
width: double.infinity,
|
||||
decoration: const BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage('lib/images/backgroundPitstopBlack.png'),
|
||||
fit: BoxFit
|
||||
.cover, // Skaliert das Bild, um den Container zu füllen
|
||||
alignment: Alignment
|
||||
.bottomCenter, // Richte das Bild am unteren Rand aus
|
||||
),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => PopScope(
|
||||
canPop: false,
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.only(
|
||||
top: MediaQuery.of(context).padding.top +
|
||||
kToolbarHeight +
|
||||
20,
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
backgroundColor: Colors.blue,
|
||||
radius: 40,
|
||||
child: Text(
|
||||
controller.circleAvatarUserChar.value,
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
controller.userNameToDisplay.value,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
backgroundColor: Colors.black87,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 3,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
inputFields(),
|
||||
const SizedBox(height: 20),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Colors.black.withValues(alpha: 0.9),
|
||||
),
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
text: 'Summe: ',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: Colors.grey.shade300,
|
||||
),
|
||||
children: <TextSpan>[
|
||||
TextSpan(
|
||||
// Hier kommt der Wert als separater TextSpan
|
||||
text: controller.rxSummePreisString.value,
|
||||
// Doppelt so groß wie der Standardtext',
|
||||
style: TextStyle(
|
||||
fontSize: 30, // Doppelt so groß wie 18
|
||||
color: Colors.blue, // Blaue Farbe
|
||||
fontWeight: FontWeight
|
||||
.bold, // Optional: Fetter Text, um ihn hervorzuheben
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
SizedBox(
|
||||
width: 350,
|
||||
height: 50,
|
||||
child: Obx(
|
||||
() => ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue.withValues(
|
||||
alpha: 0.9,
|
||||
), // Button-Farbe
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
10,
|
||||
), // Abgerundete Ecken
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
controller.saveTankstopp();
|
||||
},
|
||||
child: tankCtrl.isEditMode.value == true ? Text(
|
||||
'Tankstop erfassen',
|
||||
style:
|
||||
TextStyle(color: Colors.white, fontSize: 20),
|
||||
): Text(
|
||||
'Tankstop updaten',
|
||||
style: TextStyle(
|
||||
color: Colors.white, fontSize: 20),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 20, bottom: 50),
|
||||
child: Text(
|
||||
'Hinweis: Alle Felder sind Pflichtfelder.',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.red.shade300,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget inputFields() {
|
||||
return SizedBox(
|
||||
width: 320,
|
||||
child: Form(
|
||||
key: controller.formKeyTank,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
child: Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
validator: (value) => controller.validateOrt(value),
|
||||
focusNode: controller.firstFocusNode, // Setze den FocusNode
|
||||
//onTap: () => controller.selectDateTime(Get.context!),
|
||||
controller: controller.ortController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Ort der Tankstelle',
|
||||
labelStyle: const TextStyle(color: Colors.white, fontSize: 20),
|
||||
errorStyle: const TextStyle(color: Colors.red, fontSize: 16),
|
||||
filled: true,
|
||||
fillColor: Colors.black.withValues(alpha: 0.9),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: const BorderSide(color: Colors.white, width: 2),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: BorderSide.none, // Entferne den Rand
|
||||
),
|
||||
),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
TextFormField(
|
||||
//focusNode: controller.firstFocusNode, // Setze den FocusNode
|
||||
readOnly: true,
|
||||
onTap: () => controller.selectDateTime(Get.context!),
|
||||
keyboardType: TextInputType.text,
|
||||
controller: controller.dateController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Datum',
|
||||
labelStyle: const TextStyle(color: Colors.white, fontSize: 20),
|
||||
errorStyle: const TextStyle(color: Colors.red, fontSize: 16),
|
||||
filled: true,
|
||||
fillColor: Colors.black.withValues(alpha: 0.9),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: const BorderSide(color: Colors.white, width: 2),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: BorderSide.none, // Entferne den Rand
|
||||
),
|
||||
),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
TextFormField(
|
||||
validator: (value) => controller.validateKilometerStand(value),
|
||||
keyboardType: TextInputType.number,
|
||||
controller: controller.kilometerStandEdittingController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Kilometerstand',
|
||||
labelStyle: const TextStyle(color: Colors.white, fontSize: 20),
|
||||
errorStyle: const TextStyle(color: Colors.red, fontSize: 16),
|
||||
filled: true,
|
||||
fillColor: Colors.black.withValues(alpha: 0.9),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: const BorderSide(color: Colors.white, width: 2),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: BorderSide.none, // Entferne den Rand
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
TextFormField(
|
||||
validator: (value) => controller.validateMenge(value),
|
||||
keyboardType: TextInputType.number,
|
||||
controller: controller.mengeController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Menge (in Litern)',
|
||||
labelStyle: const TextStyle(color: Colors.white, fontSize: 20),
|
||||
errorStyle: const TextStyle(color: Colors.red, fontSize: 16),
|
||||
filled: true,
|
||||
fillColor: Colors.black.withValues(alpha: 0.9),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: const BorderSide(color: Colors.white, width: 2),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: BorderSide.none, // Entferne den Rand
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
TextFormField(
|
||||
onChanged: (value) {
|
||||
// Update the sum price when the price per liter changes
|
||||
controller.updateSumPrice();
|
||||
},
|
||||
validator: (value) => controller.validatePricePerLiter(value),
|
||||
keyboardType: TextInputType.number,
|
||||
controller: controller.pricePerLiterController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Preis pro Liter',
|
||||
labelStyle: const TextStyle(color: Colors.white, fontSize: 20),
|
||||
errorStyle: const TextStyle(color: Colors.red, fontSize: 16),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: const BorderSide(color: Colors.white, width: 2),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.black.withValues(alpha: 0.9),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
//borderSide: BorderSide.none, // Entferne den Rand
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
179
lib/pages/tanklist/tanklist_controller.dart
Normal file
@ -0,0 +1,179 @@
|
||||
import 'package:appwrite/appwrite.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
|
||||
import '../login/login_view.dart';
|
||||
import '../graph/graph_view.dart';
|
||||
import '../../data/models/tank_model.dart';
|
||||
import '../../data/repository/appwrite_repository.dart';
|
||||
import '../../pages/tank/tank_view.dart';
|
||||
import '../../pages/gaslist/gaslist_view.dart';
|
||||
import '../print/print_view.dart';
|
||||
|
||||
class TanklistController extends GetxController {
|
||||
final _dataBox = GetStorage('MyUserStorage');
|
||||
final isloadingList = false.obs;
|
||||
final rxTankListAlles = <AppWriteTankModel>[].obs;
|
||||
final rxTankListActualYear = <AppWriteTankModel>[].obs;
|
||||
final szRxUserId = 'NoUser'.obs;
|
||||
//AppWrite API-REST get Data
|
||||
final AppwriteRepository _authRepository = AppwriteRepository();
|
||||
final szRxYear = DateTime.now().year.obs.toString().obs;
|
||||
final szRxSummeYearLiter = '0.0'.obs;
|
||||
final szRxSummePrice = '0.0'.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
szRxUserId(_dataBox.read('userId'));
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
getTankList();
|
||||
super.onReady();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {}
|
||||
|
||||
Future<void> getTankList() async {
|
||||
isloadingList(true);
|
||||
bool isErrorByLoading = false;
|
||||
String message = '';
|
||||
try {
|
||||
await _authRepository
|
||||
.listTankStops(szRxUserId.value)
|
||||
.then((tankListData) {
|
||||
if (tankListData.documents.isEmpty) {
|
||||
isErrorByLoading = false;
|
||||
message = 'Leere Liste keine Daten vorhanden';
|
||||
isloadingList(false);
|
||||
rxTankListAlles.clear();
|
||||
rxTankListActualYear.clear();
|
||||
szRxSummeYearLiter('0.0');
|
||||
szRxSummePrice('0.0');
|
||||
update();
|
||||
return;
|
||||
}
|
||||
rxTankListAlles.clear();
|
||||
var data = tankListData.toMap();
|
||||
List d = data['documents'].toList();
|
||||
rxTankListAlles.value =
|
||||
d.map((e) => AppWriteTankModel.fromMap(e['data'])).toList();
|
||||
rxTankListAlles.sort((a, b) {
|
||||
final DateTime dateA = DateTime.parse(a.date);
|
||||
final DateTime dateB = DateTime.parse(b.date);
|
||||
return dateB.compareTo(dateA);
|
||||
});
|
||||
//Tank List per Actual year**********
|
||||
rxTankListActualYear.clear();
|
||||
rxTankListActualYear.value = rxTankListAlles
|
||||
.where((tank) => tank.date.startsWith(szRxYear.value))
|
||||
.toList();
|
||||
// rxTankListActualYear.value = rxTankListAlles;
|
||||
//Summe Liter aktuelles Jahr*********
|
||||
szRxSummeYearLiter(
|
||||
rxTankListActualYear.fold<double>(0.0, (previousValue, element) {
|
||||
return previousValue + (double.tryParse(element.liters) ?? 0.0);
|
||||
}).toStringAsFixed(2));
|
||||
//Summe Preis aktuelles Jahr*********
|
||||
szRxSummePrice(
|
||||
rxTankListActualYear.fold<double>(0.0, (previousValue, element) {
|
||||
return previousValue +
|
||||
(double.tryParse(element.szSummePreis!) ?? 0.0);
|
||||
}).toStringAsFixed(2));
|
||||
message = 'Liste wurde erfolgreich geladen';
|
||||
isErrorByLoading = false;
|
||||
}).catchError((error) {
|
||||
isErrorByLoading = true;
|
||||
if (error is AppwriteException) {
|
||||
message = error.message!;
|
||||
} else {
|
||||
message = 'Uuups da ist was schief gelaufen';
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
isErrorByLoading = true;
|
||||
message = 'Fehler beim Laden der Tankliste';
|
||||
print('Error fetching tank list: $e');
|
||||
} finally {
|
||||
if (!isErrorByLoading) {
|
||||
isloadingList(false);
|
||||
}
|
||||
}
|
||||
String title = isErrorByLoading ? 'Fehler' : 'Erfolg';
|
||||
print('$title: $message');
|
||||
update();
|
||||
}
|
||||
|
||||
void goToUpdatePage(AppWriteTankModel item) {
|
||||
var map = item.toMap();
|
||||
print('Go to Update Page with data: $map');
|
||||
// Go to Update Page
|
||||
Get.offAndToNamed(TankPage.namedRoute,
|
||||
arguments: {'from': 'Update', 'data': map});
|
||||
}
|
||||
|
||||
void goToInputPage() {
|
||||
Get.offAndToNamed(TankPage.namedRoute, arguments: {'from': 'Input'});
|
||||
}
|
||||
|
||||
void logoutSessionAndGoToLoginPage() async {
|
||||
// Handle logout logic here
|
||||
print('Logout session and go to login page');
|
||||
// Clear GetStorage session ID
|
||||
_dataBox.remove('sessionId');
|
||||
_dataBox.remove('userId');
|
||||
_dataBox.remove('userName');
|
||||
_dataBox.remove('userEmail');
|
||||
print('Session ID removed from GetStorage');
|
||||
await _authRepository.logout();
|
||||
Get.offAndToNamed(LoginPage.namedRoute);
|
||||
}
|
||||
|
||||
void goToChartView() {
|
||||
Get.offAndToNamed(GraphPage.namedRoute);
|
||||
}
|
||||
|
||||
void deleteTankStop(String documentId) async {
|
||||
bool isErrorByLoading = false;
|
||||
String message = '';
|
||||
try {
|
||||
await _authRepository.deleteTankStop(documentId).then((response) async {
|
||||
await getTankList();
|
||||
message = 'Tankstopp wurde erfolgreich gelöscht';
|
||||
}).catchError((error) {
|
||||
isErrorByLoading = true;
|
||||
if (error is AppwriteException) {
|
||||
message = error.message!;
|
||||
} else {
|
||||
message = 'Uuups da ist was schief gelaufen';
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
isErrorByLoading = true;
|
||||
message = 'Fehler beim Löschen des Tankstopps';
|
||||
print('Error deleting tank stop: $e');
|
||||
}
|
||||
String title = isErrorByLoading ? 'Fehler' : 'Erfolg';
|
||||
print('$title: $message');
|
||||
}
|
||||
|
||||
Future<void> goToGasView() async {
|
||||
//Go to Gas Station List Page
|
||||
await Get.offAndToNamed(GaslistPage.namedRoute);
|
||||
|
||||
}
|
||||
|
||||
void goToPrintView() {
|
||||
|
||||
List<Map<String, dynamic>> tankMap = rxTankListActualYear.map((e) => e.toMap()).toList();
|
||||
Get.offAndToNamed(PrintPage.namedRoute, arguments: {
|
||||
'tankList': tankMap,
|
||||
'year': szRxYear.value,
|
||||
'summeLiter': szRxSummeYearLiter.value,
|
||||
'summePreis': szRxSummePrice.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
169
lib/pages/tanklist/tanklist_view.dart
Normal file
@ -0,0 +1,169 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import './tanklist_controller.dart';
|
||||
import './widgets/my_list_tile_item.dart';
|
||||
|
||||
class TanklistPage extends GetView<TanklistController> {
|
||||
static const namedRoute = '/tank-stop-liste-page';
|
||||
const TanklistPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var tankListCtrl = controller;
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
shadowColor: Colors.grey,
|
||||
title: Text('Tankstops'),
|
||||
centerTitle: true,
|
||||
//backgroundColor: Colors.grey.shade600,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.add_chart, color: Colors.grey.shade300),
|
||||
onPressed: () async {
|
||||
// Handle go to Chart View
|
||||
tankListCtrl.goToChartView();
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.local_gas_station_sharp, color: Colors.grey.shade300),
|
||||
onPressed: () async {
|
||||
// Handle go to Chart View
|
||||
tankListCtrl.goToGasView();
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.print,
|
||||
color: Colors.grey.shade300),
|
||||
onPressed: () async {
|
||||
// Handle go to Chart View
|
||||
tankListCtrl.goToPrintView();
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.logout, color: Colors.grey.shade300),
|
||||
onPressed: () async {
|
||||
// Handle logout logic here
|
||||
tankListCtrl.logoutSessionAndGoToLoginPage();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: Column(
|
||||
mainAxisAlignment: MainAxisAlignment
|
||||
.end, // Positioniere die Buttons am unteren Ende
|
||||
crossAxisAlignment: CrossAxisAlignment.end, // Richte sie rechts aus
|
||||
mainAxisSize: MainAxisSize.min, // Nimm nur den benötigten Platz ein
|
||||
children: [
|
||||
FloatingActionButton(
|
||||
onPressed: () => tankListCtrl.goToInputPage(),
|
||||
backgroundColor: Colors.blue,
|
||||
child: Icon(Icons.add),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Obx(() => tankListCtrl.isloadingList.value == false
|
||||
? Padding(
|
||||
padding: EdgeInsetsGeometry.only(left: 25, right: 25),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(tankListCtrl.szRxYear.value,
|
||||
style: TextStyle(
|
||||
fontSize: 25, color: Colors.orange)),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Text('Jahresverbrauch',
|
||||
style: TextStyle(fontSize: 14)),
|
||||
Text(tankListCtrl.szRxSummeYearLiter.value,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
color: Colors.orange)),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
Text('Jahressumme',
|
||||
style: TextStyle(fontSize: 14)),
|
||||
Text(tankListCtrl.szRxSummePrice.value,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
color: Colors.orange)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
color: Colors.grey.shade200,
|
||||
height: 0.0,
|
||||
),
|
||||
Expanded(
|
||||
child: tankListCtrl.rxTankListActualYear.isNotEmpty ? ListView.builder(
|
||||
padding: EdgeInsets.only(top: 8, bottom: 8),
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemBuilder: ((BuildContext context, int index) {
|
||||
var item = tankListCtrl.rxTankListActualYear[index];
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.blue.withValues(
|
||||
alpha: 0.35,
|
||||
), // Die Farbe des Schattens
|
||||
spreadRadius:
|
||||
1, // Wie weit sich der Schatten ausbreitet
|
||||
blurRadius:
|
||||
1, // Wie stark der Schatten verschwommen ist
|
||||
offset: const Offset(
|
||||
0,
|
||||
3,
|
||||
), // Der Versatz des Schattens (x, y)
|
||||
),
|
||||
],
|
||||
),
|
||||
child: MyListTileItem(
|
||||
listItem: item,
|
||||
onPressedEdit: () {
|
||||
tankListCtrl.goToUpdatePage(item);
|
||||
},
|
||||
onPressedDelete: () {
|
||||
tankListCtrl.deleteTankStop(item.documentId);
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(height: 15),
|
||||
],
|
||||
);
|
||||
}),
|
||||
itemCount: tankListCtrl.rxTankListActualYear.length,
|
||||
): Center(
|
||||
child: Text('Keine Einträge für das aktuelle Jahr vorhanden',
|
||||
style: TextStyle(
|
||||
fontSize: 18, color: Colors.grey.shade600)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.blue,
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
131
lib/pages/tanklist/widgets/my_list_tile_item.dart
Normal file
@ -0,0 +1,131 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../data/models/tank_model.dart';
|
||||
|
||||
class MyListTileItem extends StatelessWidget {
|
||||
const MyListTileItem(
|
||||
{super.key, required this.listItem, required this.onPressedEdit, required this.onPressedDelete});
|
||||
|
||||
final AppWriteTankModel listItem;
|
||||
final void Function()? onPressedEdit;
|
||||
final void Function()? onPressedDelete;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _myListItem(context);
|
||||
}
|
||||
|
||||
Widget _myListItem(BuildContext context) {
|
||||
var textColor = Colors.orange.shade400;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Wrap(
|
||||
alignment: WrapAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.date_range),
|
||||
SizedBox(width: 10),
|
||||
Text(
|
||||
listItem.date,
|
||||
style: TextStyle(color: textColor, fontSize: 25),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Image.asset('lib/icons/kilometer.png'),
|
||||
SizedBox(width: 10),
|
||||
Text(
|
||||
'${listItem.odometer} km',
|
||||
style: TextStyle(color: textColor, fontSize: 25),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Divider(thickness: 3, color: Colors.black),
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.local_gas_station),
|
||||
SizedBox(width: 10),
|
||||
Text(
|
||||
'${listItem.liters} L',
|
||||
style: TextStyle(color: textColor, fontSize: 25),
|
||||
),
|
||||
],
|
||||
),
|
||||
Divider(thickness: 1, color: Colors.black),
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.euro),
|
||||
SizedBox(width: 10),
|
||||
Text(
|
||||
'${listItem.pricePerLiter} €/L ',
|
||||
style: TextStyle(color: textColor, fontSize: 25),
|
||||
),
|
||||
],
|
||||
),
|
||||
Divider(thickness: 1, color: Colors.black),
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.price_change),
|
||||
SizedBox(width: 10),
|
||||
Text('${listItem.szSummePreis} €',
|
||||
style: TextStyle(color: textColor, fontSize: 25)),
|
||||
],
|
||||
),
|
||||
Divider(thickness: 1, color: Colors.black),
|
||||
Wrap(
|
||||
children: [
|
||||
Icon(Icons.pin_drop),
|
||||
SizedBox(width: 10),
|
||||
SizedBox(
|
||||
width: 250,
|
||||
child: Text(
|
||||
listItem.location,
|
||||
style: TextStyle(color: textColor, fontSize: 16),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Divider(thickness: 1, color: Colors.black),
|
||||
Wrap(
|
||||
spacing: 10,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
onPressed: onPressedEdit,
|
||||
label: Text(
|
||||
'Edit',
|
||||
style: TextStyle(color: Colors.grey.shade200),
|
||||
),
|
||||
icon: const Icon(Icons.edit),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color.fromARGB(255, 3, 71, 128),
|
||||
foregroundColor: Colors.grey.shade200,
|
||||
shadowColor: Colors.white,
|
||||
),
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
onPressed: onPressedDelete,
|
||||
label: Text(
|
||||
'Delete',
|
||||
style: TextStyle(color: Colors.grey.shade200),
|
||||
),
|
||||
icon: const Icon(Icons.delete),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color.fromARGB(255, 128, 3, 45),
|
||||
foregroundColor: Colors.grey.shade200,
|
||||
shadowColor: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:appwrite_flutter_starter_kit/utils/extensions/colors.dart';
|
||||
import '../../utils/extensions/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Background color for gradients, blur, etc.
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import 'package:appwrite_flutter_starter_kit/data/models/log.dart';
|
||||
import 'package:appwrite_flutter_starter_kit/data/models/project_info.dart';
|
||||
import 'package:appwrite_flutter_starter_kit/ui/components/responsive_layout.dart';
|
||||
import 'package:appwrite_flutter_starter_kit/ui/components/single_wrap.dart';
|
||||
import 'package:appwrite_flutter_starter_kit/utils/extensions/colors.dart';
|
||||
import '../../data/models/log.dart';
|
||||
import '../../data/models/project_info.dart';
|
||||
import '../../ui/components/responsive_layout.dart';
|
||||
import '../../ui/components/single_wrap.dart';
|
||||
import '../../utils/extensions/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CollapsibleBottomSheet extends StatefulWidget {
|
||||
@ -235,8 +235,8 @@ class ProjectSection extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
Wrap(
|
||||
alignment: WrapAlignment.spaceBetween,
|
||||
children: [
|
||||
ProjectRow(title: "Endpoint", value: projectInfo.endpoint),
|
||||
ProjectRow(title: "Project ID", value: projectInfo.projectId),
|
||||
@ -491,7 +491,7 @@ class LogsTableRow extends StatelessWidget {
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: Text(
|
||||
response.substring(0, 50),
|
||||
response.length >= 50 ? response.substring(0, 50) : response,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import 'package:appwrite_flutter_starter_kit/utils/extensions/build_context.dart';
|
||||
import '../../utils/extensions/build_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// A widget that animates a connection line with a checkmark in the middle.
|
||||
@ -15,7 +15,7 @@ class ConnectionLine extends StatelessWidget {
|
||||
return SizedBox(
|
||||
width: context.widthFactor(
|
||||
mobileFactor: 0.25,
|
||||
largeScreenFactor: 0.15,
|
||||
largeScreenFactor: 0.1,
|
||||
),
|
||||
child: Flex(
|
||||
direction: Axis.horizontal,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import 'package:appwrite_flutter_starter_kit/data/models/status.dart';
|
||||
import '../../data/models/status.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import 'package:appwrite_flutter_starter_kit/utils/extensions/build_context.dart';
|
||||
import '../../ui/components/responsive_layout.dart';
|
||||
import '../../utils/extensions/build_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
@ -8,39 +9,85 @@ class GettingStartedCards extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
GeneralInfoCard(
|
||||
title: "Edit your app",
|
||||
link: null,
|
||||
subtitle: const HighlightedText(),
|
||||
),
|
||||
GeneralInfoCard(
|
||||
title: "Head to Appwrite Cloud",
|
||||
link: "https://cloud.appwrite.io",
|
||||
subtitle: const Text(
|
||||
"Start managing your project from the Appwrite console",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFF56565C),
|
||||
return ResponsiveLayout(
|
||||
smallDeviceLayout: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
GeneralInfoCard(
|
||||
title: "Edit your app",
|
||||
link: null,
|
||||
subtitle: const HighlightedText(),
|
||||
),
|
||||
GeneralInfoCard(
|
||||
title: "Head to Appwrite Cloud",
|
||||
link: "https://cloud.appwrite.io",
|
||||
subtitle: const Text(
|
||||
"Start managing your project from the Appwrite console",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFF56565C),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
GeneralInfoCard(
|
||||
title: "Explore docs",
|
||||
link: "https://appwrite.io/docs",
|
||||
subtitle: const Text(
|
||||
"Discover the full power of Appwrite by diving into our documentation",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFF56565C),
|
||||
GeneralInfoCard(
|
||||
title: "Explore docs",
|
||||
link: "https://appwrite.io/docs",
|
||||
subtitle: const Text(
|
||||
"Discover the full power of Appwrite by diving into our documentation",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFF56565C),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
largeDeviceLayout: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: context.isExtraWideScreen ? 64 : 16.0, vertical: 16.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 16,
|
||||
children: [
|
||||
Flexible(
|
||||
child: GeneralInfoCard(
|
||||
title: "Edit your app",
|
||||
link: null,
|
||||
subtitle: const HighlightedText(),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: GeneralInfoCard(
|
||||
title: "Head to Appwrite Cloud",
|
||||
link: "https://cloud.appwrite.io",
|
||||
subtitle: const Text(
|
||||
"Start managing your project from the Appwrite console",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFF56565C),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: GeneralInfoCard(
|
||||
title: "Explore docs",
|
||||
link: "https://appwrite.io/docs",
|
||||
subtitle: const Text(
|
||||
"Discover the full power of Appwrite by diving into our documentation",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFF56565C),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -68,9 +115,12 @@ class GeneralInfoCard extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(builder: (context, constraints) {
|
||||
final double cardWidth = context.isExtraWideScreen
|
||||
? constraints.maxWidth.clamp(0, 350)
|
||||
: constraints.maxWidth;
|
||||
|
||||
return SizedBox(
|
||||
// `1` because we already have padding on sides.
|
||||
width: constraints.maxWidth * (context.isExtraWideScreen ? 0.55 : 1),
|
||||
width: cardWidth,
|
||||
child: Card(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'package:appwrite_flutter_starter_kit/data/models/status.dart';
|
||||
import 'package:appwrite_flutter_starter_kit/ui/icons/appwrite.dart';
|
||||
import '../../data/models/status.dart';
|
||||
import '../../ui/icons/appwrite.dart';
|
||||
import '../../utils/extensions/build_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'connection_line.dart';
|
||||
@ -21,9 +22,15 @@ class TopPlatformView extends StatelessWidget {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
PlatformIcon(child: FlutterLogo(size: 40)),
|
||||
PlatformIcon(
|
||||
size: context.isExtraWideScreen ? 142 : 100,
|
||||
child: FlutterLogo(size: context.isExtraWideScreen ? 56 : 40),
|
||||
),
|
||||
ConnectionLine(show: status == Status.success),
|
||||
PlatformIcon(child: AppwriteIcon(size: 40)),
|
||||
PlatformIcon(
|
||||
size: context.isExtraWideScreen ? 142 : 100,
|
||||
child: AppwriteIcon(size: context.isExtraWideScreen ? 56 : 40),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -51,7 +58,7 @@ class PlatformIcon extends StatelessWidget {
|
||||
height: size,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFAFAFD),
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
borderRadius: BorderRadius.circular(context.isExtraWideScreen ? size * 0.2 : 24),
|
||||
border: Border.all(color: const Color(0x0A19191C), width: 1),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
@ -65,9 +72,10 @@ class PlatformIcon extends StatelessWidget {
|
||||
child: Container(
|
||||
width: size * 0.86,
|
||||
height: size * 0.86,
|
||||
margin: context.isExtraWideScreen ? EdgeInsets.all(8) : null,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderRadius: BorderRadius.circular(context.isExtraWideScreen ? size * 0.2: 16),
|
||||
border: Border.all(color: const Color(0xFFFAFAFB), width: 1),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
|
||||
@ -3,7 +3,11 @@ import 'dart:io';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
|
||||
import './extensions/http_overrides.dart';
|
||||
|
||||
/// A utility class for initializing the Flutter application.
|
||||
///
|
||||
@ -19,6 +23,11 @@ class AppInitializer {
|
||||
_ensureInitialized();
|
||||
await _setupWindowDimensions();
|
||||
await _setupDeviceOrientation();
|
||||
await _setupDateFormating();
|
||||
//dotENV initial
|
||||
await dotenv.load(fileName: '.env');
|
||||
//Overrides initial
|
||||
HttpOverrides.global = MyHttpOverrides();
|
||||
}
|
||||
|
||||
/// Ensures that Flutter bindings are initialized.
|
||||
@ -51,4 +60,8 @@ class AppInitializer {
|
||||
overlays: [SystemUiOverlay.bottom, SystemUiOverlay.top],
|
||||
);
|
||||
}
|
||||
// Initialisiert die Lokalisierungsdaten für eine bestimmte Sprache
|
||||
static Future<void> _setupDateFormating() async {
|
||||
await initializeDateFormatting('de_DE', null);
|
||||
}
|
||||
}
|
||||
|
||||
29
lib/utils/extensions/constants.dart
Normal file
@ -0,0 +1,29 @@
|
||||
|
||||
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
var kTextStyle = TextStyle(fontSize: 20, color: Colors.grey.shade100);
|
||||
var kTextStyleSub = TextStyle(fontSize: 16, color: Colors.grey.shade400);
|
||||
var kColorEuroChart = Colors.red.shade500;
|
||||
var kColorBenzinChart = Colors.yellow.shade500;
|
||||
var kColorPerLiterChart = Colors.blueGrey.shade300;
|
||||
var kChartDescriptionFontStyle = TextStyle(
|
||||
color: Colors.grey.shade900,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
);
|
||||
|
||||
var kBarTitleStyle = TextStyle(
|
||||
color: Colors.blue.shade900,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
);
|
||||
|
||||
var kInputDecorationDropDownMenueYear = const InputDecoration(
|
||||
prefixIcon: Icon(Icons.date_range),
|
||||
hintText: 'Jahresauswahl',
|
||||
filled: true,
|
||||
fillColor: Colors.black,
|
||||
errorStyle: TextStyle(color: Colors.yellow),
|
||||
);
|
||||
9
lib/utils/extensions/http_overrides.dart
Normal file
@ -0,0 +1,9 @@
|
||||
import 'dart:io';
|
||||
|
||||
class MyHttpOverrides extends HttpOverrides{
|
||||
@override
|
||||
HttpClient createHttpClient(SecurityContext? context){
|
||||
return super.createHttpClient(context)
|
||||
..badCertificateCallback = (X509Certificate cert, String host, int port)=> true;
|
||||
}
|
||||
}
|
||||
21
lib/utils/extensions/sample_bindings.dart
Normal file
@ -0,0 +1,21 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../pages/gaslist/gaslist_controller.dart';
|
||||
import '../../pages/graph/graph_controller.dart';
|
||||
import '../../pages/login/login_controller.dart';
|
||||
import '../../pages/tank/tank_controller.dart';
|
||||
import '../../pages/tanklist/tanklist_controller.dart';
|
||||
import '../../pages/print/print_controller.dart';
|
||||
|
||||
class SampleBindings extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
// Define your dependencies here
|
||||
Get.lazyPut<LoginController>(() => LoginController());
|
||||
Get.lazyPut<TankController>(() => TankController());
|
||||
Get.lazyPut<TanklistController>(() => TanklistController());
|
||||
Get.lazyPut<GraphController>(() => GraphController());
|
||||
Get.lazyPut<GaslistController>(() => GaslistController());
|
||||
Get.lazyPut<PrintController>(() => PrintController());
|
||||
}
|
||||
}
|
||||
53
lib/utils/extensions/sample_routes.dart
Normal file
@ -0,0 +1,53 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../pages/graph/graph_view.dart';
|
||||
import '../../pages/login/login_view.dart';
|
||||
import '../../pages/print/print_view.dart';
|
||||
import '../../pages/tank/tank_view.dart';
|
||||
import '../../pages/tanklist/tanklist_view.dart';
|
||||
import '../../pages/gaslist/gaslist_view.dart';
|
||||
import './sample_bindings.dart';
|
||||
|
||||
|
||||
|
||||
class SampleRouts {
|
||||
static final sampleBindings = SampleBindings();
|
||||
static List<GetPage<dynamic>> samplePages = [
|
||||
GetPage(
|
||||
name: LoginPage.namedRoute,
|
||||
page: () => const LoginPage(),
|
||||
binding: sampleBindings,
|
||||
),
|
||||
GetPage(
|
||||
name: TankPage.namedRoute,
|
||||
page: () => const TankPage(),
|
||||
binding: sampleBindings,
|
||||
),
|
||||
GetPage(
|
||||
name: TanklistPage.namedRoute,
|
||||
page: () => const TanklistPage(),
|
||||
binding: sampleBindings,
|
||||
),
|
||||
GetPage(
|
||||
name: GraphPage.namedRoute,
|
||||
page: () => const GraphPage(),
|
||||
binding: sampleBindings,
|
||||
),
|
||||
GetPage(
|
||||
name: GaslistPage.namedRoute,
|
||||
page: () => const GaslistPage(),
|
||||
binding: sampleBindings,
|
||||
),
|
||||
GetPage(
|
||||
name: PrintPage.namedRoute,
|
||||
page: () => const PrintPage(),
|
||||
binding: sampleBindings,
|
||||
),
|
||||
// GetPage(
|
||||
// name: MapPage.namedRoute,
|
||||
// page: () => const MapPage(),
|
||||
// binding: SampleBindings(),
|
||||
// ),
|
||||
];
|
||||
|
||||
}
|
||||
89
lib/utils/extensions/static_helper.dart
Normal file
@ -0,0 +1,89 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
|
||||
import '../../data/models/tank_model.dart';
|
||||
|
||||
class StaticHelper {
|
||||
static double sumMonatEuro = 0.0;
|
||||
static double sumMonatLiter = 0.0;
|
||||
static List staticSearchList = [];
|
||||
|
||||
static GetStorage dataBox = GetStorage('MyUserStorage');
|
||||
|
||||
static List listMonth = [
|
||||
{'month': 'Jänner', 'value': '01'},
|
||||
{'month': 'Februar', 'value': '02'},
|
||||
{'month': 'März', 'value': '03'},
|
||||
{'month': 'April', 'value': '04'},
|
||||
{'month': 'Mai', 'value': '05'},
|
||||
{'month': 'Juni', 'value': '06'},
|
||||
{'month': 'Juli', 'value': '07'},
|
||||
{'month': 'August', 'value': '08'},
|
||||
{'month': 'September', 'value': '09'},
|
||||
{'month': 'Oktober', 'value': '10'},
|
||||
{'month': 'November', 'value': '11'},
|
||||
{'month': 'Dezember', 'value': '12'},
|
||||
];
|
||||
|
||||
static List staticListGetDaysInBetween(
|
||||
List<AppWriteTankModel> list,
|
||||
String monthValue,
|
||||
int year,
|
||||
) {
|
||||
List<AppWriteTankModel> searchList = [];
|
||||
List<DateTime> days = [];
|
||||
var dateYear = year;
|
||||
var auswahlMonat = int.parse(monthValue);
|
||||
var startDate = DateTime(dateYear, auswahlMonat, 1);
|
||||
var endDate = DateTime(dateYear, auswahlMonat + 1, 0);
|
||||
//List of Days
|
||||
for (int i = 0; i <= endDate.difference(startDate).inDays; i++) {
|
||||
days.add(DateTime(startDate.year, startDate.month, startDate.day + i));
|
||||
}
|
||||
sumMonatEuro = 0.0;
|
||||
sumMonatLiter = 0.0;
|
||||
int entryCounter = 0;
|
||||
for (var day in days) {
|
||||
for (AppWriteTankModel model in list) {
|
||||
int milliSecDate = (DateTime.parse(model.date)).microsecondsSinceEpoch;
|
||||
if (milliSecDate == day.microsecondsSinceEpoch) {
|
||||
entryCounter = entryCounter + 1;
|
||||
model.mnIndexCount = entryCounter;
|
||||
searchList.add(model);
|
||||
var mnEuroGesamt =
|
||||
double.parse(model.liters) * double.parse(model.pricePerLiter);
|
||||
sumMonatEuro += mnEuroGesamt;
|
||||
sumMonatLiter += double.parse(model.liters);
|
||||
}
|
||||
}
|
||||
}
|
||||
searchList.sort((a, b) {
|
||||
final DateTime dateA = DateTime.parse(a.date);
|
||||
final DateTime dateB = DateTime.parse(b.date);
|
||||
return dateB.compareTo(dateA);
|
||||
});
|
||||
return staticSearchList = searchList;
|
||||
}
|
||||
|
||||
static getMySnackeBar(String title, String message, Color backgroundColor) {
|
||||
Get.snackbar(
|
||||
title,
|
||||
message,
|
||||
backgroundColor: backgroundColor,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
duration: const Duration(seconds: 4),
|
||||
);
|
||||
}
|
||||
|
||||
static removeFromStorage() {
|
||||
dataBox.remove('sessionId');
|
||||
dataBox.remove('userId');
|
||||
dataBox.remove('userName');
|
||||
dataBox.remove('userEmail');
|
||||
}
|
||||
|
||||
static bool hasData() {
|
||||
return dataBox.hasData('sessionId');
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,7 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <desktop_webview_window/desktop_webview_window_plugin.h>
|
||||
#include <printing/printing_plugin.h>
|
||||
#include <screen_retriever_linux/screen_retriever_linux_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
#include <window_manager/window_manager_plugin.h>
|
||||
@ -16,6 +17,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) desktop_webview_window_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopWebviewWindowPlugin");
|
||||
desktop_webview_window_plugin_register_with_registrar(desktop_webview_window_registrar);
|
||||
g_autoptr(FlPluginRegistrar) printing_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "PrintingPlugin");
|
||||
printing_plugin_register_with_registrar(printing_registrar);
|
||||
g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin");
|
||||
screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar);
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
desktop_webview_window
|
||||
printing
|
||||
screen_retriever_linux
|
||||
url_launcher_linux
|
||||
window_manager
|
||||
|
||||
@ -8,8 +8,10 @@ import Foundation
|
||||
import desktop_webview_window
|
||||
import device_info_plus
|
||||
import flutter_web_auth_2
|
||||
import geolocator_apple
|
||||
import package_info_plus
|
||||
import path_provider_foundation
|
||||
import printing
|
||||
import screen_retriever_macos
|
||||
import url_launcher_macos
|
||||
import window_manager
|
||||
@ -19,8 +21,10 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
DesktopWebviewWindowPlugin.register(with: registry.registrar(forPlugin: "DesktopWebviewWindowPlugin"))
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
FlutterWebAuth2Plugin.register(with: registry.registrar(forPlugin: "FlutterWebAuth2Plugin"))
|
||||
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
PrintingPlugin.register(with: registry.registrar(forPlugin: "PrintingPlugin"))
|
||||
ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
|
||||
|
||||
@ -67,7 +67,7 @@
|
||||
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
|
||||
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
|
||||
33CC10ED2044A3C60003C045 /* AppwriteStarterKit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = appwrite_flutter_starter_kit.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
33CC10ED2044A3C60003C045 /* AppwriteStarterKit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tankguru_flutter_app_appwrite.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
|
||||
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||
|
||||
@ -2,6 +2,14 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>This app needs access to your location to find nearby gas stations and services.</string>
|
||||
<key>NSLocationAlwaysUsageDescription</key>
|
||||
<string>This app needs access to your location to track your position even when the app is in the background.</string>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>location</string>
|
||||
</array>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
|
||||
192
pubspec.lock
@ -41,6 +41,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
barcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: barcode
|
||||
sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.9"
|
||||
bidi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bidi
|
||||
sha256: "77f475165e94b261745cf1032c751e2032b8ed92ccb2bf5716036db79320637d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.13"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -137,6 +153,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.2"
|
||||
equatable:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: equatable
|
||||
sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.7"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -153,13 +177,37 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
fl_chart:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: fl_chart
|
||||
sha256: "577aeac8ca414c25333334d7c4bb246775234c0e44b38b10a82b559dd4d764e7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_dotenv:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_dotenv
|
||||
sha256: b7c7be5cd9f6ef7a78429cabd2774d3c4af50e79cb2b7593e3d5d763ef95c61b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.2.1"
|
||||
flutter_launcher_icons:
|
||||
dependency: "direct dev"
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_launcher_icons
|
||||
sha256: bfa04787c85d80ecb3f8777bde5fc10c3de809240c48fa061a2c2bf15ea5211c
|
||||
@ -203,6 +251,70 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
geolocator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: geolocator
|
||||
sha256: f62bcd90459e63210bbf9c35deb6a51c521f992a78de19a1fe5c11704f9530e2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "13.0.4"
|
||||
geolocator_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_android
|
||||
sha256: fcb1760a50d7500deca37c9a666785c047139b5f9ee15aa5469fae7dbbe3170d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.6.2"
|
||||
geolocator_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_apple
|
||||
sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.13"
|
||||
geolocator_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_platform_interface
|
||||
sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.6"
|
||||
geolocator_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_web
|
||||
sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.3"
|
||||
geolocator_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_windows
|
||||
sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.5"
|
||||
get:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: get
|
||||
sha256: c79eeb4339f1f3deffd9ec912f8a923834bec55f7b49c9e882b8fef2c139d425
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.7.2"
|
||||
get_storage:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: get_storage
|
||||
sha256: "39db1fffe779d0c22b3a744376e86febe4ade43bf65e06eab5af707dc84185a2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -212,13 +324,13 @@ packages:
|
||||
source: hosted
|
||||
version: "0.15.5"
|
||||
http:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
|
||||
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.5.0"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -251,6 +363,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.9.0"
|
||||
latlong2:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: latlong2
|
||||
sha256: "98227922caf49e6056f91b6c56945ea1c7b166f28ffcd5fb8e72fc0b453cc8fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.1"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -299,6 +419,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.0"
|
||||
path_parsing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_parsing
|
||||
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -347,6 +475,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
pdf:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: pdf
|
||||
sha256: "28eacad99bffcce2e05bba24e50153890ad0255294f4dd78a17075a2ba5c8416"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.11.3"
|
||||
pdf_widget_wrapper:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pdf_widget_wrapper
|
||||
sha256: c930860d987213a3d58c7ec3b7ecf8085c3897f773e8dc23da9cae60a5d6d0f5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -379,6 +523,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.1"
|
||||
printing:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: printing
|
||||
sha256: "482cd5a5196008f984bb43ed0e47cbfdca7373490b62f3b27b3299275bf22a93"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.14.2"
|
||||
qr:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: qr
|
||||
sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
screen_retriever:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -432,6 +592,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
sprintf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sprintf
|
||||
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -536,14 +704,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.1"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.2.0"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -625,5 +801,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.7.0-0 <4.0.0"
|
||||
flutter: ">=3.27.0"
|
||||
dart: ">=3.8.0-0 <4.0.0"
|
||||
flutter: ">=3.27.4"
|
||||
|
||||
31
pubspec.yaml
@ -1,6 +1,6 @@
|
||||
name: appwrite_flutter_starter_kit
|
||||
name: tankguru_flutter_app_appwrite
|
||||
description: "Appwrite StarterKit in Flutter"
|
||||
publish_to: 'none'
|
||||
publish_to: "none"
|
||||
|
||||
version: 1.0.0
|
||||
|
||||
@ -8,14 +8,23 @@ environment:
|
||||
sdk: ^3.5.4
|
||||
|
||||
dependencies:
|
||||
appwrite: ^14.0.0
|
||||
cupertino_icons: ^1.0.8
|
||||
fl_chart: ^1.0.0
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
flutter_dotenv: ^5.2.1
|
||||
flutter_launcher_icons: ^0.14.3
|
||||
geolocator: ^13.0.1
|
||||
get: ^4.6.6
|
||||
get_storage: ^2.1.1
|
||||
http: ^1.4.0
|
||||
intl: ^0.20.2
|
||||
appwrite: ^14.0.0
|
||||
latlong2: ^0.9.1
|
||||
pdf: ^3.11.3
|
||||
printing: ^5.14.2
|
||||
url_launcher: ^6.3.1
|
||||
window_manager: ^0.4.3
|
||||
cupertino_icons: ^1.0.8
|
||||
|
||||
dependency_overrides:
|
||||
flutter_web_auth_2: 4.1.0
|
||||
@ -23,7 +32,15 @@ dependency_overrides:
|
||||
dev_dependencies:
|
||||
flutter_lints: ^5.0.0
|
||||
flutter_native_splash: ^2.4.4
|
||||
flutter_launcher_icons: ^0.14.3
|
||||
|
||||
flutter_icons:
|
||||
android: true
|
||||
ios: true
|
||||
image_path: "lib/icons/gasolineCyberpunk.png"
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- .env
|
||||
- lib/images/
|
||||
- lib/icons/
|
||||
|
||||