import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:uuid/uuid.dart'; import '../models/kauf_filament_model.dart'; import '../models/bauteil_model.dart'; import '../models/anwendung_model.dart'; import '../helper/storage_keys.dart'; import '../helper/filament_bestand.dart'; import '../helper/display_formats.dart'; import '../helper/input_validators.dart'; import '../services/get_storage_services.dart'; class HomeController extends GetxController { static const storageKey = StorageKeys.filamente; final storage = Get.find(); final uuid = const Uuid(); final filamente = [].obs; final verbrauch = {}.obs; final filamentIdsMitProfil = {}.obs; void loadProfile() { filamentIdsMitProfil.assignAll( storage .getModelList(StorageKeys.anwendungen) .map(AnwendungModel.fromJson) .map((profile) => profile.filamentId), ); } void loadVerbrauch() { verbrauch.assignAll( FilamentBestand.verbrauch( storage.getModelList(StorageKeys.bauteile).map(BauteilModel.fromJson), ), ); } double? restgewicht(KaufFilamentModel filament) { final gewicht = filament.gewichtGramm; return gewicht == null ? null : gewicht - (verbrauch[filament.filamentId] ?? 0); } final formKey = GlobalKey(); final pageScrollController = ScrollController(); final formScrollController = ScrollController(); final haendlerController = TextEditingController(); final farbeController = TextEditingController(); final preisController = TextEditingController(); final markeController = TextEditingController(); final gewichtController = TextEditingController(); final bruttoGewichtController = TextEditingController(); final materialController = TextEditingController(text: 'PLA'); final kaufdatum = DateTime.now().obs; final material = 'PLA'.obs; final editingId = RxnString(); static const defaultMaterialien = [ 'PLA', 'PLA+', 'PLA light', 'PLA-CF', 'PETG', 'PETG-CF', 'ABS', 'ASA', 'TPU', 'Sonstiges', ]; final materialien = defaultMaterialien.toList().obs; final materialMessage = RxnString(); final isSavingMaterial = false.obs; @override void onInit() { super.onInit(); loadFilamente(); loadMaterialien(); selectMaterial(materialien.firstOrNull ?? ''); } void loadMaterialien() { final saved = storage.getModelList(StorageKeys.materialien); if (saved.isNotEmpty) { materialien.assignAll((saved.single['werte'] as List).cast()); } else { materialien.assignAll({ ...defaultMaterialien, ...filamente.map((filament) => filament.material), }); } } void selectMaterial(String value) { material.value = value; materialController.text = value; } Future addMaterial() async { final value = materialController.text.trim(); if (value.isEmpty) { materialMessage.value = 'Bitte eine Materialart eingeben.'; return false; } final existing = materialien.firstWhereOrNull( (item) => item.toLowerCase() == value.toLowerCase(), ); if (existing != null) { materialMessage.value = null; selectMaterial(existing); return true; } final saved = await _saveMaterialien([...materialien, value]); if (saved) selectMaterial(value); return saved; } Future removeMaterial(String value) => _saveMaterialien(materialien.where((item) => item != value).toList()); Future _saveMaterialien(List values) async { if (isSavingMaterial.value) return false; isSavingMaterial.value = true; materialMessage.value = null; try { await storage.saveModelList(StorageKeys.materialien, [ {'werte': values}, ]); materialien.assignAll(values); return true; } catch (_) { materialMessage.value = 'Artenliste konnte nicht gespeichert werden. Bitte erneut versuchen.'; return false; } finally { isSavingMaterial.value = false; } } void loadFilamente() { loadVerbrauch(); loadProfile(); final data = storage.getModelList(storageKey); filamente.assignAll(data.map(KaufFilamentModel.fromJson)); sortFilamente(); } void sortFilamente() { filamente.sort((a, b) => b.kaufdatum.compareTo(a.kaufdatum)); } Future persist() async { await storage.saveModelList( storageKey, filamente.map((f) => f.toJson()).toList(), ); } String? validateBruttogewicht(String? value) { if ((value ?? '').trim().isEmpty) return null; final error = InputValidators.positive(value); if (error != null) return error; final brutto = InputValidators.number(value)!; final netto = InputValidators.number(gewichtController.text); if (netto != null && netto.isFinite && brutto < netto) { return 'Bruttogewicht muss mindestens dem Nettogewicht entsprechen.'; } return null; } Future saveFilament() async { if (!formKey.currentState!.validate()) return; final preis = double.tryParse( preisController.text.trim().replaceAll(',', '.'), ); if (preis == null || !preis.isFinite || preis < 0) { Get.snackbar('Ungültiger Preis', 'Bitte Preis überprüfen.'); return; } loadVerbrauch(); final gewicht = InputValidators.number(gewichtController.text); final bereitsVerbraucht = editingId.value == null ? 0 : (verbrauch[editingId.value!] ?? 0); if (gewicht == null || !gewicht.isFinite || gewicht <= 0 || gewicht < bereitsVerbraucht) { Get.snackbar( 'Ungültiges Gewicht', 'Das Nettostartgewicht muss positiv sein und mindestens dem bereits gebuchten Verbrauch entsprechen.', ); return; } if (!await addMaterial()) return; material.value = materialController.text.trim(); final filament = KaufFilamentModel( filamentId: editingId.value ?? uuid.v4(), marke: markeController.text.trim(), kaufdatum: kaufdatum.value, haendler: haendlerController.text.trim(), material: material.value, preis: preis, farbe: farbeController.text.trim(), gewicht: gewicht.toString(), bruttoGewichtGramm: InputValidators.number(bruttoGewichtController.text), ); if (editingId.value == null) { filamente.add(filament); } else { final index = filamente.indexWhere( (f) => f.filamentId == editingId.value, ); if (index < 0) { Get.snackbar('Fehler', 'Datensatz nicht gefunden.'); return; } filamente[index] = filament; } sortFilamente(); await persist(); resetForm(); } Future editFilament(KaufFilamentModel filament) async { editingId.value = filament.filamentId; kaufdatum.value = filament.kaufdatum; markeController.text = filament.marke; haendlerController.text = filament.haendler; selectMaterial(filament.material); preisController.text = filament.preis.toStringAsFixed(2); bruttoGewichtController.text = filament.bruttoGewichtGramm?.toString().replaceAll('.', ',') ?? ''; farbeController.text = filament.farbe; gewichtController.text = filament.gewichtGramm == null ? filament.gewicht : DisplayFormats.number(filament.gewichtGramm!); final scroll = formScrollController.hasClients ? formScrollController : pageScrollController; if (scroll.hasClients) { await scroll.animateTo( 0, duration: const Duration(milliseconds: 300), curve: Curves.easeOutCubic, ); } } Future duplicateFilament(KaufFilamentModel filament) async { filamente.add(filament.copyWith(filamentId: uuid.v4())); sortFilamente(); await persist(); } Future deleteFilament(String filamentId) async { filamente.removeWhere((f) => f.filamentId == filamentId); if (editingId.value == filamentId) resetForm(); await persist(); } void resetForm() { formKey.currentState?.reset(); editingId.value = null; haendlerController.clear(); markeController.clear(); farbeController.clear(); preisController.clear(); kaufdatum.value = DateTime.now(); selectMaterial(materialien.firstOrNull ?? ''); materialMessage.value = null; gewichtController.clear(); bruttoGewichtController.clear(); } @override void onClose() { pageScrollController.dispose(); formScrollController.dispose(); haendlerController.dispose(); markeController.dispose(); farbeController.dispose(); preisController.dispose(); gewichtController.dispose(); bruttoGewichtController.dispose(); materialController.dispose(); super.onClose(); } }