Compare commits

...
2 Commits
22 changed files with 570 additions and 274 deletions
+15
View File
@@ -0,0 +1,15 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Flutter Web (Chrome, Port 5000)",
"request": "launch",
"type": "dart",
"deviceId": "chrome",
"toolArgs": [
"--web-port",
"5000"
]
}
]
}
+6
View File
@@ -0,0 +1,6 @@
class EnvDirectus {
static const String directusPublicEndpoint = 'https://directus.joshihomeserver.ipv64.net';
static const String directusApiTokenBearer = '6mqGYbOCQAg7UAhIubb87wXcJVHTAsv9';
static const String directusTankUsersRest = 'TankUsers';
static const String directusTankStopRest = 'TankStopGuru01';
}
+7 -8
View File
@@ -1,21 +1,20 @@
import 'package:get/get.dart';
import '../models/tank_model.dart';
import '../models/tank_guru_directus_model.dart';
import '../pages/edit_view.dart';
import '../services/appwrite_service.dart';
import '../services/directus_service.dart';
class DetailController extends GetxController {
late TankModel tank;
final appwriteService = AppwriteService();
late TankGuruDirectusModel tank;
final directusService = DirectusService();
@override
void onInit() {
tank = Get.arguments as TankModel;
tank = Get.arguments as TankGuruDirectusModel;
super.onInit();
}
void deleteEntry() {
appwriteService
.deleteDocumentFromCollection(tank.szDocumentId)
void deleteEntry() async {
await directusService.deleteDocumentFromTable(tank.iD!)
.then((_) {
Get.back(
result: 'deleted',
+27 -23
View File
@@ -1,14 +1,17 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../helper/helper.dart' as helper;
import '../models/locationiq_model.dart';
import '../models/tank_model.dart';
import '../services/appwrite_service.dart';
//import '../models/tank_model.dart';
//import '../services/appwrite_service.dart';
import '../models/tank_guru_directus_model.dart';
import '../services/directus_service.dart';
import '../services/geolocation_service.dart';
import '../services/location_iq_service.dart';
import '../pages/home_view.dart';
class EditController extends GetxController {
final AppwriteService appwriteService = AppwriteService();
final DirectusService directusService = DirectusService();
// Form controllers
final dateController = TextEditingController();
@@ -23,8 +26,9 @@ class EditController extends GetxController {
final calculatedTotal = '0.00'.obs;
final isLoadingLocation = false.obs;
TankModel? editingTankModel;
TankGuruDirectusModel? editingTankModel;
LocationIQ? currentLocationIQ;
String currentUserIdentId = helper.kCurrentUserIdentId; // Use the current user identId from helper
@override
void onInit() {
@@ -32,7 +36,7 @@ class EditController extends GetxController {
// Check if we're editing or creating new
if (Get.arguments != null) {
editingTankModel = Get.arguments as TankModel;
editingTankModel = Get.arguments as TankGuruDirectusModel;
isNewEntry.value = false;
_loadExistingData();
} else {
@@ -47,12 +51,12 @@ class EditController extends GetxController {
}
void _loadExistingData() {
dateController.text = editingTankModel!.szDate;
odometerController.text = editingTankModel!.szOdometer;
litersController.text = editingTankModel!.szLiters;
pricePerLiterController.text = editingTankModel!.szPricePerLiter;
locationController.text = editingTankModel!.szLocation;
calculatedTotal.value = editingTankModel!.szPriceTotal;
dateController.text = editingTankModel!.tankDate!;
odometerController.text = editingTankModel!.odometer!;
litersController.text = editingTankModel!.liter!;
pricePerLiterController.text = editingTankModel!.pricePerLiter!;
locationController.text = editingTankModel!.standort!;
calculatedTotal.value = editingTankModel!.szPriceTotal!;
}
void _setDefaultDate() {
@@ -147,26 +151,26 @@ class EditController extends GetxController {
isLoading.value = true;
try {
final userId = await appwriteService.getCurrentUserId();
if (userId == null) {
final userId = currentUserIdentId;
if (userId == '') {
throw Exception('Benutzer nicht eingeloggt');
}
final data = {
'userId': userId,
'date': dateController.text,
'odometer': odometerController.text,
'liters': litersController.text.replaceAll(',', '.'),
'pricePerLiter': pricePerLiterController.text.replaceAll(',', '.'),
'location': locationController.text,
'UserIdentId': userId,
'TankDate': dateController.text,
'Odometer': odometerController.text,
'Liter': litersController.text.replaceAll(',', '.'),
'PricePerLiter': pricePerLiterController.text.replaceAll(',', '.'),
'Standort': locationController.text,
};
bool success;
if (isNewEntry.value) {
success = await appwriteService.createDocumentInCollection(data);
success = await directusService.createDocumentInTable(data);
} else {
success = await appwriteService.updateDocumentInCollection(
editingTankModel!.szDocumentId,
success = await directusService.updateDocumentInTable(
editingTankModel!.iD,
data,
);
}
@@ -181,7 +185,7 @@ class EditController extends GetxController {
backgroundColor: Colors.green[100],
colorText: Colors.green[900],
);
Get.offAllNamed(HomePage.namedRoute);
Get.offAllNamed(HomePage.namedRoute, arguments: {'userIdentId': userId});
} else {
throw Exception('Speichern fehlgeschlagen');
}
+11 -10
View File
@@ -1,8 +1,9 @@
import 'package:get/get.dart';
import '../models/tank_model.dart';
//import '../models/tank_model.dart';
import '../models/tank_guru_directus_model.dart';
class GraphController extends GetxController {
final listTankModel = <TankModel>[].obs;
final listTankModel = <TankGuruDirectusModel>[].obs;
final sumYearKm = 0.0.obs;
final sumYearLiters = 0.0.obs;
final sumYearPrice = 0.0.obs;
@@ -12,7 +13,7 @@ class GraphController extends GetxController {
@override
void onInit() {
if (Get.arguments != null) {
var args = Get.arguments as List<TankModel>;
var args = Get.arguments as List<TankGuruDirectusModel>;
listTankModel.addAll(args);
_calcSumms();
}
@@ -36,23 +37,23 @@ class GraphController extends GetxController {
// Sort by date to calculate km correctly
listTankModel.sort((a, b) {
var dateA = DateTime.tryParse(a.szDate)!;
var dateB = DateTime.tryParse(b.szDate)!;
var dateA = DateTime.tryParse(a.tankDate!)!;
var dateB = DateTime.tryParse(b.tankDate!)!;
return dateA.compareTo(dateB);
});
var firstOdometer = double.tryParse(listTankModel.first.szOdometer) ?? 0.0;
var lastOdometer = double.tryParse(listTankModel.last.szOdometer) ?? 0.0;
var firstOdometer = double.tryParse(listTankModel.first.odometer!) ?? 0.0;
var lastOdometer = double.tryParse(listTankModel.last.odometer!) ?? 0.0;
sumYearKm.value = lastOdometer - firstOdometer;
szTachostand.value = listTankModel.last.szOdometer;
szTachostand.value = listTankModel.last.odometer!;
double totalLiters = 0.0;
double totalPrice = 0.0;
for (var tank in listTankModel) {
totalLiters += double.tryParse(tank.szLiters) ?? 0.0;
totalPrice += double.tryParse(tank.szPriceTotal) ?? 0.0;
totalLiters += double.tryParse(tank.liter!) ?? 0.0;
totalPrice += double.tryParse(tank.szPriceTotal!) ?? 0.0;
}
sumYearLiters.value = totalLiters;
+30 -35
View File
@@ -1,21 +1,24 @@
import 'package:flutter_tank_web_app/pages/gasstations_view.dart';
import 'package:get/get.dart';
import '../models/tank_model.dart';
import '../models/tank_guru_directus_model.dart';
import '../pages/detail_view.dart';
import '../pages/edit_view.dart';
import '../services/appwrite_service.dart';
import '../pages/graph_view.dart';
import '../pages/gasstations_view.dart';
import '../services/directus_service.dart';
class HomeController extends GetxController {
final isLoading = false.obs;
final listTankModel = <TankModel>[].obs;
final appwriteService = AppwriteService();
//final listTankModel = <TankModel>[].obs;
final listTankModel = <TankGuruDirectusModel>[].obs;
//final appwriteService = AppwriteService();
final directusService = DirectusService();
final currentYear = DateTime.now().year.obs;
final argumentMap = Get.arguments as Map<String, dynamic>?;
@override
void onInit() {
_loadListDocument();
_loadListDirectus();
super.onInit();
}
@@ -27,45 +30,46 @@ class HomeController extends GetxController {
void changeYear(int year) {
currentYear.value = year;
_loadListDocument();
_loadListDirectus();
update();
}
Future<void> _loadListDocument() async {
Future<void> _loadListDirectus() async {
isLoading.value = true;
if (listTankModel.isNotEmpty) {
listTankModel.clear();
}
var userId = await appwriteService.getCurrentUserId();
var userId = argumentMap?['userIdentId'] as String?;
if (userId == null) {
//User nicht eingeloggt, evtl. zur Login-Seite navigieren
update();
return;
}
var resultList = await appwriteService.getDocumentsFromCollection(userId);
var resultList = await directusService.getTankGuruDataFromTable(userId);
if (resultList.isEmpty) {
//Dokumente erfolgreich geladen, hier können Sie die Liste verarbeiten
print('Dokumente wurden nicht geladen: ${resultList.length}');
} else {
for (var doc in resultList) {
var tankModel = TankModel.fromMap(doc.data);
listTankModel.add(tankModel);
}
listTankModel(
resultList
.map((tankStopJson) => TankGuruDirectusModel.fromJson(tankStopJson))
.toList(),
);
if (listTankModel.isNotEmpty) {
var sortList = listTankModel.where((tank) {
var year = (DateTime.tryParse(tank.szDate)!).year;
var year = (DateTime.tryParse(tank.tankDate!)!).year;
return year == currentYear.value;
}).toList();
listTankModel.clear();
listTankModel.addAll(sortList);
listTankModel.sort((a, b) {
var dateA = DateTime.tryParse(a.szDate)!;
var dateB = DateTime.tryParse(b.szDate)!;
var dateA = DateTime.tryParse(a.tankDate!)!;
var dateB = DateTime.tryParse(b.tankDate!)!;
return dateB.compareTo(dateA);
});
for (var tank in listTankModel) {
print(
'SortTankModel: ${tank.szDate} - ${tank.szLiters}L - ${tank.szPricePerLiter}€/L - Total: ${tank.szPriceTotal}€',
'SortTankModel: ${tank.tankDate!} - ${tank.liter}L - ${tank.pricePerLiter}€/L - Total: ${tank.szPriceTotal}€',
);
}
}
@@ -75,27 +79,18 @@ class HomeController extends GetxController {
}
Future<void> logout() async {
var logoutSuccess = await appwriteService.logout();
if (logoutSuccess) {
Get.snackbar(
'Logout erfolgreich',
'Sie wurden abgemeldet.',
snackPosition: SnackPosition.BOTTOM,
);
Get.offAllNamed('/login-page');
} else {
Get.snackbar(
'Logout fehlgeschlagen',
'Bitte versuchen Sie es erneut.',
snackPosition: SnackPosition.BOTTOM,
);
}
Get.snackbar(
'Logout erfolgreich',
'Sie wurden abgemeldet.',
snackPosition: SnackPosition.BOTTOM,
);
Get.offAllNamed('/login-page');
}
Future<void> viewTankDetails(TankModel tank) async {
Future<void> viewTankDetails(TankGuruDirectusModel tank) async {
var result = await Get.toNamed(DetailPage.namedRoute, arguments: tank);
if (result == 'deleted') {
_loadListDocument();
_loadListDirectus();
}
}
+41 -10
View File
@@ -1,13 +1,16 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../helper/helper.dart' as helper;
import '../models/tank_user_model.dart';
import '../pages/home_view.dart';
import '../pages/signin_view.dart';
import '../services/appwrite_service.dart';
import '../services/directus_service.dart';
import '../helper/password_helper.dart';
class LoginController extends GetxController {
final emailController = TextEditingController();
final passwordController = TextEditingController();
final appwriteService = AppwriteService();
final directusService = DirectusService();
@override
void onReady() {}
@@ -18,29 +21,57 @@ class LoginController extends GetxController {
passwordController.dispose();
}
void login() async {
void newLoginDirectus() async {
var email = emailController.text;
var password = passwordController.text;
// Hier können Sie die Login-Logik implementieren
print('Login mit E-Mail: $email, Passwort: $password');
var loginSuccess = await appwriteService.login(email, password);
if (loginSuccess) {
var result = await directusService.getTankUserList();
if(result.isEmpty) {
Get.snackbar(
'Login fehlgeschlagen',
'Fehler beim Abrufen der Benutzerdaten. Bitte versuchen Sie es später erneut.',
snackPosition: SnackPosition.BOTTOM,
);
return;
}
var tankUserList = result.map((userJson) => TankUser.fromJson(userJson)).toList();
var identId = tankUserList.firstWhereOrNull((user) => user.email == email)?.userIdentId ?? '';
if(identId.isEmpty) {
Get.snackbar(
'Login fehlgeschlagen',
'Benutzer mit dieser E-Mail-Adresse wurde nicht gefunden.',
snackPosition: SnackPosition.BOTTOM,
);
return;
}
var storedHash = tankUserList.firstWhereOrNull((user) => user.email == email)?.passwordHash ?? '';
if(storedHash.isEmpty) {
Get.snackbar(
'Login fehlgeschlagen',
'Kein gespeicherter Passwort-Hash für diesen Benutzer gefunden.',
snackPosition: SnackPosition.BOTTOM,
);
return;
}
var isPassCorrect = await PasswordHelper.verifyPassword(password, storedHash);
if(isPassCorrect && identId.isNotEmpty) {
Get.snackbar(
'Login erfolgreich',
'Willkommen zurück!',
snackPosition: SnackPosition.BOTTOM,
);
Get.offAndToNamed(HomePage.namedRoute);
print('Login erfolgreich für Benutzer mit IdentId: $identId');
helper.kCurrentUserIdentId = identId; // Set the current user identId
Get.offAndToNamed(HomePage.namedRoute, arguments: {'userIdentId': identId});
} else {
Get.snackbar(
'Login fehlgeschlagen',
'Bitte überprüfen Sie Ihre E-Mail und Ihr Passwort und versuchen Sie es erneut.',
'Bitte überprüfen Sie Ihre E-Mail und Ihr Passwort und versuchen Sie es erneut\noder registrieren Sie sich, wenn Sie noch kein Konto haben.',
snackPosition: SnackPosition.BOTTOM,
);
}
}
void goToSignInPage() {
Get.offAndToNamed(SigninPage.namedRoute);
Get.offAndToNamed(SigninPage.namedRoute, arguments: {'userIdentId': ''});
}
}
+17 -10
View File
@@ -1,14 +1,19 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../pages/home_view.dart';
import '../pages/login_view.dart';
import '../services/appwrite_service.dart';
import '../services/directus_service.dart';
class SigninController extends GetxController {
final userNameController = TextEditingController();
final emailController = TextEditingController();
final passwordController = TextEditingController();
final appwriteService = AppwriteService();
final cityController = TextEditingController();
final countryController = TextEditingController();
final directusService = DirectusService();
late String userIdentId = Get.arguments['userIdentId'] ?? '';
@override
void onReady() {}
@@ -18,6 +23,8 @@ class SigninController extends GetxController {
userNameController.dispose();
emailController.dispose();
passwordController.dispose();
cityController.dispose();
countryController.dispose();
}
void register() async {
@@ -27,16 +34,15 @@ class SigninController extends GetxController {
var userName = userNameController.text;
var email = emailController.text;
var password = passwordController.text;
var city = cityController.text; // Hier können Sie die Stadt aus den Eingaben abrufen, falls erforderlich
var country = countryController.text; // Hier können Sie das Land aus den Eingaben abrufen, falls erforderlich
// Testausgabe
print(
'Registrieren mit Benutzername: $userName, E-Mail: $email, Passwort: $password',
);
//appwrite Registrierung und login Logik hier einfügen
var registrationSuccess = await appwriteService.register(
userName,
email,
password,
'Registrieren mit Benutzername: $userName, E-Mail: $email, Passwort: $password, Stadt: $city, Land: $country',
);
//directus Registrierung und login Logik hier einfügen
bool registrationSuccess = await directusService.register(userName, email, password, city, country);
//Nach erfolgreicher Registrierung Meldung und zur Startseite navigieren
if (registrationSuccess) {
Get.snackbar(
@@ -44,7 +50,8 @@ class SigninController extends GetxController {
'Willkommen, $userName!',
snackPosition: SnackPosition.BOTTOM,
);
Get.offAndToNamed(HomePage.namedRoute);
userIdentId = base64Encode(utf8.encode('$email:$password'));
Get.offAndToNamed(HomePage.namedRoute, arguments: {'userIdentId': userIdentId});
} else {
Get.snackbar(
'Registrierung fehlgeschlagen',
+2
View File
@@ -12,3 +12,5 @@ String getCurrentDate(DateTime date) {
var kListDropDownYear = <int>[
for (var year = DateTime.now().year; year >= 2020; year--) year
];
var kCurrentUserIdentId = '';
+120
View File
@@ -0,0 +1,120 @@
import 'dart:convert';
import 'dart:math';
import 'package:cryptography/cryptography.dart';
class PasswordHelper {
static const int _saltLength = 16;
static const int _hashLength = 32;
// 19 MiB, laut aktueller cryptography-Doku ein sinnvoller Startwert.
static const int _memory = 19 * 1024;
static const int _iterations = 2;
static const int _parallelism = 1;
static final Argon2id _algorithm = Argon2id(
memory: _memory,
iterations: _iterations,
parallelism: _parallelism,
hashLength: _hashLength,
);
/// Erstellt einen neuen Passwort-Hash inklusive zufälligem Salt.
///
/// Rückgabeformat:
/// argon2id$memory$iterations$parallelism$saltBase64$hashBase64
static Future<String> hashPassword(String password) async {
final salt = _generateSalt(_saltLength);
final secretKey = await _algorithm.deriveKeyFromPassword(
password: password,
nonce: salt,
);
final hashBytes = await secretKey.extractBytes();
final saltBase64 = base64UrlEncode(salt);
final hashBase64 = base64UrlEncode(hashBytes);
return 'argon2id'
'\$$_memory'
'\$$_iterations'
'\$$_parallelism'
'\$$saltBase64'
'\$$hashBase64';
}
/// Prüft ein Klartext-Passwort gegen den gespeicherten Hash.
static Future<bool> verifyPassword(
String password,
String storedHash,
) async {
try {
final parts = storedHash.split('\$');
if (parts.length != 6) {
return false;
}
if (parts[0] != 'argon2id') {
return false;
}
final memory = int.parse(parts[1]);
final iterations = int.parse(parts[2]);
final parallelism = int.parse(parts[3]);
final salt = base64Url.decode(parts[4]);
final expectedHash = base64Url.decode(parts[5]);
final algorithm = Argon2id(
memory: memory,
iterations: iterations,
parallelism: parallelism,
hashLength: expectedHash.length,
);
final secretKey = await algorithm.deriveKeyFromPassword(
password: password,
nonce: salt,
);
final calculatedHash = await secretKey.extractBytes();
return _constantTimeEquals(
calculatedHash,
expectedHash,
);
} catch (_) {
return false;
}
}
/// Erzeugt kryptographisch zufälligen Salt.
static List<int> _generateSalt(int length) {
final random = Random.secure();
return List<int>.generate(
length,
(_) => random.nextInt(256),
);
}
/// Vergleich ohne vorzeitigen Abbruch.
static bool _constantTimeEquals(
List<int> a,
List<int> b,
) {
if (a.length != b.length) {
return false;
}
var difference = 0;
for (var i = 0; i < a.length; i++) {
difference |= a[i] ^ b[i];
}
return difference == 0;
}
}
+2 -2
View File
@@ -22,9 +22,9 @@ class ChartData {
for (int i = 0; i < tankList.length; i++) {
final tank = tankList[i];
final liters = double.tryParse(tank.szLiters) ?? 0.0;
final liters = double.tryParse(tank.liter) ?? 0.0;
final priceTotal = double.tryParse(tank.szPriceTotal) ?? 0.0;
final pricePerLiter = double.tryParse(tank.szPricePerLiter) ?? 0.0;
final pricePerLiter = double.tryParse(tank.pricePerLiter) ?? 0.0;
points.add(
ChartDataPoint(
+57
View File
@@ -0,0 +1,57 @@
class TankGuruDirectusModel {
int? iD;
String? userIdentId;
String? tankDate;
String? odometer;
String? liter;
String? pricePerLiter;
String? standort;
String? createdAt;
String? szPriceTotal;
TankGuruDirectusModel(
{this.iD,
this.userIdentId,
this.tankDate,
this.odometer,
this.liter,
this.pricePerLiter,
this.standort,
this.createdAt,
this.szPriceTotal});
TankGuruDirectusModel.fromJson(Map<String, dynamic> json) {
iD = json['ID'];
userIdentId = json['UserIdentId'];
tankDate = json['TankDate'];
odometer = json['Odometer'];
liter = json['Liter'];
pricePerLiter = json['PricePerLiter'];
standort = json['Standort'];
createdAt = json['CreatedAt'];
// Berechnung des Gesamtpreises
if (liter != null && pricePerLiter != null) {
double literValue = double.tryParse(liter!) ?? 0.0;
double pricePerLiterValue = double.tryParse(pricePerLiter!) ?? 0.0;
szPriceTotal = (literValue * pricePerLiterValue).toStringAsFixed(3);
} else {
szPriceTotal = '0.00';
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['ID'] = iD;
data['UserIdentId'] = userIdentId;
data['TankDate'] = tankDate;
data['Odometer'] = odometer;
data['Liter'] = liter;
data['PricePerLiter'] = pricePerLiter;
data['Standort'] = standort;
data['CreatedAt'] = createdAt;
data['SzPriceTotal'] = szPriceTotal;
return data;
}
}
+44
View File
@@ -0,0 +1,44 @@
class TankUser {
int? iD;
String? userIdentId;
String? userName;
String? city;
String? country;
String? email;
String? passwordHash;
String? createdAt;
TankUser(
{this.iD,
this.userIdentId,
this.userName,
this.city,
this.country,
this.email,
this.passwordHash,
this.createdAt});
TankUser.fromJson(Map<String, dynamic> json) {
iD = json['ID'];
userIdentId = json['UserIdentId'];
userName = json['UserName'];
city = json['City'];
country = json['Country'];
email = json['Email'];
passwordHash = json['PasswordHash'];
createdAt = json['CreatedAt'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['ID'] = iD;
data['UserIdentId'] = userIdentId;
data['UserName'] = userName;
data['City'] = city;
data['Country'] = country;
data['Email'] = email;
data['PasswordHash'] = passwordHash;
data['CreatedAt'] = createdAt;
return data;
}
}
+9 -7
View File
@@ -54,8 +54,8 @@ class DetailPage extends GetView<DetailController> {
DetailStatWidget(
icon: Icons.location_on,
label: 'Standort',
value: detailCtrl.tank.szLocation.isNotEmpty
? detailCtrl.tank.szLocation
value: detailCtrl.tank.standort!.isNotEmpty
? detailCtrl.tank.standort!
: 'Nicht angegeben',
iconColor: Colors.red,
),
@@ -63,7 +63,9 @@ class DetailPage extends GetView<DetailController> {
DetailStatWidget(
icon: Icons.calendar_today,
label: 'Datum',
value: detailCtrl.tank.szDate,
value: detailCtrl.tank.tankDate!.isNotEmpty
? detailCtrl.tank.tankDate!
: 'Nicht angegeben',
iconColor: Colors.blueGrey,
),
],
@@ -78,7 +80,7 @@ class DetailPage extends GetView<DetailController> {
DetailStatWidget(
icon: Icons.local_gas_station,
label: 'Liter getankt',
value: '${detailCtrl.tank.szLiters} L',
value: '${detailCtrl.tank.liter!} L',
iconColor: Colors.orange,
valueSize: 24,
valueWeight: FontWeight.bold,
@@ -90,7 +92,7 @@ class DetailPage extends GetView<DetailController> {
child: DetailStatWidget(
icon: Icons.euro,
label: 'Preis pro Liter',
value: '${detailCtrl.tank.szPricePerLiter}€',
value: '${detailCtrl.tank.pricePerLiter!}€',
iconColor: Colors.green,
),
),
@@ -99,7 +101,7 @@ class DetailPage extends GetView<DetailController> {
child: DetailStatWidget(
icon: Icons.receipt,
label: 'Gesamtpreis',
value: '${detailCtrl.tank.szPriceTotal}€',
value: '${detailCtrl.tank.szPriceTotal!}€',
iconColor: Colors.green[700]!,
valueWeight: FontWeight.bold,
),
@@ -118,7 +120,7 @@ class DetailPage extends GetView<DetailController> {
DetailStatWidget(
icon: Icons.speed,
label: 'Kilometerstand',
value: '${detailCtrl.tank.szOdometer} km',
value: '${detailCtrl.tank.odometer!} km',
iconColor: Colors.blue,
valueSize: 24,
valueWeight: FontWeight.bold,
+4 -4
View File
@@ -144,7 +144,7 @@ class HomePage extends GetView<HomeController> {
CrossAxisAlignment.start,
children: [
Text(
tank.szDate,
tank.tankDate!,
style: const TextStyle(
fontSize: 16,
fontWeight:
@@ -153,7 +153,7 @@ class HomePage extends GetView<HomeController> {
),
const SizedBox(height: 2),
Text(
'${tank.szLiters} Liter',
'${tank.liter!} Liter',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
@@ -198,12 +198,12 @@ class HomePage extends GetView<HomeController> {
_buildInfoChip(
icon: Icons.euro,
label: 'Preis/L',
value: '${tank.szPricePerLiter}€',
value: '${tank.pricePerLiter!}€',
),
_buildInfoChip(
icon: Icons.speed,
label: 'Km-Stand',
value: '${tank.szOdometer} km',
value: '${tank.odometer!} km',
),
],
),
+1 -1
View File
@@ -24,7 +24,7 @@ class LoginPage extends GetView<LoginController> {
color: Colors.blue.shade100,
child: SingleChildScrollView(
child: MyLoginWidget(
onButtonPressed: () => logCtrl.login(),
onButtonPressed: () => logCtrl.newLoginDirectus(),
signInOnTab: () => logCtrl.goToSignInPage(),
logCtrl: logCtrl,
),
+132
View File
@@ -0,0 +1,132 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../config/env_directus.dart';
import '../helper/password_helper.dart';
class DirectusService {
static final String endpoint = EnvDirectus.directusPublicEndpoint;
static final String apiTokenBearer = EnvDirectus.directusApiTokenBearer;
static final String tankUsersRest = EnvDirectus.directusTankUsersRest;
static final String tankStopRest = EnvDirectus.directusTankStopRest;
static Map<String, String> get headers => {
'Authorization': 'Bearer $apiTokenBearer',
'Content-Type': 'application/json',
};
Future<List<dynamic>> getTankUserList() async {
var client = http.Client();
String apiUrl = '$endpoint/items/$tankUsersRest';
var response = await client.get(Uri.parse(apiUrl), headers: headers);
if(response.statusCode != 200) {
print('Fehler beim Abrufen der TankUser-Liste: ${response.statusCode}');
client.close();
return [];
}
final json = jsonDecode(
utf8.decode(response.bodyBytes),
) as Map<String, dynamic>;
//print('$json}');
client.close();
return json['data'] as List<dynamic>;
}
Future<List<dynamic>> getTankGuruDataFromTable(String userIdentId) async {
var client = http.Client();
String apiUrl = '$endpoint/items/$tankStopRest?filter[UserIdentId][_eq]=$userIdentId';
var response = await client.get(Uri.parse(apiUrl), headers: headers);
if(response.statusCode != 200) {
print('Fehler beim Abrufen der Dokumente aus der Collection: ${response.statusCode}');
client.close();
return [];
}
final json = jsonDecode(
utf8.decode(response.bodyBytes),
) as Map<String, dynamic>;
//print('$json}');
client.close();
return json['data'] as List<dynamic>;
}
Future<void> deleteDocumentFromTable(int iD) async {
var client = http.Client();
String apiUrl = '$endpoint/items/$tankStopRest/$iD';
var response = await client.delete(Uri.parse(apiUrl), headers: headers);
if(response.statusCode < 200 || response.statusCode >= 300) {
print('Fehler beim Löschen des Dokuments aus der Collection: ${response.statusCode}');
client.close();
throw Exception('Fehler beim Löschen des Dokuments aus der Collection: ${response.statusCode}');
}
client.close();
}
Future<bool> createDocumentInTable(Map<String, String> data) async {
return await http.post(
Uri.parse('$endpoint/items/$tankStopRest'),
headers: headers,
body: jsonEncode(data),
).then((response) {
if (response.statusCode == 200 || response.statusCode == 201) {
print('Dokument erfolgreich erstellt: ${response.body}');
return true;
} else {
print('Fehler beim Erstellen des Dokuments: ${response.statusCode} - ${response.body}');
return false;
}
}).catchError((error) {
print('Fehler beim Erstellen des Dokuments: $error');
return false;
});
}
Future<bool> updateDocumentInTable(int? iD, Map<String, String> data) async {
return iD != null
? http.patch(
Uri.parse('$endpoint/items/$tankStopRest/$iD'),
headers: headers,
body: jsonEncode(data),
).then((response) {
if (response.statusCode == 200) {
print('Dokument erfolgreich aktualisiert: ${response.body}');
return true;
} else {
print('Fehler beim Aktualisieren des Dokuments: ${response.statusCode} - ${response.body}');
return false;
}
}).catchError((error) {
print('Fehler beim Aktualisieren des Dokuments: $error');
return false;
})
: Future.value(false);
}
Future<bool> register(String userName, String email, String password, String city, String country) async {
var client = http.Client();
String apiUrl = '$endpoint/items/$tankUsersRest';
Map<String, String> data = {
'UserIdentId': base64Encode(utf8.encode('$email:$password')),
'UserName': userName,
'City': city,
'Country': country,
'Email': email,
'PasswordHash': await PasswordHelper.hashPassword(password),
};
var response = await client.post(
Uri.parse(apiUrl),
headers: headers,
body: jsonEncode(data),
);
if (response.statusCode == 200 || response.statusCode == 201) {
print('Benutzer erfolgreich registriert: ${response.body}');
client.close();
return true;
} else {
print('Fehler bei der Registrierung des Benutzers: ${response.statusCode} - ${response.body}');
client.close();
return false;
}
}
}
+4 -3
View File
@@ -1,8 +1,9 @@
import 'package:flutter/material.dart';
import '../models/tank_model.dart';
//import '../models/tank_model.dart';
import '../models/tank_guru_directus_model.dart';
class DetailHeaderWidget extends StatelessWidget {
final TankModel tank;
final TankGuruDirectusModel tank;
const DetailHeaderWidget({super.key, required this.tank});
@@ -37,7 +38,7 @@ class DetailHeaderWidget extends StatelessWidget {
),
const SizedBox(height: 4),
Text(
tank.szDate,
tank.tankDate!,
style: const TextStyle(
color: Colors.white,
fontSize: 20,
+24
View File
@@ -100,6 +100,30 @@ class MySigninWidget extends StatelessWidget {
),
),
),
SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 50.0),
child: TextField(
controller: signCtrl.cityController,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'City',
),
),
),
SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 50.0),
child: TextField(
controller: signCtrl.countryController,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Country',
),
),
),
SizedBox(height: 30),
ElevatedButton(
style: ButtonStyle(
+14 -158
View File
@@ -1,14 +1,6 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
appwrite:
dependency: "direct main"
description:
name: appwrite
sha256: "3e1f618c8f75bafa49ef7b1b445f64c53cf4620a195443f4d119bbc95a666d0a"
url: "https://pub.dev"
source: hosted
version: "14.0.0"
args:
dependency: transitive
description:
@@ -65,14 +57,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.19.1"
cookie_jar:
dependency: transitive
description:
name: cookie_jar
sha256: a6ac027d3ed6ed756bfce8f3ff60cb479e266f3b0fdabd6242b804b6765e52de
url: "https://pub.dev"
source: hosted
version: "4.0.8"
crypto:
dependency: transitive
description:
@@ -81,6 +65,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.7"
cryptography:
dependency: "direct main"
description:
name: cryptography
sha256: "3eda3029d34ec9095a27a198ac9785630fe525c0eb6a49f3d575272f8e792ef0"
url: "https://pub.dev"
source: hosted
version: "2.9.0"
cupertino_icons:
dependency: "direct main"
description:
@@ -97,22 +89,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.7.11"
device_info_plus:
dependency: transitive
description:
name: device_info_plus
sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074
url: "https://pub.dev"
source: hosted
version: "10.1.2"
device_info_plus_platform_interface:
dependency: transitive
description:
name: device_info_plus_platform_interface
sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f
url: "https://pub.dev"
source: hosted
version: "7.0.3"
fake_async:
dependency: transitive
description:
@@ -163,22 +139,6 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_web_auth_2:
dependency: transitive
description:
name: flutter_web_auth_2
sha256: "4d3d2fd3d26bf1a26b3beafd4b4b899c0ffe10dc99af25abc58ffe24e991133c"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
flutter_web_auth_2_platform_interface:
dependency: transitive
description:
name: flutter_web_auth_2_platform_interface
sha256: e8669e262005a8354389ba2971f0fc1c36188481234ff50d013aaf993f30f739
url: "https://pub.dev"
source: hosted
version: "3.1.0"
flutter_web_plugins:
dependency: transitive
description: flutter
@@ -356,10 +316,10 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd"
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
url: "https://pub.dev"
source: hosted
version: "0.12.20"
version: "0.12.18"
material_color_utilities:
dependency: transitive
description:
@@ -372,10 +332,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.19.0"
version: "1.17.0"
native_toolchain_c:
dependency: transitive
description:
@@ -545,10 +505,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11"
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
url: "https://pub.dev"
source: hosted
version: "0.7.12"
version: "0.7.9"
typed_data:
dependency: transitive
description:
@@ -557,78 +517,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
universal_io:
dependency: transitive
description:
name: universal_io
sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2
url: "https://pub.dev"
source: hosted
version: "2.3.1"
url_launcher:
dependency: transitive
description:
name: url_launcher
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
url: "https://pub.dev"
source: hosted
version: "6.3.2"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611"
url: "https://pub.dev"
source: hosted
version: "6.3.28"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad
url: "https://pub.dev"
source: hosted
version: "6.3.6"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
url: "https://pub.dev"
source: hosted
version: "3.2.2"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18"
url: "https://pub.dev"
source: hosted
version: "3.2.5"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f
url: "https://pub.dev"
source: hosted
version: "2.4.2"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
url: "https://pub.dev"
source: hosted
version: "3.1.5"
uuid:
dependency: transitive
description:
@@ -661,22 +549,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.1"
web_socket:
dependency: transitive
description:
name: web_socket
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
url: "https://pub.dev"
source: hosted
version: "3.0.3"
win32:
dependency: transitive
description:
@@ -685,22 +557,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.15.0"
win32_registry:
dependency: transitive
description:
name: win32_registry
sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852"
url: "https://pub.dev"
source: hosted
version: "1.1.5"
window_to_front:
dependency: transitive
description:
name: window_to_front
sha256: "7aef379752b7190c10479e12b5fd7c0b9d92adc96817d9e96c59937929512aee"
url: "https://pub.dev"
source: hosted
version: "0.0.3"
xdg_directories:
dependency: transitive
description:
+1 -1
View File
@@ -9,7 +9,7 @@ environment:
sdk: ^3.10.7
dependencies:
appwrite: ^14.0.0
cryptography: ^2.9.0
cupertino_icons: ^1.0.8
flutter:
sdk: flutter