import 'dart:convert'; import '../helper/storage_keys.dart'; import 'anwendung_model.dart'; import 'abfall_model.dart'; import 'bauteil_model.dart'; import 'kauf_filament_model.dart'; class BackupModel { static const keys = [ StorageKeys.filamente, StorageKeys.anwendungen, StorageKeys.bauteile, StorageKeys.materialien, StorageKeys.abfaelle, ]; final Map>> data; const BackupModel(this.data); String encode() => const JsonEncoder.withIndent(' ').convert({ 'format': 'ertl_filament_verwaltung', 'version': 1, 'exportiertAm': DateTime.now().toUtc().toIso8601String(), 'daten': data, }); factory BackupModel.decode(String source) { try { final json = jsonDecode(source.replaceFirst(RegExp(r'^\uFEFF'), '')) as Map; if (json['format'] != 'ertl_filament_verwaltung' || json['version'] != 1) { throw const FormatException(); } final raw = json['daten'] as Map; final data = >>{ for (final key in keys) key: ((key == StorageKeys.abfaelle && !raw.containsKey(key)) ? [] : raw[key] as List) .map((item) => Map.from(item as Map)) .toList(), }; final filamentIds = {}; for (final json in data[StorageKeys.filamente]!) { final item = KaufFilamentModel.fromJson(json); if (item.filamentId.isEmpty || !filamentIds.add(item.filamentId) || item.material.trim().isEmpty || !_positive(item.gewichtGramm) || !_nonNegative(item.preis) || (item.bruttoGewichtGramm != null && (!_positive(item.bruttoGewichtGramm) || item.bruttoGewichtGramm! < item.gewichtGramm!))) { throw const FormatException(); } } final profileIds = {}; for (final json in data[StorageKeys.anwendungen]!) { final item = AnwendungModel.fromJson(json); if (item.anwendungId.isEmpty || !profileIds.add(item.anwendungId) || item.filamentId.isEmpty || !_positive(item.druckTemperatur) || !_nonNegative(item.druckbettTemperatur) || (item.bauraumTemperatur != null && !_nonNegative(item.bauraumTemperatur!))) { throw const FormatException(); } } final partIds = {}; for (final json in data[StorageKeys.bauteile]!) { final item = BauteilModel.fromJson(json); if (item.bauteilId.isEmpty || !partIds.add(item.bauteilId) || item.name.trim().isEmpty || item.druckdauerMinuten <= 0 || !_positive(item.gewichtGramm) || !_nonNegative(item.abfallGramm) || (item.abfallKosten != null && !_nonNegative(item.abfallKosten!)) || (item.materialKosten != null && !_nonNegative(item.materialKosten!)) || (item.aufschlagProzent != null && !_nonNegative(item.aufschlagProzent!)) || (item.gesamtpreis != null && !_nonNegative(item.gesamtpreis!)) || item.filamente.isEmpty || item.filamente.any( (entry) => entry.filamentId.isEmpty || !_nonNegative(entry.verbrauchGramm), )) { throw const FormatException(); } if (item.fotoBase64 != null) base64Decode(item.fotoBase64!); } final wasteIds = {}; for (final json in data[StorageKeys.abfaelle]!) { final item = AbfallModel.fromJson(json); if (item.abfallId.isEmpty || !wasteIds.add(item.abfallId) || !_positive(item.gewichtGramm) || !_nonNegative(item.kosten)) { throw const FormatException(); } } final materials = data[StorageKeys.materialien]!; if (materials.isNotEmpty) { if (materials.length != 1) throw const FormatException(); final values = (materials.single['werte'] as List).cast(); if (values.any((value) => value.trim().isEmpty) || values.map((value) => value.toLowerCase()).toSet().length != values.length) { throw const FormatException(); } } return BackupModel(data); } catch (_) { throw const FormatException( 'Ungültige oder nicht unterstützte Datensicherung. Bitte eine vollständige JSON-Datei dieser App auswählen.', ); } } static bool _nonNegative(num value) => value.isFinite && value >= 0; static bool _positive(num? value) => value != null && value.isFinite && value > 0; }