ok for now
This commit is contained in:
@@ -1,10 +1,26 @@
|
||||
/// Thin wrapper around [SharedPreferences] for local key-value persistence.
|
||||
library;
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../models/transaction_meta.dart';
|
||||
import '../utils/constants.dart';
|
||||
|
||||
/// Persisted annotation store for transactions.
|
||||
class TxMetaStoreData {
|
||||
const TxMetaStoreData({
|
||||
this.byId = const {},
|
||||
this.categoryByDescription = const {},
|
||||
this.exactTemplates = const {},
|
||||
});
|
||||
|
||||
final Map<String, TxMeta> byId;
|
||||
final Map<String, TxCategory> categoryByDescription;
|
||||
final Map<String, TxExactTemplate> exactTemplates;
|
||||
}
|
||||
|
||||
/// Loads and stores user preferences. Call [init] once at startup.
|
||||
class StorageService {
|
||||
StorageService({this._preferences});
|
||||
@@ -35,4 +51,123 @@ class StorageService {
|
||||
Future<void> setUseRemoteData(bool value) {
|
||||
return _prefs.setBool(StorageKeys.useRemoteData, value);
|
||||
}
|
||||
|
||||
TxMetaStoreData loadTxMetaStore() {
|
||||
final raw = _prefs.getString(StorageKeys.txMeta);
|
||||
if (raw != null && raw.isNotEmpty) {
|
||||
return _decodeStore(raw);
|
||||
}
|
||||
|
||||
// Migrate flat v1 map → v2 store.
|
||||
final legacy = _prefs.getString(StorageKeys.txMetaLegacy);
|
||||
if (legacy == null || legacy.isEmpty) return const TxMetaStoreData();
|
||||
try {
|
||||
final decoded = jsonDecode(legacy);
|
||||
if (decoded is! Map) return const TxMetaStoreData();
|
||||
final byId = <String, TxMeta>{};
|
||||
final categoryByDescription = <String, TxCategory>{};
|
||||
decoded.forEach((key, value) {
|
||||
if (key is! String || value is! Map) return;
|
||||
final meta = TxMeta.fromJson(Map<String, dynamic>.from(value));
|
||||
byId[key] = meta;
|
||||
if (meta.hasCategory) {
|
||||
final desc = _descriptionFromLegacyId(key);
|
||||
if (desc != null) {
|
||||
categoryByDescription[desc] = meta.category;
|
||||
}
|
||||
}
|
||||
});
|
||||
return TxMetaStoreData(
|
||||
byId: byId,
|
||||
categoryByDescription: categoryByDescription,
|
||||
);
|
||||
} catch (_) {
|
||||
return const TxMetaStoreData();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveTxMetaStore(TxMetaStoreData store) {
|
||||
final encoded = {
|
||||
'byId': {
|
||||
for (final e in store.byId.entries) e.key: e.value.toJson(),
|
||||
},
|
||||
'categoryByDescription': {
|
||||
for (final e in store.categoryByDescription.entries)
|
||||
e.key: e.value.name,
|
||||
},
|
||||
'exactTemplates': {
|
||||
for (final e in store.exactTemplates.entries) e.key: e.value.toJson(),
|
||||
},
|
||||
};
|
||||
return _prefs.setString(StorageKeys.txMeta, jsonEncode(encoded));
|
||||
}
|
||||
|
||||
/// Deprecated shim used by older call sites during transition.
|
||||
Map<String, TxMeta> loadTxMeta() => loadTxMetaStore().byId;
|
||||
|
||||
Future<void> saveTxMeta(Map<String, TxMeta> meta) {
|
||||
final current = loadTxMetaStore();
|
||||
return saveTxMetaStore(
|
||||
TxMetaStoreData(
|
||||
byId: meta,
|
||||
categoryByDescription: current.categoryByDescription,
|
||||
exactTemplates: current.exactTemplates,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TxMetaStoreData _decodeStore(String raw) {
|
||||
try {
|
||||
final decoded = jsonDecode(raw);
|
||||
if (decoded is! Map) return const TxMetaStoreData();
|
||||
final root = Map<String, dynamic>.from(decoded);
|
||||
|
||||
final byId = <String, TxMeta>{};
|
||||
final byIdRaw = root['byId'];
|
||||
if (byIdRaw is Map) {
|
||||
byIdRaw.forEach((key, value) {
|
||||
if (key is String && value is Map) {
|
||||
byId[key] = TxMeta.fromJson(Map<String, dynamic>.from(value));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final categoryByDescription = <String, TxCategory>{};
|
||||
final cats = root['categoryByDescription'];
|
||||
if (cats is Map) {
|
||||
cats.forEach((key, value) {
|
||||
if (key is String && value is String) {
|
||||
categoryByDescription[key] = TxCategoryX.fromName(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final exactTemplates = <String, TxExactTemplate>{};
|
||||
final templates = root['exactTemplates'];
|
||||
if (templates is Map) {
|
||||
templates.forEach((key, value) {
|
||||
if (key is String && value is Map) {
|
||||
exactTemplates[key] =
|
||||
TxExactTemplate.fromJson(Map<String, dynamic>.from(value));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return TxMetaStoreData(
|
||||
byId: byId,
|
||||
categoryByDescription: categoryByDescription,
|
||||
exactTemplates: exactTemplates,
|
||||
);
|
||||
} catch (_) {
|
||||
return const TxMetaStoreData();
|
||||
}
|
||||
}
|
||||
|
||||
/// Legacy id format: `date|currency|amount|description`.
|
||||
String? _descriptionFromLegacyId(String id) {
|
||||
final parts = id.split('|');
|
||||
if (parts.length < 4) return null;
|
||||
final description = parts.sublist(3).join('|');
|
||||
return description.trim().toLowerCase().replaceAll(RegExp(r'\s+'), ' ');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user