Umbau auf directus API Rest Service

This commit is contained in:
2026-09-25 16:44:42 +02:00
parent 7ddcdf71bc
commit 84724ef10f
22 changed files with 570 additions and 274 deletions
+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();
}
}
+42 -11
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',
+3 -1
View File
@@ -11,4 +11,6 @@ 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(