diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..1131171 --- /dev/null +++ b/.vscode/launch.json @@ -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" + ] + } + ] +} diff --git a/lib/config/env_directus.dart b/lib/config/env_directus.dart new file mode 100644 index 0000000..20d59ec --- /dev/null +++ b/lib/config/env_directus.dart @@ -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'; +} diff --git a/lib/controller/detail_controller.dart b/lib/controller/detail_controller.dart index 24130f4..4280eb9 100644 --- a/lib/controller/detail_controller.dart +++ b/lib/controller/detail_controller.dart @@ -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', diff --git a/lib/controller/edit_controller.dart b/lib/controller/edit_controller.dart index b513871..ef4d9c4 100644 --- a/lib/controller/edit_controller.dart +++ b/lib/controller/edit_controller.dart @@ -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'); } diff --git a/lib/controller/graph_controller.dart b/lib/controller/graph_controller.dart index ae36015..b8fbb38 100644 --- a/lib/controller/graph_controller.dart +++ b/lib/controller/graph_controller.dart @@ -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 = [].obs; + final listTankModel = [].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; + var args = Get.arguments as List; 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; diff --git a/lib/controller/home_controller.dart b/lib/controller/home_controller.dart index a5d8084..63fb680 100644 --- a/lib/controller/home_controller.dart +++ b/lib/controller/home_controller.dart @@ -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 = [].obs; - final appwriteService = AppwriteService(); + //final listTankModel = [].obs; + final listTankModel = [].obs; + //final appwriteService = AppwriteService(); + final directusService = DirectusService(); final currentYear = DateTime.now().year.obs; + final argumentMap = Get.arguments as Map?; @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 _loadListDocument() async { + Future _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 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 viewTankDetails(TankModel tank) async { + Future viewTankDetails(TankGuruDirectusModel tank) async { var result = await Get.toNamed(DetailPage.namedRoute, arguments: tank); if (result == 'deleted') { - _loadListDocument(); + _loadListDirectus(); } } diff --git a/lib/controller/login_controller.dart b/lib/controller/login_controller.dart index d0036fc..d8e48a1 100644 --- a/lib/controller/login_controller.dart +++ b/lib/controller/login_controller.dart @@ -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': ''}); } } diff --git a/lib/controller/signin_controller.dart b/lib/controller/signin_controller.dart index 317cc22..b411cba 100644 --- a/lib/controller/signin_controller.dart +++ b/lib/controller/signin_controller.dart @@ -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', diff --git a/lib/helper/helper.dart b/lib/helper/helper.dart index 3b43b20..6c72425 100644 --- a/lib/helper/helper.dart +++ b/lib/helper/helper.dart @@ -11,4 +11,6 @@ String getCurrentDate(DateTime date) { var kListDropDownYear = [ for (var year = DateTime.now().year; year >= 2020; year--) year -]; \ No newline at end of file +]; + +var kCurrentUserIdentId = ''; \ No newline at end of file diff --git a/lib/helper/password_helper.dart b/lib/helper/password_helper.dart new file mode 100644 index 0000000..0ac74fb --- /dev/null +++ b/lib/helper/password_helper.dart @@ -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 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 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 _generateSalt(int length) { + final random = Random.secure(); + + return List.generate( + length, + (_) => random.nextInt(256), + ); + } + + /// Vergleich ohne vorzeitigen Abbruch. + static bool _constantTimeEquals( + List a, + List 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; + } +} \ No newline at end of file diff --git a/lib/models/chart_model.dart b/lib/models/chart_model.dart index 2c4ddad..474f06e 100644 --- a/lib/models/chart_model.dart +++ b/lib/models/chart_model.dart @@ -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( diff --git a/lib/models/tank_guru_directus_model.dart b/lib/models/tank_guru_directus_model.dart new file mode 100644 index 0000000..ffe720b --- /dev/null +++ b/lib/models/tank_guru_directus_model.dart @@ -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 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 toJson() { + final Map data = {}; + 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; + } +} diff --git a/lib/models/tank_user_model.dart b/lib/models/tank_user_model.dart new file mode 100644 index 0000000..4562c8e --- /dev/null +++ b/lib/models/tank_user_model.dart @@ -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 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 toJson() { + final Map data = {}; + 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; + } +} diff --git a/lib/pages/detail_view.dart b/lib/pages/detail_view.dart index c3d19cb..e784d61 100644 --- a/lib/pages/detail_view.dart +++ b/lib/pages/detail_view.dart @@ -54,8 +54,8 @@ class DetailPage extends GetView { 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 { 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 { 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 { 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 { 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 { DetailStatWidget( icon: Icons.speed, label: 'Kilometerstand', - value: '${detailCtrl.tank.szOdometer} km', + value: '${detailCtrl.tank.odometer!} km', iconColor: Colors.blue, valueSize: 24, valueWeight: FontWeight.bold, diff --git a/lib/pages/home_view.dart b/lib/pages/home_view.dart index 5a0d537..7e2b554 100644 --- a/lib/pages/home_view.dart +++ b/lib/pages/home_view.dart @@ -144,7 +144,7 @@ class HomePage extends GetView { CrossAxisAlignment.start, children: [ Text( - tank.szDate, + tank.tankDate!, style: const TextStyle( fontSize: 16, fontWeight: @@ -153,7 +153,7 @@ class HomePage extends GetView { ), 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 { _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', ), ], ), diff --git a/lib/pages/login_view.dart b/lib/pages/login_view.dart index f7962f4..db4114f 100644 --- a/lib/pages/login_view.dart +++ b/lib/pages/login_view.dart @@ -24,7 +24,7 @@ class LoginPage extends GetView { color: Colors.blue.shade100, child: SingleChildScrollView( child: MyLoginWidget( - onButtonPressed: () => logCtrl.login(), + onButtonPressed: () => logCtrl.newLoginDirectus(), signInOnTab: () => logCtrl.goToSignInPage(), logCtrl: logCtrl, ), diff --git a/lib/services/appwrite_service.dart b/lib/services/appwrite_service.dart.bak similarity index 100% rename from lib/services/appwrite_service.dart rename to lib/services/appwrite_service.dart.bak diff --git a/lib/services/directus_service.dart b/lib/services/directus_service.dart new file mode 100644 index 0000000..2ade9de --- /dev/null +++ b/lib/services/directus_service.dart @@ -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 get headers => { + 'Authorization': 'Bearer $apiTokenBearer', + 'Content-Type': 'application/json', + }; + + Future> 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; + //print('$json}'); + client.close(); + return json['data'] as List; + } + + Future> 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; + //print('$json}'); + client.close(); + return json['data'] as List; + } + + Future 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 createDocumentInTable(Map 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 updateDocumentInTable(int? iD, Map 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 register(String userName, String email, String password, String city, String country) async { + var client = http.Client(); + String apiUrl = '$endpoint/items/$tankUsersRest'; + Map 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; + } + } + +} diff --git a/lib/widgets/detail_header_widget.dart b/lib/widgets/detail_header_widget.dart index fa1ffb1..8a4777e 100644 --- a/lib/widgets/detail_header_widget.dart +++ b/lib/widgets/detail_header_widget.dart @@ -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, diff --git a/lib/widgets/my_signin_widget.dart b/lib/widgets/my_signin_widget.dart index 7143199..54da8a8 100644 --- a/lib/widgets/my_signin_widget.dart +++ b/lib/widgets/my_signin_widget.dart @@ -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( diff --git a/pubspec.lock b/pubspec.lock index 822578f..7307677 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.18" + version: "0.12.19" material_color_utilities: dependency: transitive description: @@ -372,10 +332,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.18.0" native_toolchain_c: dependency: transitive description: @@ -545,10 +505,10 @@ packages: dependency: transitive description: name: test_api - sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" url: "https://pub.dev" source: hosted - version: "0.7.9" + version: "0.7.11" 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: diff --git a/pubspec.yaml b/pubspec.yaml index 3015e6a..efea09f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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