import 'package:drift/drift.dart'; import 'package:immich_mobile/domain/models/config/app_config.dart'; import 'package:immich_mobile/domain/models/config/system_config.dart'; import 'package:immich_mobile/domain/models/metadata_key.dart'; import 'package:immich_mobile/infrastructure/entities/metadata.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; class MetadataRepository extends DriftDatabaseRepository { final Drift _db; final Map _cache = {}; MetadataRepository._(this._db) : super(_db); static MetadataRepository? _instance; static MetadataRepository get instance { final instance = _instance; if (instance == null) { throw StateError('MetadataRepository not initialized. Call ensureInitialized() first'); } return instance; } AppConfig _appConfig = const .new(); AppConfig get appConfig => _appConfig; SystemConfig _systemConfig = const .new(); SystemConfig get systemConfig => _systemConfig; static Future ensureInitialized(Drift db) async { if (_instance == null) { final instance = MetadataRepository._(db); await instance._hydrate(); _instance = instance; } return _instance!; } static Future refresh() async { instance._cache.clear(); instance._appConfig = const .new(); instance._systemConfig = const .new(); await instance._hydrate(); } Future _hydrate() async => _hydrateCache(await _db.select(_db.metadataEntity).get()); T _read(MetadataKey key) => (_cache[key] as T?) ?? key.defaultValue; Future write(MetadataKey key, U value) async { if (_read(key) == value) { return; } await _db .into(_db.metadataEntity) .insertOnConflictUpdate( MetadataEntityCompanion.insert(key: key.key, value: key.encode(value), updatedAt: Value(DateTime.now())), ); _updateCache(key, value); } Future delete(MetadataKey key) async { await (_db.delete(_db.metadataEntity)..where((t) => t.key.equals(key.key))).go(); _updateCache(key, key.defaultValue); } Stream watchAppConfig() => _watchDomain(.appConfig).distinct(); Stream watchSystemConfig() => _watchDomain(.systemConfig).distinct(); Stream _watchDomain(MetadataDomain domain) { final query = _db.select(_db.metadataEntity)..where((t) => t.key.like('${domain.prefix}.%')); return query.watch().map((rows) { _hydrateCache(rows); return domain.config(this); }); } void _hydrateCache(List rows) { final keyMap = MetadataKey.asKeyMap(); for (final row in rows) { final key = keyMap[row.key]; if (key == null) { continue; } _updateCache(key, key.decode(row.value)); } } void _updateCache(MetadataKey key, T value) { if (_cache[key] == value) { return; } _cache[key] = value; key.domain.rebuild(this); } } extension on MetadataDomain { T config(MetadataRepository repo) => switch (this) { .appConfig => repo._appConfig as T, .systemConfig => repo._systemConfig as T, }; void rebuild(MetadataRepository repo) { switch (this) { case .appConfig: repo._appConfig = .new( theme: .new( mode: repo._read(.themeMode), primaryColor: repo._read(.themePrimaryColor), dynamicTheme: repo._read(.themeDynamic), colorfulInterface: repo._read(.themeColorfulInterface), ), cleanup: .new( keepFavorites: repo._read(.cleanupKeepFavorites), keepMediaType: repo._read(.cleanupKeepMediaType), keepAlbumIds: repo._read(.cleanupKeepAlbumIds), cutoffDaysAgo: repo._read(.cleanupCutoffDaysAgo), defaultsInitialized: repo._read(.cleanupDefaultsInitialized), ), map: .new( relativeDays: repo._read(.mapRelativeDate), favoritesOnly: repo._read(.mapShowFavoriteOnly), includeArchived: repo._read(.mapIncludeArchived), themeMode: repo._read(.mapThemeMode), withPartners: repo._read(.mapWithPartners), ), timeline: .new( tilesPerRow: repo._read(.timelineTilesPerRow), groupAssetsBy: repo._read(.timelineGroupAssetsBy), storageIndicator: repo._read(.timelineStorageIndicator), ), image: .new(preferRemote: repo._read(.imagePreferRemote), loadOriginal: repo._read(.imageLoadOriginal)), viewer: .new( loopVideo: repo._read(.viewerLoopVideo), loadOriginalVideo: repo._read(.viewerLoadOriginalVideo), autoPlayVideo: repo._read(.viewerAutoPlayVideo), tapToNavigate: repo._read(.viewerTapToNavigate), ), slideshow: .new( transition: repo._read(.slideshowTransition), repeat: repo._read(.slideshowRepeat), duration: repo._read(.slideshowDuration), look: repo._read(.slideshowLook), direction: repo._read(.slideshowDirection), ), ); case .systemConfig: repo._systemConfig = .new(logLevel: repo._read(.logLevel)); } } }