fertig bis auf Tankstellen und Graph

This commit is contained in:
2026-01-23 15:03:18 +01:00
parent 5f4f2c4379
commit d5b8df9506
27 changed files with 2198 additions and 17 deletions

View File

@@ -1,12 +1,36 @@
import 'package:flutter_tank_web_app/services/appwrite_service.dart';
import 'package:get/get.dart';
import '../models/tank_model.dart';
import '../pages/edit_view.dart';
class DetailController extends GetxController {
late TankModel tank;
final appwriteService = AppwriteService();
@override
void onInit() {
tank = Get.arguments as TankModel;
super.onInit();
}
void deleteEntry() {
appwriteService
.deleteDocumentFromCollection(tank.szDocumentId)
.then((_) {
Get.back(
result: 'deleted',
); // Zurück zur vorherigen Seite nach dem Löschen
})
.catchError((error) {
Get.snackbar(
'Fehler',
'Eintrag konnte nicht gelöscht werden: $error',
snackPosition: SnackPosition.BOTTOM,
);
});
}
Future<void> editEntry() async {
await Get.offAllNamed(EditPage.namedRoute, arguments: tank);
}
}

View File

@@ -0,0 +1,247 @@
import 'package:flutter/material.dart';
import 'package:flutter_tank_web_app/pages/home_view.dart';
import 'package:get/get.dart';
import 'package:geolocator/geolocator.dart';
import '../models/tank_model.dart';
import '../services/appwrite_service.dart';
class EditController extends GetxController {
final AppwriteService appwriteService = AppwriteService();
// Form controllers
final dateController = TextEditingController();
final odometerController = TextEditingController();
final litersController = TextEditingController();
final pricePerLiterController = TextEditingController();
final locationController = TextEditingController();
// Observable states
final isLoading = false.obs;
final isNewEntry = true.obs;
final calculatedTotal = '0.00'.obs;
final isLoadingLocation = false.obs;
TankModel? editingTankModel;
@override
void onInit() {
super.onInit();
// Check if we're editing or creating new
if (Get.arguments != null) {
editingTankModel = Get.arguments as TankModel;
isNewEntry.value = false;
_loadExistingData();
} else {
isNewEntry.value = true;
_setDefaultDate();
_requestLocation();
}
// Add listeners for automatic calculation
litersController.addListener(_calculateTotal);
pricePerLiterController.addListener(_calculateTotal);
}
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;
}
void _setDefaultDate() {
final now = DateTime.now();
dateController.text =
'${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}';
}
Future<void> _requestLocation() async {
bool serviceEnabled;
LocationPermission permission;
try {
isLoadingLocation.value = true;
// 1. Prüfen, ob Standortdienste aktiviert sind
serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
return Future.error('Standortdienste sind deaktiviert.');
}
// 2. Berechtigungen prüfen
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
return Future.error('Berechtigung verweigert.');
}
}
// 3. Position abrufen
Position position = await Geolocator.getCurrentPosition(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.high,
),
);
// 4. Standort über Backend-Proxy abrufen
var lat = position.latitude;
var lon = position.longitude;
print('📍 Verwende Backend-Proxy für Geocoding...');
String location = await appwriteService.geocodeLocation(lat, lon);
locationController.text = location;
// Info anzeigen basierend auf Ergebnis
if (location.startsWith('Lat:')) {
Get.snackbar(
'Hinweis',
'Adresse konnte nicht abgerufen werden. Koordinaten gespeichert.',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.orange[100],
colorText: Colors.orange[900],
duration: const Duration(seconds: 3),
);
} else {
Get.snackbar(
'Erfolg',
'Standort: $location',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.green[100],
colorText: Colors.green[900],
duration: const Duration(seconds: 2),
);
}
} catch (e) {
Get.snackbar(
"Fehler",
"Standort konnte nicht abgerufen werden: $e",
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red[100],
colorText: Colors.red[900],
);
print("Fehler beim Abrufen des Standorts: $e");
locationController.text = '';
} finally {
isLoadingLocation.value = false;
}
}
void _calculateTotal() {
final liters = double.tryParse(litersController.text) ?? 0.0;
final pricePerLiter = double.tryParse(pricePerLiterController.text) ?? 0.0;
calculatedTotal.value = (liters * pricePerLiter).toStringAsFixed(2);
}
Future<void> selectDate(BuildContext context) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2020),
lastDate: DateTime.now(),
builder: (context, child) {
return Theme(
data: ThemeData.light().copyWith(
colorScheme: ColorScheme.light(
primary: Colors.blueGrey[700]!,
onPrimary: Colors.white,
),
),
child: child!,
);
},
);
if (picked != null) {
dateController.text =
'${picked.year}-${picked.month.toString().padLeft(2, '0')}-${picked.day.toString().padLeft(2, '0')}';
}
}
Future<void> saveTankEntry() async {
if (!_validateForm()) {
Get.snackbar(
'Validierungsfehler',
'Bitte füllen Sie alle Pflichtfelder aus',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red[100],
colorText: Colors.red[900],
);
return;
}
isLoading.value = true;
try {
final userId = await appwriteService.getCurrentUserId();
if (userId == null) {
throw Exception('Benutzer nicht eingeloggt');
}
final data = {
'userId': userId,
'date': dateController.text,
'odometer': odometerController.text,
'liters': litersController.text,
'pricePerLiter': pricePerLiterController.text,
'location': locationController.text,
};
bool success;
if (isNewEntry.value) {
success = await appwriteService.createDocumentInCollection(data);
} else {
success = await appwriteService.updateDocumentInCollection(
editingTankModel!.szDocumentId,
data,
);
}
if (success) {
Get.snackbar(
'Erfolgreich',
isNewEntry.value
? 'Tankeintrag erstellt'
: 'Tankeintrag aktualisiert',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.green[100],
colorText: Colors.green[900],
);
Get.offAllNamed(HomePage.namedRoute);
} else {
throw Exception('Speichern fehlgeschlagen');
}
} catch (e) {
Get.snackbar(
'Fehler',
'Beim Speichern ist ein Fehler aufgetreten: $e',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red[100],
colorText: Colors.red[900],
);
} finally {
isLoading.value = false;
}
}
bool _validateForm() {
return dateController.text.isNotEmpty &&
odometerController.text.isNotEmpty &&
litersController.text.isNotEmpty &&
pricePerLiterController.text.isNotEmpty;
}
@override
void onClose() {
dateController.dispose();
odometerController.dispose();
litersController.dispose();
pricePerLiterController.dispose();
locationController.dispose();
super.onClose();
}
}

View File

@@ -1,8 +1,8 @@
import 'package:get/get.dart';
import '../models/tank_model.dart';
import '../pages/detail_view.dart';
import '../pages/edit_view.dart';
import '../services/appwrite_service.dart';
class HomeController extends GetxController {
@@ -24,7 +24,7 @@ class HomeController extends GetxController {
Future<void> _loadListDocument() async {
isLoading.value = true;
if(listTankModel.isNotEmpty){
if (listTankModel.isNotEmpty) {
listTankModel.clear();
}
var dateYear = DateTime.now().year;
@@ -84,7 +84,14 @@ class HomeController extends GetxController {
}
}
void viewTankDetails(TankModel tank) {
Get.toNamed(DetailPage.namedRoute, arguments: tank);
Future<void> viewTankDetails(TankModel tank) async {
var result = await Get.toNamed(DetailPage.namedRoute, arguments: tank);
if (result == 'deleted') {
_loadListDocument();
}
}
Future<void> navigateToAddTankEntry() async {
await Get.offAllNamed(EditPage.namedRoute);
}
}