Compare commits

...

1 Commits

Author SHA1 Message Date
shenlong-tanwen 541f4987c7 refactor: cleanup metadata 2026-05-19 01:40:24 +05:30
34 changed files with 304 additions and 237 deletions
@@ -1,3 +1,4 @@
import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
class AlbumConfig {
@@ -5,7 +6,12 @@ class AlbumConfig {
final bool isReverse;
final bool isGrid;
const AlbumConfig({this.sortMode = AlbumSortMode.mostRecent, this.isReverse = true, this.isGrid = false});
const AlbumConfig({required this.sortMode, required this.isReverse, required this.isGrid});
AlbumConfig.defaults()
: sortMode = MetadataKey.albumSortMode.defaultValue,
isReverse = MetadataKey.albumIsReverse.defaultValue,
isGrid = MetadataKey.albumIsGrid.defaultValue;
AlbumConfig copyWith({AlbumSortMode? sortMode, bool? isReverse, bool? isGrid}) => AlbumConfig(
sortMode: sortMode ?? this.sortMode,
@@ -20,17 +20,28 @@ class AppConfig {
final BackupConfig backup;
const AppConfig({
this.theme = const .new(),
this.cleanup = const .new(),
this.map = const .new(),
this.timeline = const .new(),
this.image = const .new(),
this.viewer = const .new(),
this.slideshow = const .new(),
this.album = const .new(),
this.backup = const .new(),
required this.theme,
required this.cleanup,
required this.map,
required this.timeline,
required this.image,
required this.viewer,
required this.slideshow,
required this.album,
required this.backup,
});
AppConfig.defaults()
: theme = .defaults(),
cleanup = .defaults(),
map = .defaults(),
timeline = .defaults(),
image = .defaults(),
viewer = .defaults(),
slideshow = .defaults(),
album = .defaults(),
backup = .defaults();
AppConfig copyWith({
ThemeConfig? theme,
CleanupConfig? cleanup,
@@ -1,3 +1,5 @@
import 'package:immich_mobile/domain/models/metadata_key.dart';
class BackupConfig {
final bool enabled;
final bool useCellularForVideos;
@@ -7,14 +9,22 @@ class BackupConfig {
final bool syncAlbums;
const BackupConfig({
this.enabled = false,
this.useCellularForVideos = false,
this.useCellularForPhotos = false,
this.requireCharging = false,
this.triggerDelay = 30,
this.syncAlbums = false,
required this.enabled,
required this.useCellularForVideos,
required this.useCellularForPhotos,
required this.requireCharging,
required this.triggerDelay,
required this.syncAlbums,
});
BackupConfig.defaults()
: enabled = MetadataKey.backupEnabled.defaultValue,
useCellularForVideos = MetadataKey.backupUseCellularForVideos.defaultValue,
useCellularForPhotos = MetadataKey.backupUseCellularForPhotos.defaultValue,
requireCharging = MetadataKey.backupRequireCharging.defaultValue,
triggerDelay = MetadataKey.backupTriggerDelay.defaultValue,
syncAlbums = MetadataKey.backupSyncAlbums.defaultValue;
BackupConfig copyWith({
bool? enabled,
bool? useCellularForVideos,
@@ -1,4 +1,5 @@
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
class CleanupConfig {
final bool keepFavorites;
@@ -8,13 +9,20 @@ class CleanupConfig {
final bool defaultsInitialized;
const CleanupConfig({
this.keepFavorites = true,
this.keepMediaType = AssetKeepType.none,
this.keepAlbumIds = const [],
this.cutoffDaysAgo = -1,
this.defaultsInitialized = false,
required this.keepFavorites,
required this.keepMediaType,
required this.keepAlbumIds,
required this.cutoffDaysAgo,
required this.defaultsInitialized,
});
CleanupConfig.defaults()
: keepFavorites = MetadataKey.cleanupKeepFavorites.defaultValue,
keepMediaType = MetadataKey.cleanupKeepMediaType.defaultValue,
keepAlbumIds = MetadataKey.cleanupKeepAlbumIds.defaultValue,
cutoffDaysAgo = MetadataKey.cleanupCutoffDaysAgo.defaultValue,
defaultsInitialized = MetadataKey.cleanupDefaultsInitialized.defaultValue;
CleanupConfig copyWith({
bool? keepFavorites,
AssetKeepType? keepMediaType,
@@ -1,8 +1,14 @@
import 'package:immich_mobile/domain/models/metadata_key.dart';
class ImageConfig {
final bool preferRemote;
final bool loadOriginal;
const ImageConfig({this.preferRemote = false, this.loadOriginal = false});
const ImageConfig({required this.preferRemote, required this.loadOriginal});
ImageConfig.defaults()
: preferRemote = MetadataKey.imagePreferRemote.defaultValue,
loadOriginal = MetadataKey.imageLoadOriginal.defaultValue;
ImageConfig copyWith({bool? preferRemote, bool? loadOriginal}) =>
ImageConfig(preferRemote: preferRemote ?? this.preferRemote, loadOriginal: loadOriginal ?? this.loadOriginal);
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
class MapConfig {
final int relativeDays;
@@ -8,13 +9,20 @@ class MapConfig {
final bool withPartners;
const MapConfig({
this.relativeDays = 0,
this.favoritesOnly = false,
this.includeArchived = false,
this.themeMode = ThemeMode.system,
this.withPartners = false,
required this.relativeDays,
required this.favoritesOnly,
required this.includeArchived,
required this.themeMode,
required this.withPartners,
});
MapConfig.defaults()
: relativeDays = MetadataKey.mapRelativeDate.defaultValue,
favoritesOnly = MetadataKey.mapShowFavoriteOnly.defaultValue,
includeArchived = MetadataKey.mapIncludeArchived.defaultValue,
themeMode = MetadataKey.mapThemeMode.defaultValue,
withPartners = MetadataKey.mapWithPartners.defaultValue;
MapConfig copyWith({
int? relativeDays,
bool? favoritesOnly,
@@ -1,4 +1,5 @@
import 'package:flutter/foundation.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
class NetworkConfig {
final bool autoEndpointSwitching;
@@ -8,13 +9,20 @@ class NetworkConfig {
final Map<String, String> customHeaders;
const NetworkConfig({
this.autoEndpointSwitching = false,
required this.autoEndpointSwitching,
this.preferredWifiName,
this.localEndpoint,
this.externalEndpointList = const [],
this.customHeaders = const {},
required this.externalEndpointList,
required this.customHeaders,
});
NetworkConfig.defaults()
: autoEndpointSwitching = MetadataKey.networkAutoEndpointSwitching.defaultValue,
preferredWifiName = MetadataKey.networkPreferredWifiName.defaultValue,
localEndpoint = MetadataKey.networkLocalEndpoint.defaultValue,
externalEndpointList = MetadataKey.networkExternalEndpointList.defaultValue,
customHeaders = MetadataKey.networkCustomHeaders.defaultValue;
NetworkConfig copyWith({
bool? autoEndpointSwitching,
String? preferredWifiName,
@@ -1,4 +1,5 @@
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
class SlideshowConfig {
final bool transition;
@@ -8,13 +9,20 @@ class SlideshowConfig {
final SlideshowDirection direction;
const SlideshowConfig({
this.transition = true,
this.repeat = true,
this.duration = 5,
this.look = SlideshowLook.contain,
this.direction = SlideshowDirection.forward,
required this.transition,
required this.repeat,
required this.duration,
required this.look,
required this.direction,
});
SlideshowConfig.defaults()
: transition = MetadataKey.slideshowTransition.defaultValue,
repeat = MetadataKey.slideshowRepeat.defaultValue,
duration = MetadataKey.slideshowDuration.defaultValue,
look = MetadataKey.slideshowLook.defaultValue,
direction = MetadataKey.slideshowDirection.defaultValue;
SlideshowConfig copyWith({
bool? transition,
bool? repeat,
@@ -1,11 +1,14 @@
import 'package:immich_mobile/domain/models/config/network_config.dart';
import 'package:immich_mobile/domain/models/log.model.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
class SystemConfig {
final LogLevel logLevel;
final NetworkConfig network;
const SystemConfig({this.logLevel = .info, this.network = const .new()});
const SystemConfig({required this.logLevel, required this.network});
SystemConfig.defaults() : logLevel = MetadataKey.logLevel.defaultValue, network = NetworkConfig.defaults();
SystemConfig copyWith({LogLevel? logLevel, NetworkConfig? network}) =>
SystemConfig(logLevel: logLevel ?? this.logLevel, network: network ?? this.network);
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/constants/colors.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
class ThemeConfig {
final ThemeMode mode;
@@ -8,12 +9,18 @@ class ThemeConfig {
final bool colorfulInterface;
const ThemeConfig({
this.mode = .system,
this.primaryColor = .indigo,
this.dynamicTheme = false,
this.colorfulInterface = true,
required this.mode,
required this.primaryColor,
required this.dynamicTheme,
required this.colorfulInterface,
});
ThemeConfig.defaults()
: mode = MetadataKey.themeMode.defaultValue,
primaryColor = MetadataKey.themePrimaryColor.defaultValue,
dynamicTheme = MetadataKey.themeDynamic.defaultValue,
colorfulInterface = MetadataKey.themeColorfulInterface.defaultValue;
ThemeConfig copyWith({
ThemeMode? mode,
ImmichColorPreset? primaryColor,
@@ -1,3 +1,4 @@
import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
class TimelineConfig {
@@ -5,7 +6,12 @@ class TimelineConfig {
final GroupAssetsBy groupAssetsBy;
final bool storageIndicator;
const TimelineConfig({this.tilesPerRow = 4, this.groupAssetsBy = GroupAssetsBy.day, this.storageIndicator = true});
const TimelineConfig({required this.tilesPerRow, required this.groupAssetsBy, required this.storageIndicator});
TimelineConfig.defaults()
: tilesPerRow = MetadataKey.timelineTilesPerRow.defaultValue,
groupAssetsBy = MetadataKey.timelineGroupAssetsBy.defaultValue,
storageIndicator = MetadataKey.timelineStorageIndicator.defaultValue;
TimelineConfig copyWith({int? tilesPerRow, GroupAssetsBy? groupAssetsBy, bool? storageIndicator}) => TimelineConfig(
tilesPerRow: tilesPerRow ?? this.tilesPerRow,
@@ -1,3 +1,5 @@
import 'package:immich_mobile/domain/models/metadata_key.dart';
class ViewerConfig {
final bool loopVideo;
final bool loadOriginalVideo;
@@ -5,12 +7,18 @@ class ViewerConfig {
final bool tapToNavigate;
const ViewerConfig({
this.loopVideo = true,
this.loadOriginalVideo = false,
this.autoPlayVideo = true,
this.tapToNavigate = false,
required this.loopVideo,
required this.loadOriginalVideo,
required this.autoPlayVideo,
required this.tapToNavigate,
});
ViewerConfig.defaults()
: loopVideo = MetadataKey.viewerLoopVideo.defaultValue,
loadOriginalVideo = MetadataKey.viewerLoadOriginalVideo.defaultValue,
autoPlayVideo = MetadataKey.viewerAutoPlayVideo.defaultValue,
tapToNavigate = MetadataKey.viewerTapToNavigate.defaultValue;
ViewerConfig copyWith({bool? loopVideo, bool? loadOriginalVideo, bool? autoPlayVideo, bool? tapToNavigate}) =>
ViewerConfig(
loopVideo: loopVideo ?? this.loopVideo,
+43 -70
View File
@@ -20,106 +20,79 @@ enum MetadataDomain<T extends Object> {
enum MetadataKey<T extends Object> {
// Theme
themePrimaryColor<ImmichColorPreset>(.appConfig, 'theme.primaryColor', .indigo, _EnumCodec(ImmichColorPreset.values)),
themeMode<ThemeMode>(.appConfig, 'theme.mode', .system, _EnumCodec(ThemeMode.values)),
themeDynamic<bool>(.appConfig, 'theme.dynamic', false),
themeColorfulInterface<bool>(.appConfig, 'theme.colorfulInterface', true),
themePrimaryColor<ImmichColorPreset>(.appConfig, .indigo, _EnumCodec(ImmichColorPreset.values)),
themeMode<ThemeMode>(.appConfig, .system, _EnumCodec(ThemeMode.values)),
themeDynamic<bool>(.appConfig, false),
themeColorfulInterface<bool>(.appConfig, true),
// Image
imagePreferRemote<bool>(.appConfig, 'image.preferRemote', false),
imageLoadOriginal<bool>(.appConfig, 'image.loadOriginal', false),
imagePreferRemote<bool>(.appConfig, false),
imageLoadOriginal<bool>(.appConfig, false),
// Viewer
viewerLoopVideo<bool>(.appConfig, 'viewer.loopVideo', true),
viewerLoadOriginalVideo<bool>(.appConfig, 'viewer.loadOriginalVideo', false),
viewerAutoPlayVideo<bool>(.appConfig, 'viewer.autoPlayVideo', true),
viewerTapToNavigate<bool>(.appConfig, 'viewer.tapToNavigate', false),
viewerLoopVideo<bool>(.appConfig, true),
viewerLoadOriginalVideo<bool>(.appConfig, false),
viewerAutoPlayVideo<bool>(.appConfig, true),
viewerTapToNavigate<bool>(.appConfig, false),
// Network
networkAutoEndpointSwitching<bool>(.systemConfig, 'network.autoEndpointSwitching', false),
networkPreferredWifiName<String>(.systemConfig, 'network.preferredWifiName', ''),
networkLocalEndpoint<String>(.systemConfig, 'network.localEndpoint', ''),
networkExternalEndpointList<List<String>>(
.systemConfig,
'network.externalEndpointList',
[],
_ListCodec(_PrimitiveCodec.string),
),
networkAutoEndpointSwitching<bool>(.systemConfig, false),
networkPreferredWifiName<String>(.systemConfig, ''),
networkLocalEndpoint<String>(.systemConfig, ''),
networkExternalEndpointList<List<String>>(.systemConfig, [], _ListCodec(_PrimitiveCodec.string)),
networkCustomHeaders<Map<String, String>>(
.systemConfig,
'network.customHeaders',
{},
_MapCodec(_PrimitiveCodec.string, _PrimitiveCodec.string),
),
// Album
albumSortMode<AlbumSortMode>(
.appConfig,
'album.sortMode',
AlbumSortMode.mostRecent,
_EnumCodec(AlbumSortMode.values),
),
albumIsReverse<bool>(.appConfig, 'album.isReverse', true),
albumIsGrid<bool>(.appConfig, 'album.isGrid', false),
albumSortMode<AlbumSortMode>(.appConfig, AlbumSortMode.mostRecent, _EnumCodec(AlbumSortMode.values)),
albumIsReverse<bool>(.appConfig, true),
albumIsGrid<bool>(.appConfig, false),
// Backup
backupEnabled<bool>(.appConfig, 'backup.enabled', false),
backupUseCellularForVideos<bool>(.appConfig, 'backup.useCellularForVideos', false),
backupUseCellularForPhotos<bool>(.appConfig, 'backup.useCellularForPhotos', false),
backupRequireCharging<bool>(.appConfig, 'backup.requireCharging', false),
backupTriggerDelay<int>(.appConfig, 'backup.triggerDelay', 30),
backupSyncAlbums<bool>(.appConfig, 'backup.syncAlbums', false),
backupEnabled<bool>(.appConfig, false),
backupUseCellularForVideos<bool>(.appConfig, false),
backupUseCellularForPhotos<bool>(.appConfig, false),
backupRequireCharging<bool>(.appConfig, false),
backupTriggerDelay<int>(.appConfig, 30),
backupSyncAlbums<bool>(.appConfig, false),
// Timeline
timelineTilesPerRow<int>(.appConfig, 'timeline.tilesPerRow', 4),
timelineGroupAssetsBy<GroupAssetsBy>(
.appConfig,
'timeline.groupAssetsBy',
GroupAssetsBy.day,
_EnumCodec(GroupAssetsBy.values),
),
timelineStorageIndicator<bool>(.appConfig, 'timeline.storageIndicator', true),
timelineTilesPerRow<int>(.appConfig, 4),
timelineGroupAssetsBy<GroupAssetsBy>(.appConfig, GroupAssetsBy.day, _EnumCodec(GroupAssetsBy.values)),
timelineStorageIndicator<bool>(.appConfig, true),
// Log
logLevel<LogLevel>(.systemConfig, 'log.level', .info, _EnumCodec(LogLevel.values)),
logLevel<LogLevel>(.systemConfig, .info, _EnumCodec(LogLevel.values)),
// Map
mapShowFavoriteOnly<bool>(.appConfig, 'map.showFavoriteOnly', false),
mapRelativeDate<int>(.appConfig, 'map.relativeDate', 0),
mapIncludeArchived<bool>(.appConfig, 'map.includeArchived', false),
mapThemeMode<ThemeMode>(.appConfig, 'map.themeMode', .system, _EnumCodec(ThemeMode.values)),
mapWithPartners<bool>(.appConfig, 'map.withPartners', false),
mapShowFavoriteOnly<bool>(.appConfig, false),
mapRelativeDate<int>(.appConfig, 0),
mapIncludeArchived<bool>(.appConfig, false),
mapThemeMode<ThemeMode>(.appConfig, .system, _EnumCodec(ThemeMode.values)),
mapWithPartners<bool>(.appConfig, false),
// Cleanup
cleanupKeepFavorites<bool>(.appConfig, 'cleanup.keepFavorites', true),
cleanupKeepMediaType<AssetKeepType>(
.appConfig,
'cleanup.keepMediaType',
AssetKeepType.none,
_EnumCodec(AssetKeepType.values),
),
cleanupKeepAlbumIds<List<String>>(.appConfig, 'cleanup.keepAlbumIds', [], _ListCodec(_PrimitiveCodec.string)),
cleanupCutoffDaysAgo<int>(.appConfig, 'cleanup.cutoffDaysAgo', -1),
cleanupDefaultsInitialized<bool>(.appConfig, 'cleanup.defaultsInitialized', false),
cleanupKeepFavorites<bool>(.appConfig, true),
cleanupKeepMediaType<AssetKeepType>(.appConfig, AssetKeepType.none, _EnumCodec(AssetKeepType.values)),
cleanupKeepAlbumIds<List<String>>(.appConfig, [], _ListCodec(_PrimitiveCodec.string)),
cleanupCutoffDaysAgo<int>(.appConfig, -1),
cleanupDefaultsInitialized<bool>(.appConfig, false),
// Slideshow
slideshowTransition<bool>(.appConfig, 'slideshow.transition', true),
slideshowRepeat<bool>(.appConfig, 'slideshow.repeat', true),
slideshowDuration<int>(.appConfig, 'slideshow.duration', 5),
slideshowLook<SlideshowLook>(.appConfig, 'slideshow.look', SlideshowLook.contain, _EnumCodec(SlideshowLook.values)),
slideshowDirection<SlideshowDirection>(
.appConfig,
'slideshow.direction',
SlideshowDirection.forward,
_EnumCodec(SlideshowDirection.values),
);
slideshowTransition<bool>(.appConfig, true),
slideshowRepeat<bool>(.appConfig, true),
slideshowDuration<int>(.appConfig, 5),
slideshowLook<SlideshowLook>(.appConfig, SlideshowLook.contain, _EnumCodec(SlideshowLook.values)),
slideshowDirection<SlideshowDirection>(.appConfig, SlideshowDirection.forward, _EnumCodec(SlideshowDirection.values));
final MetadataDomain domain;
final String name;
final T defaultValue;
final _MetadataCodec<T>? _codecOverride;
const MetadataKey(this.domain, this.name, this.defaultValue, [this._codecOverride]);
const MetadataKey(this.domain, this.defaultValue, [this._codecOverride]);
String get key => '${domain.prefix}.$name';
@@ -39,7 +39,7 @@ class BackgroundWorkerFgService {
_foregroundHostApi.saveNotificationMessage(title, body);
Future<void> configure({int? minimumDelaySeconds, bool? requireCharging}) {
final backup = MetadataRepository.instance.appConfig.backup;
final backup = MetadataStore.appConfig.backup;
return _foregroundHostApi.configure(
BackgroundWorkerSettings(
minimumDelaySeconds: minimumDelaySeconds ?? backup.triggerDelay,
@@ -69,7 +69,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
BackgroundWorkerFlutterApi.setUp(this);
}
bool get _isBackupEnabled => MetadataRepository.instance.appConfig.backup.enabled;
bool get _isBackupEnabled => MetadataStore.appConfig.backup.enabled;
Future<void> init() async {
try {
@@ -6,12 +6,7 @@ import 'package:immich_mobile/extensions/string_extensions.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<MetadataKey, Object> _cache = {};
MetadataRepository._(this._db) : super(_db);
abstract final class MetadataStore {
static MetadataRepository? _instance;
static MetadataRepository get instance {
@@ -22,26 +17,36 @@ class MetadataRepository extends DriftDatabaseRepository {
return instance;
}
AppConfig _appConfig = const .new();
AppConfig get appConfig => _appConfig;
SystemConfig _systemConfig = const .new();
SystemConfig get systemConfig => _systemConfig;
static Future<MetadataRepository> ensureInitialized(Drift db) async {
if (_instance == null) {
final instance = MetadataRepository._(db);
final instance = MetadataRepository(db);
await instance._hydrate();
_instance = instance;
}
return _instance!;
}
static Future<void> refresh() async {
instance._cache.clear();
instance._appConfig = const .new();
instance._systemConfig = const .new();
await instance._hydrate();
static AppConfig get appConfig => instance.appConfig;
static SystemConfig get systemConfig => instance.systemConfig;
}
class MetadataRepository extends DriftDatabaseRepository {
final Drift _db;
final Map<MetadataKey, Object> _cache = {};
MetadataRepository(this._db) : super(_db);
AppConfig _appConfig = AppConfig.defaults();
AppConfig get appConfig => _appConfig;
SystemConfig _systemConfig = SystemConfig.defaults();
SystemConfig get systemConfig => _systemConfig;
Future<void> refresh() async {
_cache.clear();
_appConfig = AppConfig.defaults();
_systemConfig = SystemConfig.defaults();
await _hydrate();
}
Future<void> _hydrate() async => _hydrateCache(await _db.select(_db.metadataEntity).get());
@@ -52,6 +57,9 @@ class MetadataRepository extends DriftDatabaseRepository {
if (_read(key) == value) {
return;
}
if (value == key.defaultValue) {
return delete(key);
}
await _db
.into(_db.metadataEntity)
@@ -74,7 +82,10 @@ class MetadataRepository extends DriftDatabaseRepository {
final query = _db.select(_db.metadataEntity)..where((t) => t.key.like('${domain.prefix}.%'));
return query.watch().map((rows) {
_hydrateCache(rows);
return domain.config(this);
return switch (domain) {
.appConfig => _appConfig as T,
.systemConfig => _systemConfig as T,
};
});
}
@@ -94,84 +105,74 @@ class MetadataRepository extends DriftDatabaseRepository {
return;
}
_cache[key] = value;
key.domain.rebuild(this);
}
}
extension<T extends Object> on MetadataDomain<T> {
T config(MetadataRepository repo) => switch (this) {
.appConfig => repo._appConfig as T,
.systemConfig => repo._systemConfig as T,
};
void rebuild(MetadataRepository repo) {
switch (this) {
switch (key.domain) {
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),
),
album: .new(
sortMode: repo._read(.albumSortMode),
isReverse: repo._read(.albumIsReverse),
isGrid: repo._read(.albumIsGrid),
),
backup: .new(
enabled: repo._read(.backupEnabled),
useCellularForVideos: repo._read(.backupUseCellularForVideos),
useCellularForPhotos: repo._read(.backupUseCellularForPhotos),
requireCharging: repo._read(.backupRequireCharging),
triggerDelay: repo._read(.backupTriggerDelay),
syncAlbums: repo._read(.backupSyncAlbums),
),
);
_appConfig = _buildAppConfig();
case .systemConfig:
repo._systemConfig = .new(
logLevel: repo._read(.logLevel),
network: .new(
autoEndpointSwitching: repo._read(.networkAutoEndpointSwitching),
preferredWifiName: repo._read(.networkPreferredWifiName).nullIfEmpty,
localEndpoint: repo._read(.networkLocalEndpoint).nullIfEmpty,
externalEndpointList: repo._read(.networkExternalEndpointList),
customHeaders: repo._read(.networkCustomHeaders),
),
);
_systemConfig = _buildSystemConfig();
}
}
AppConfig _buildAppConfig() => .new(
theme: .new(
mode: _read(.themeMode),
primaryColor: _read(.themePrimaryColor),
dynamicTheme: _read(.themeDynamic),
colorfulInterface: _read(.themeColorfulInterface),
),
cleanup: .new(
keepFavorites: _read(.cleanupKeepFavorites),
keepMediaType: _read(.cleanupKeepMediaType),
keepAlbumIds: _read(.cleanupKeepAlbumIds),
cutoffDaysAgo: _read(.cleanupCutoffDaysAgo),
defaultsInitialized: _read(.cleanupDefaultsInitialized),
),
map: .new(
relativeDays: _read(.mapRelativeDate),
favoritesOnly: _read(.mapShowFavoriteOnly),
includeArchived: _read(.mapIncludeArchived),
themeMode: _read(.mapThemeMode),
withPartners: _read(.mapWithPartners),
),
timeline: .new(
tilesPerRow: _read(.timelineTilesPerRow),
groupAssetsBy: _read(.timelineGroupAssetsBy),
storageIndicator: _read(.timelineStorageIndicator),
),
image: .new(preferRemote: _read(.imagePreferRemote), loadOriginal: _read(.imageLoadOriginal)),
viewer: .new(
loopVideo: _read(.viewerLoopVideo),
loadOriginalVideo: _read(.viewerLoadOriginalVideo),
autoPlayVideo: _read(.viewerAutoPlayVideo),
tapToNavigate: _read(.viewerTapToNavigate),
),
slideshow: .new(
transition: _read(.slideshowTransition),
repeat: _read(.slideshowRepeat),
duration: _read(.slideshowDuration),
look: _read(.slideshowLook),
direction: _read(.slideshowDirection),
),
album: .new(sortMode: _read(.albumSortMode), isReverse: _read(.albumIsReverse), isGrid: _read(.albumIsGrid)),
backup: .new(
enabled: _read(.backupEnabled),
useCellularForVideos: _read(.backupUseCellularForVideos),
useCellularForPhotos: _read(.backupUseCellularForPhotos),
requireCharging: _read(.backupRequireCharging),
triggerDelay: _read(.backupTriggerDelay),
syncAlbums: _read(.backupSyncAlbums),
),
);
SystemConfig _buildSystemConfig() => .new(
logLevel: _read(.logLevel),
network: .new(
autoEndpointSwitching: _read(.networkAutoEndpointSwitching),
preferredWifiName: _read(.networkPreferredWifiName).nullIfEmpty,
localEndpoint: _read(.networkLocalEndpoint).nullIfEmpty,
externalEndpointList: _read(.networkExternalEndpointList),
customHeaders: _read(.networkCustomHeaders),
),
);
}
@@ -103,7 +103,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
return;
}
final isBackupEnabled = MetadataRepository.instance.appConfig.backup.enabled;
final isBackupEnabled = MetadataStore.appConfig.backup.enabled;
await ref.read(driftBackupProvider.notifier).getBackupStatus(user.id);
final currentTotalAssetCount = ref.read(driftBackupProvider.select((p) => p.totalCount));
final totalChanged = currentTotalAssetCount != _initialTotalAssetCount;
@@ -45,7 +45,7 @@ class DriftBackupOptionsPage extends ConsumerWidget {
}
await ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id);
final isBackupEnabled = MetadataRepository.instance.appConfig.backup.enabled;
final isBackupEnabled = MetadataStore.appConfig.backup.enabled;
if (!isBackupEnabled) {
return;
}
@@ -341,7 +341,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
await backgroundManager.hashAssets();
}
if (MetadataRepository.instance.appConfig.backup.syncAlbums) {
if (MetadataStore.appConfig.backup.syncAlbums) {
await backgroundManager.syncLinkedAlbum();
}
} catch (e) {
@@ -370,7 +370,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
}
Future<void> _resumeBackup(DriftBackupNotifier notifier) async {
final isEnableBackup = MetadataRepository.instance.appConfig.backup.enabled;
final isEnableBackup = MetadataStore.appConfig.backup.enabled;
if (isEnableBackup) {
final currentUser = Store.tryGet(StoreKey.currentUser);
@@ -9,10 +9,10 @@ import 'package:immich_mobile/extensions/platform_extensions.dart';
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart';
import 'package:immich_mobile/providers/cast.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:logging/logging.dart';
import 'package:native_video_player/native_video_player.dart';
@@ -188,6 +188,4 @@ ImageProvider? getThumbnailImageProvider(BaseAsset asset, {Size size = kThumbnai
}
bool _shouldUseLocalAsset(BaseAsset asset) =>
asset.hasLocal &&
(!asset.hasRemote || !MetadataRepository.instance.appConfig.image.preferRemote) &&
!asset.isEdited;
asset.hasLocal && (!asset.hasRemote || !MetadataStore.appConfig.image.preferRemote) && !asset.isEdited;
@@ -104,7 +104,7 @@ class LocalFullImageProvider extends CancellableImageProvider<LocalFullImageProv
return;
}
final loadOriginal = MetadataRepository.instance.appConfig.image.loadOriginal;
final loadOriginal = MetadataStore.appConfig.image.loadOriginal;
final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio;
var request = this.request = LocalImageRequest(
localId: key.id,
@@ -122,7 +122,7 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
edited: key.edited,
),
);
final loadOriginal = assetType == AssetType.image && MetadataRepository.instance.appConfig.image.loadOriginal;
final loadOriginal = assetType == AssetType.image && MetadataStore.appConfig.image.loadOriginal;
yield* loadRequest(previewRequest, decode, isFinal: !loadOriginal);
if (!loadOriginal) {
+2 -1
View File
@@ -8,6 +8,7 @@ import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/domain/services/user.service.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
import 'package:immich_mobile/models/auth/auth_state.model.dart';
import 'package:immich_mobile/models/auth/login_response.model.dart';
import 'package:immich_mobile/providers/api.provider.dart';
@@ -130,7 +131,7 @@ class AuthNotifier extends StateNotifier<AuthState> {
await _apiService.updateHeaders();
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
final headerMap = _ref.read(metadataProvider).systemConfig.network.customHeaders;
final headerMap = MetadataStore.systemConfig.network.customHeaders;
final customHeaders = headerMap.isEmpty ? null : jsonEncode(headerMap);
await _widgetService.writeCredentials(serverEndpoint, accessToken, customHeaders);
@@ -3,7 +3,7 @@ import 'package:immich_mobile/domain/models/config/app_config.dart';
import 'package:immich_mobile/domain/models/config/system_config.dart';
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
final metadataProvider = Provider.autoDispose<MetadataRepository>((_) => MetadataRepository.instance);
final metadataProvider = Provider.autoDispose<MetadataRepository>((_) => MetadataStore.instance);
final appConfigProvider = Provider.autoDispose<AppConfig>((ref) {
final repo = ref.watch(metadataProvider);
+2 -2
View File
@@ -177,7 +177,7 @@ class ApiService {
if (serverEndpoint != null && serverEndpoint.isNotEmpty) {
urls.add(serverEndpoint);
}
final network = MetadataRepository.instance.systemConfig.network;
final network = MetadataStore.systemConfig.network;
final localEndpoint = network.localEndpoint;
if (localEndpoint != null) {
urls.add(localEndpoint);
@@ -191,7 +191,7 @@ class ApiService {
}
static Map<String, String> getRequestHeaders() {
return MetadataRepository.instance.systemConfig.network.customHeaders;
return MetadataStore.systemConfig.network.customHeaders;
}
ApiClient get apiClient => _apiClient;
+1 -1
View File
@@ -100,7 +100,7 @@ class AuthService {
_log.severe("Error clearing local data", error, stackTrace);
});
await MetadataRepository.instance.write(MetadataKey.backupEnabled, false);
await MetadataStore.instance.write(MetadataKey.backupEnabled, false);
}
}
@@ -359,7 +359,7 @@ class BackgroundUploadService {
}
bool _shouldRequireWiFi(LocalAsset asset) {
final backup = MetadataRepository.instance.appConfig.backup;
final backup = MetadataStore.appConfig.backup;
if (asset.isVideo && backup.useCellularForVideos) {
return false;
}
@@ -451,7 +451,7 @@ class ForegroundUploadService {
}
bool _shouldRequireWiFi(LocalAsset asset) {
final backup = MetadataRepository.instance.appConfig.backup;
final backup = MetadataStore.appConfig.backup;
if (asset.isVideo && backup.useCellularForVideos) {
return false;
}
+1 -1
View File
@@ -49,7 +49,7 @@ abstract final class Bootstrap {
await StoreService.init(storeRepository: storeRepo, listenUpdates: listenStoreUpdates);
final metadataRepo = await MetadataRepository.ensureInitialized(drift);
final metadataRepo = await MetadataStore.ensureInitialized(drift);
await LogService.init(
logRepository: LogRepository(logDb),
+3
View File
@@ -267,6 +267,9 @@ class _StoreMigrator {
Future<void> complete() async {
await _db.batch((batch) {
for (final entry in _cache.entries) {
if (entry.value == entry.key.defaultValue) {
continue;
}
batch.insert(
_db.metadataEntity,
MetadataEntityCompanion(key: Value(entry.key.key), value: Value(entry.key.encode(entry.value))),
@@ -187,7 +187,7 @@ class LoginForm extends HookConsumerWidget {
await backgroundManager.syncRemote();
await backgroundManager.hashAssets();
if (MetadataRepository.instance.appConfig.backup.syncAlbums) {
if (MetadataStore.appConfig.backup.syncAlbums) {
await backgroundManager.syncLinkedAlbum();
}
}
@@ -39,7 +39,9 @@ void main() {
registerFallbackValue(LogLevel.info);
when(() => mockLogRepo.truncate(limit: any(named: 'limit'))).thenAnswer((_) async => {});
when(() => mockMetadataRepository.systemConfig).thenReturn(const SystemConfig(logLevel: LogLevel.fine));
when(
() => mockMetadataRepository.systemConfig,
).thenReturn(SystemConfig(logLevel: LogLevel.fine, network: .defaults()));
when(() => mockMetadataRepository.write<LogLevel, LogLevel>(MetadataKey.logLevel, any())).thenAnswer((_) async {});
when(() => mockLogRepo.getAll()).thenAnswer((_) async => []);
when(() => mockLogRepo.insert(any())).thenAnswer((_) async => true);
@@ -14,7 +14,7 @@ void main() {
setUpAll(() async {
ctx = MediumRepositoryContext();
sut = await MetadataRepository.ensureInitialized(ctx.db);
sut = MetadataRepository(ctx.db);
});
tearDownAll(() async {
@@ -23,7 +23,7 @@ void main() {
setUp(() async {
await ctx.db.delete(ctx.db.metadataEntity).go();
await MetadataRepository.refresh();
await sut.refresh();
});
group('defaults', () {
@@ -78,7 +78,7 @@ void main() {
// Cache hasn't seen this row yet — view still returns the default.
expect(sut.appConfig.theme.mode, ThemeMode.system);
await MetadataRepository.refresh();
await sut.refresh();
expect(sut.appConfig.theme.mode, ThemeMode.dark);
});
@@ -88,7 +88,7 @@ void main() {
await ctx.db.delete(ctx.db.metadataEntity).go();
expect(sut.appConfig.theme.mode, ThemeMode.dark);
await MetadataRepository.refresh();
await sut.refresh();
expect(sut.appConfig.theme.mode, ThemeMode.system);
});
@@ -103,7 +103,7 @@ void main() {
),
);
await MetadataRepository.refresh();
await sut.refresh();
expect(sut.appConfig.theme.mode, ThemeMode.system);
});
});
@@ -38,7 +38,7 @@ void main() {
);
db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
await StoreService.init(storeRepository: DriftStoreRepository(db));
await MetadataRepository.ensureInitialized(db);
await MetadataStore.ensureInitialized(db);
await Store.put(StoreKey.serverEndpoint, 'http://test-server.com');
await Store.put(StoreKey.deviceId, 'test-device-id');