import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:immich_mobile/constants/colors.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/log.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; import 'package:immich_mobile/utils/semver.dart'; enum SettingsKey { // Theme themePrimaryColor(codec: _EnumCodec(ImmichColorPreset.values)), themeMode(codec: _EnumCodec(ThemeMode.values)), themeDynamic(), themeColorfulInterface(), // Image imagePreferRemote(), imageLoadOriginal(), // Viewer viewerLoopVideo(), viewerLoadOriginalVideo(), viewerAutoPlayVideo(), viewerTapToNavigate(), // Network networkAutoEndpointSwitching(), networkPreferredWifiName(), networkLocalEndpoint(), networkExternalEndpointList>(codec: _ListCodec(_PrimitiveCodec.string)), networkCustomHeaders>(codec: _MapCodec(_PrimitiveCodec.string, _PrimitiveCodec.string)), // Album albumSortMode(codec: _EnumCodec(AlbumSortMode.values)), albumIsReverse(), albumIsGrid(), // Backup backupEnabled(), backupUseCellularForVideos(), backupUseCellularForPhotos(), backupRequireCharging(), backupTriggerDelay(), backupSyncAlbums(), // Timeline timelineTilesPerRow(), timelineGroupAssetsBy(codec: _EnumCodec(GroupAssetsBy.values)), timelineStorageIndicator(), // Log logLevel(codec: _EnumCodec(LogLevel.values)), // Map mapShowFavoriteOnly(), mapRelativeDate(), mapIncludeArchived(), mapThemeMode(codec: _EnumCodec(ThemeMode.values)), mapWithPartners(), // Cleanup cleanupKeepFavorites(), cleanupKeepMediaType(codec: _EnumCodec(AssetKeepType.values)), cleanupKeepAlbumIds>(codec: _ListCodec(_PrimitiveCodec.string)), cleanupCutoffDaysAgo(), cleanupDefaultsInitialized(), // Share shareFileType(codec: _EnumCodec(ShareAssetType.values)), // Slideshow slideshowRepeat(), slideshowDuration(), slideshowLook(codec: _EnumCodec(SlideshowLook.values)), slideshowDirection(codec: _EnumCodec(SlideshowDirection.values)), // Feature message featureMessageSeenRelease(codec: _SemVerCodec()); final _SettingsCodec? _codecOverride; const SettingsKey({_SettingsCodec? codec}) : _codecOverride = codec; _SettingsCodec get _codec => _codecOverride ?? _SettingsCodec.forType(T); String encode(T value) => _codec.encode(value); T decode(String raw) => _codec.decode(raw); } sealed class _SettingsCodec { const _SettingsCodec(); String encode(T value); T decode(String raw); static final Map> _primitives = { ..._register(_PrimitiveCodec.integer), ..._register(_PrimitiveCodec.real), ..._register(_PrimitiveCodec.boolean), ..._register(_PrimitiveCodec.string), ..._register(const _DateTimeCodec()), }; static Map> _register(_SettingsCodec codec) => { T: codec, // Reifies the nullable type T so it can be used as a key in the _primitives map _typeOf(): codec, }; static Type _typeOf() => T; static _SettingsCodec forType(Type runtimeType) { final codec = _primitives[runtimeType]; if (codec == null) { throw StateError('No primitive codec for $runtimeType. Provide an explicit codec when defining the SettingsKey.'); } return codec as _SettingsCodec; } } final class _EnumCodec extends _SettingsCodec { final List values; const _EnumCodec(this.values); @override String encode(T value) => value.name; @override T decode(String raw) => values.firstWhere((v) => v.name == raw); } final class _DateTimeCodec extends _SettingsCodec { const _DateTimeCodec(); @override String encode(DateTime value) => value.toIso8601String(); @override DateTime decode(String raw) => DateTime.parse(raw); } final class _SemVerCodec extends _SettingsCodec { const _SemVerCodec(); @override String encode(SemVer value) => value.toString(); @override SemVer decode(String raw) => SemVer.fromString(raw); } final class _MapCodec extends _SettingsCodec> { final _SettingsCodec _keyCodec; final _SettingsCodec _valueCodec; const _MapCodec(this._keyCodec, this._valueCodec); @override String encode(Map value) { final entries = {}; value.forEach((k, v) => entries[_keyCodec.encode(k)] = _valueCodec.encode(v)); return jsonEncode(entries); } @override Map decode(String raw) { try { final decoded = jsonDecode(raw); if (decoded is! Map) { return {}; } final result = {}; for (final entry in decoded.entries) { final rawKey = entry.key; final rawValue = entry.value; if (rawKey is! String || rawValue is! String) { return {}; } final k = _keyCodec.decode(rawKey); final v = _valueCodec.decode(rawValue); result[k] = v; } return result; } on FormatException { return {}; } } } final class _ListCodec extends _SettingsCodec> { final _SettingsCodec _elementCodec; const _ListCodec(this._elementCodec); @override String encode(List value) => jsonEncode(value.map(_elementCodec.encode).toList()); @override List decode(String raw) { try { final decoded = jsonDecode(raw); if (decoded is! List) { return []; } final result = []; for (final item in decoded) { if (item is! String) { return []; } final element = _elementCodec.decode(item); result.add(element); } return result; } on FormatException { return []; } } } final class _PrimitiveCodec extends _SettingsCodec { final T Function(String) _parse; const _PrimitiveCodec._(this._parse); @override String encode(T value) => value.toString(); @override T decode(String raw) => _parse(raw); static const integer = _PrimitiveCodec._(int.parse); static const real = _PrimitiveCodec._(double.parse); static const boolean = _PrimitiveCodec._(bool.parse); static const string = _PrimitiveCodec._(_identity); static String _identity(String s) => s; }