added categories, import export, settings

This commit is contained in:
davmar
2026-08-29 16:45:27 +02:00
parent 2a5a922aab
commit 732a0dc14a
30 changed files with 3700 additions and 167 deletions
+99
View File
@@ -0,0 +1,99 @@
/// JSON backup document: products, trips, settings, and embedded photos.
library;
import 'dart:convert';
const int kBackupVersion = 1;
class BackupDocument {
const BackupDocument({
required this.version,
required this.exportedAt,
required this.darkMode,
required this.supermarkets,
required this.products,
required this.shops,
required this.shopItems,
required this.images,
});
final int version;
final String exportedAt;
final bool darkMode;
final List<Map<String, Object?>> supermarkets;
final List<Map<String, Object?>> products;
final List<Map<String, Object?>> shops;
final List<Map<String, Object?>> shopItems;
final Map<String, String> images;
Map<String, Object?> toJson() => {
'format': 'receipity-backup',
'version': version,
'exportedAt': exportedAt,
'darkMode': darkMode,
'supermarkets': supermarkets,
'products': products,
'shops': shops,
'shopItems': shopItems,
'images': images,
};
String encode() => const JsonEncoder.withIndent(' ').convert(toJson());
static BackupDocument parse(String json) {
final decoded = jsonDecode(json);
if (decoded is! Map<String, dynamic>) {
throw const FormatException('Backup is not a JSON object.');
}
if (decoded['format'] != 'receipity-backup') {
throw const FormatException('This file is not a Receipity backup.');
}
final version = decoded['version'];
if (version is! int || version < 1 || version > kBackupVersion) {
throw const FormatException('Unsupported backup version.');
}
return BackupDocument(
version: version,
exportedAt: decoded['exportedAt'] as String? ?? '',
darkMode: decoded['darkMode'] == true,
supermarkets: _maps(decoded['supermarkets']),
products: _maps(decoded['products']),
shops: _maps(decoded['shops']),
shopItems: _maps(decoded['shopItems']),
images: _strings(decoded['images']),
);
}
static List<Map<String, Object?>> _maps(Object? value) {
if (value == null) return [];
if (value is! List) {
throw const FormatException('Backup tables must be lists.');
}
return [
for (final item in value)
if (item is Map)
Map<String, Object?>.from(
item.map((key, val) => MapEntry(key.toString(), val)),
),
];
}
static Map<String, String> _strings(Object? value) {
if (value == null) return {};
if (value is! Map) return {};
return {
for (final entry in value.entries)
if (entry.value is String) entry.key.toString(): entry.value as String,
};
}
}
String? backupImageKey(String? absolutePath) {
if (absolutePath == null || absolutePath.isEmpty) return null;
final normalized = absolutePath.replaceAll('\\', '/');
final products = normalized.split('/products/');
if (products.length == 2) return 'products/${products.last}';
final receipts = normalized.split('/receipts/');
if (receipts.length == 2) return 'receipts/${receipts.last}';
return null;
}