Umbau auf directus API Rest Service
This commit is contained in:
@@ -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';
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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': ''});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -11,4 +11,6 @@ String getCurrentDate(DateTime date) {
|
||||
|
||||
var kListDropDownYear = <int>[
|
||||
for (var year = DateTime.now().year; year >= 2020; year--) year
|
||||
];
|
||||
];
|
||||
|
||||
var kCurrentUserIdentId = '';
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user