migrate timeline config

This commit is contained in:
shenlong-tanwen
2026-05-04 00:49:35 +07:00
parent 80378a42a7
commit 9fb2a1a02a
18 changed files with 137 additions and 71 deletions
@@ -1,25 +1,40 @@
import 'package:immich_mobile/domain/models/config/cleanup_config.dart'; import 'package:immich_mobile/domain/models/config/cleanup_config.dart';
import 'package:immich_mobile/domain/models/config/map_config.dart'; import 'package:immich_mobile/domain/models/config/map_config.dart';
import 'package:immich_mobile/domain/models/config/theme_config.dart'; import 'package:immich_mobile/domain/models/config/theme_config.dart';
import 'package:immich_mobile/domain/models/config/timeline_config.dart';
class AppConfig { class AppConfig {
final ThemeConfig theme; final ThemeConfig theme;
final CleanupConfig cleanup; final CleanupConfig cleanup;
final MapConfig map; final MapConfig map;
final TimelineConfig timeline;
const AppConfig({this.theme = const .new(), this.cleanup = const .new(), this.map = const .new()}); const AppConfig({
this.theme = const .new(),
this.cleanup = const .new(),
this.map = const .new(),
this.timeline = const .new(),
});
AppConfig copyWith({ThemeConfig? theme, CleanupConfig? cleanup, MapConfig? map}) => AppConfig copyWith({ThemeConfig? theme, CleanupConfig? cleanup, MapConfig? map, TimelineConfig? timeline}) => .new(
.new(theme: theme ?? this.theme, cleanup: cleanup ?? this.cleanup, map: map ?? this.map); theme: theme ?? this.theme,
cleanup: cleanup ?? this.cleanup,
map: map ?? this.map,
timeline: timeline ?? this.timeline,
);
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
identical(this, other) || identical(this, other) ||
(other is AppConfig && other.theme == theme && other.cleanup == cleanup && other.map == map); (other is AppConfig &&
other.theme == theme &&
other.cleanup == cleanup &&
other.map == map &&
other.timeline == timeline);
@override @override
int get hashCode => Object.hash(theme, cleanup, map); int get hashCode => Object.hash(theme, cleanup, map, timeline);
@override @override
String toString() => 'AppConfig(theme: $theme, cleanup: $cleanup, map: $map)'; String toString() => 'AppConfig(theme: $theme, cleanup: $cleanup, map: $map, timeline: $timeline)';
} }
@@ -16,14 +16,14 @@ class MapConfig {
}); });
MapConfig copyWith({ MapConfig copyWith({
int? relativeDate, int? relativeDays,
bool? showFavoriteOnly, bool? favoritesOnly,
bool? includeArchived, bool? includeArchived,
ThemeMode? themeMode, ThemeMode? themeMode,
bool? withPartners, bool? withPartners,
}) => MapConfig( }) => MapConfig(
relativeDays: relativeDate ?? this.relativeDays, relativeDays: relativeDays ?? this.relativeDays,
favoritesOnly: showFavoriteOnly ?? this.favoritesOnly, favoritesOnly: favoritesOnly ?? this.favoritesOnly,
includeArchived: includeArchived ?? this.includeArchived, includeArchived: includeArchived ?? this.includeArchived,
themeMode: themeMode ?? this.themeMode, themeMode: themeMode ?? this.themeMode,
withPartners: withPartners ?? this.withPartners, withPartners: withPartners ?? this.withPartners,
@@ -0,0 +1,30 @@
import 'package:immich_mobile/domain/models/timeline.model.dart';
class TimelineConfig {
final int tilesPerRow;
final GroupAssetsBy groupAssetsBy;
final bool storageIndicator;
const TimelineConfig({this.tilesPerRow = 4, this.groupAssetsBy = GroupAssetsBy.day, this.storageIndicator = true});
TimelineConfig copyWith({int? tilesPerRow, GroupAssetsBy? groupAssetsBy, bool? storageIndicator}) => TimelineConfig(
tilesPerRow: tilesPerRow ?? this.tilesPerRow,
groupAssetsBy: groupAssetsBy ?? this.groupAssetsBy,
storageIndicator: storageIndicator ?? this.storageIndicator,
);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is TimelineConfig &&
other.tilesPerRow == tilesPerRow &&
other.groupAssetsBy == groupAssetsBy &&
other.storageIndicator == storageIndicator);
@override
int get hashCode => Object.hash(tilesPerRow, groupAssetsBy, storageIndicator);
@override
String toString() =>
'TimelineConfig(tilesPerRow: $tilesPerRow, groupAssetsBy: $groupAssetsBy, storageIndicator: $storageIndicator)';
}
@@ -7,6 +7,7 @@ import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/config/app_config.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/config/system_config.dart';
import 'package:immich_mobile/domain/models/log.model.dart'; import 'package:immich_mobile/domain/models/log.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
enum MetadataDomain<T extends Object> { enum MetadataDomain<T extends Object> {
appConfig<AppConfig>('config.app'), appConfig<AppConfig>('config.app'),
@@ -23,6 +24,16 @@ enum MetadataKey<T extends Object> {
themeDynamic<bool>(.appConfig, 'theme.dynamic', false), themeDynamic<bool>(.appConfig, 'theme.dynamic', false),
themeColorfulInterface<bool>(.appConfig, 'theme.colorfulInterface', true), themeColorfulInterface<bool>(.appConfig, 'theme.colorfulInterface', true),
// Timeline
timelineTilesPerRow<int>(.appConfig, 'timeline.tilesPerRow', 4),
timelineGroupAssetsBy<GroupAssetsBy>(
.appConfig,
'timeline.groupAssetsBy',
GroupAssetsBy.day,
_EnumCodec(GroupAssetsBy.values),
),
timelineStorageIndicator<bool>(.appConfig, 'timeline.storageIndicator', true),
// Log // Log
logLevel<LogLevel>(.systemConfig, 'log.level', .info, _EnumCodec(LogLevel.values)), logLevel<LogLevel>(.systemConfig, 'log.level', .info, _EnumCodec(LogLevel.values)),
@@ -1,9 +1,6 @@
import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart';
enum Setting<T> { enum Setting<T> {
tilesPerRow<int>(StoreKey.tilesPerRow, 4),
groupAssetsBy<int>(StoreKey.groupAssetsBy, 0),
showStorageIndicator<bool>(StoreKey.storageIndicator, true),
loadOriginal<bool>(StoreKey.loadOriginal, false), loadOriginal<bool>(StoreKey.loadOriginal, false),
loadOriginalVideo<bool>(StoreKey.loadOriginalVideo, false), loadOriginalVideo<bool>(StoreKey.loadOriginalVideo, false),
autoPlayVideo<bool>(StoreKey.autoPlayVideo, true), autoPlayVideo<bool>(StoreKey.autoPlayVideo, true),
+7 -12
View File
@@ -19,24 +19,17 @@ enum StoreKey<T> {
backgroundBackup<bool>._(14), backgroundBackup<bool>._(14),
sslClientCertData<String>._(15), sslClientCertData<String>._(15),
sslClientPasswd<String>._(16), sslClientPasswd<String>._(16),
// user settings from [AppSettingsEnum] below:
loadOriginal<bool>._(101),
tilesPerRow<int>._(103),
groupAssetsBy<int>._(105),
uploadErrorNotificationGracePeriod<int>._(106), uploadErrorNotificationGracePeriod<int>._(106),
storageIndicator<bool>._(109),
thumbnailCacheSize<int>._(110), thumbnailCacheSize<int>._(110),
imageCacheSize<int>._(111), imageCacheSize<int>._(111),
albumThumbnailCacheSize<int>._(112), albumThumbnailCacheSize<int>._(112),
selectedAlbumSortOrder<int>._(113), selectedAlbumSortOrder<int>._(113),
advancedTroubleshooting<bool>._(114), advancedTroubleshooting<bool>._(114),
preferRemoteImage<bool>._(116), preferRemoteImage<bool>._(116),
loopVideo<bool>._(117),
selfSignedCert<bool>._(120), selfSignedCert<bool>._(120),
selectedAlbumSortReverse<bool>._(123), selectedAlbumSortReverse<bool>._(123),
enableHapticFeedback<bool>._(126), enableHapticFeedback<bool>._(126),
customHeaders<String>._(127), customHeaders<String>._(127),
syncAlbums<bool>._(131), syncAlbums<bool>._(131),
// Auto endpoint switching // Auto endpoint switching
@@ -45,17 +38,16 @@ enum StoreKey<T> {
localEndpoint<String>._(134), localEndpoint<String>._(134),
externalEndpointList<String>._(135), externalEndpointList<String>._(135),
// Video settings
loadOriginalVideo<bool>._(136),
manageLocalMediaAndroid<bool>._(137), manageLocalMediaAndroid<bool>._(137),
// Read-only Mode settings // Read-only Mode settings
readonlyModeEnabled<bool>._(138), readonlyModeEnabled<bool>._(138),
autoPlayVideo<bool>._(139),
albumGridView<bool>._(140), albumGridView<bool>._(140),
loadOriginal<bool>._(101),
// Image viewer navigation settings // Image viewer navigation settings
loopVideo<bool>._(117),
loadOriginalVideo<bool>._(136),
autoPlayVideo<bool>._(139),
tapToNavigate<bool>._(141), tapToNavigate<bool>._(141),
// Experimental stuff // Experimental stuff
@@ -74,6 +66,9 @@ enum StoreKey<T> {
legacyCleanupKeepAlbumIds<String>._(1010), legacyCleanupKeepAlbumIds<String>._(1010),
legacyCleanupCutoffDaysAgo<int>._(1011), legacyCleanupCutoffDaysAgo<int>._(1011),
legacyCleanupDefaultsInitialized<bool>._(1012), legacyCleanupDefaultsInitialized<bool>._(1012),
legacyTilesPerRow<int>._(103),
legacyGroupAssetsBy<int>._(105),
legacyStorageIndicator<bool>._(109),
legacyMapRelativeDate<int>._(119), legacyMapRelativeDate<int>._(119),
legacyMapShowFavoriteOnly<bool>._(118), legacyMapShowFavoriteOnly<bool>._(118),
legacyMapIncludeArchived<bool>._(121), legacyMapIncludeArchived<bool>._(121),
@@ -5,10 +5,9 @@ import 'package:collection/collection.dart';
import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/models/setting.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/services/setting.service.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
import 'package:immich_mobile/utils/async_mutex.dart'; import 'package:immich_mobile/utils/async_mutex.dart';
@@ -39,14 +38,16 @@ enum TimelineOrigin {
class TimelineFactory { class TimelineFactory {
final DriftTimelineRepository _timelineRepository; final DriftTimelineRepository _timelineRepository;
final SettingsService _settingsService; final MetadataRepository _metadataRepository;
const TimelineFactory({required DriftTimelineRepository timelineRepository, required SettingsService settingsService}) const TimelineFactory({
: _timelineRepository = timelineRepository, required DriftTimelineRepository timelineRepository,
_settingsService = settingsService; required MetadataRepository metadataRepository,
}) : _timelineRepository = timelineRepository,
_metadataRepository = metadataRepository;
GroupAssetsBy get groupBy { GroupAssetsBy get groupBy {
final group = GroupAssetsBy.values[_settingsService.get(Setting.groupAssetsBy)]; final group = _metadataRepository.appConfig.timeline.groupAssetsBy;
// We do not support auto grouping in the new timeline yet, fallback to day grouping // We do not support auto grouping in the new timeline yet, fallback to day grouping
return group == GroupAssetsBy.auto ? GroupAssetsBy.day : group; return group == GroupAssetsBy.auto ? GroupAssetsBy.day : group;
} }
@@ -121,6 +121,11 @@ extension<T extends Object> on MetadataDomain<T> {
themeMode: repo._read(.mapThemeMode), themeMode: repo._read(.mapThemeMode),
withPartners: repo._read(.mapWithPartners), withPartners: repo._read(.mapWithPartners),
), ),
timeline: .new(
tilesPerRow: repo._read(.timelineTilesPerRow),
groupAssetsBy: repo._read(.timelineGroupAssetsBy),
storageIndicator: repo._read(.timelineStorageIndicator),
),
); );
case .systemConfig: case .systemConfig:
repo._systemConfig = .new(logLevel: repo._read(.logLevel)); repo._systemConfig = .new(logLevel: repo._read(.logLevel));
@@ -2,15 +2,14 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/setting.model.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/duration_extensions.dart'; import 'package:immich_mobile/extensions/duration_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart'; import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.dart'; import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.dart';
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
class ThumbnailTile extends ConsumerStatefulWidget { class ThumbnailTile extends ConsumerStatefulWidget {
@@ -61,7 +60,7 @@ class _ThumbnailTileState extends ConsumerState<ThumbnailTile> {
); );
final bool storageIndicator = final bool storageIndicator =
ref.watch(settingsProvider.select((s) => s.get(Setting.showStorageIndicator))) && widget.showStorageIndicator; ref.watch(appConfigProvider.select((s) => s.timeline.storageIndicator)) && widget.showStorageIndicator;
if (!isCurrentAsset) { if (!isCurrentAsset) {
_hideIndicators = false; _hideIndicators = false;
@@ -1,12 +1,11 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/setting.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart'; import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
import 'package:immich_mobile/presentation/widgets/timeline/fixed/segment_builder.dart'; import 'package:immich_mobile/presentation/widgets/timeline/fixed/segment_builder.dart';
import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart'; import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart';
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
class TimelineArgs { class TimelineArgs {
@@ -93,7 +92,7 @@ final timelineSegmentProvider = StreamProvider.autoDispose<List<Segment>>((ref)
final availableTileWidth = args.maxWidth - (spacing * (columnCount - 1)); final availableTileWidth = args.maxWidth - (spacing * (columnCount - 1));
final tileExtent = math.max(0, availableTileWidth) / columnCount; final tileExtent = math.max(0, availableTileWidth) / columnCount;
final groupBy = args.groupBy ?? GroupAssetsBy.values[ref.watch(settingsProvider).get(Setting.groupAssetsBy)]; final groupBy = args.groupBy ?? ref.watch(appConfigProvider.select((config) => config.timeline.groupAssetsBy));
final timelineService = ref.watch(timelineServiceProvider); final timelineService = ref.watch(timelineServiceProvider);
yield* timelineService.watchBuckets().map((buckets) { yield* timelineService.watchBuckets().map((buckets) {
@@ -102,7 +101,7 @@ final timelineSegmentProvider = StreamProvider.autoDispose<List<Segment>>((ref)
tileHeight: tileExtent, tileHeight: tileExtent,
columnCount: columnCount, columnCount: columnCount,
spacing: spacing, spacing: spacing,
groupBy: groupBy, groupBy: groupBy!,
).generate(); ).generate();
}); });
}, dependencies: [timelineServiceProvider, timelineArgsProvider]); }, dependencies: [timelineServiceProvider, timelineArgsProvider]);
@@ -10,7 +10,7 @@ import 'package:flutter/rendering.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
@@ -22,8 +22,8 @@ import 'package:immich_mobile/presentation/widgets/timeline/scrubber.widget.dart
import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart'; import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline_drag_region.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline_drag_region.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart'; import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart';
@@ -74,7 +74,7 @@ class Timeline extends StatelessWidget {
(ref) => TimelineArgs( (ref) => TimelineArgs(
maxWidth: constraints.maxWidth, maxWidth: constraints.maxWidth,
maxHeight: constraints.maxHeight, maxHeight: constraints.maxHeight,
columnCount: ref.watch(settingsProvider.select((s) => s.get(Setting.tilesPerRow))), columnCount: ref.watch(appConfigProvider.select((config) => config.timeline.tilesPerRow)),
showStorageIndicator: showStorageIndicator, showStorageIndicator: showStorageIndicator,
withStack: withStack, withStack: withStack,
groupBy: groupBy, groupBy: groupBy,
@@ -161,7 +161,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
_scrollController = ScrollController(onAttach: _restoreAssetPosition); _scrollController = ScrollController(onAttach: _restoreAssetPosition);
_eventSubscription = EventStream.shared.listen(_onEvent); _eventSubscription = EventStream.shared.listen(_onEvent);
final currentTilesPerRow = ref.read(settingsProvider).get(Setting.tilesPerRow); final currentTilesPerRow = ref.read(appConfigProvider.select((config) => config.timeline.tilesPerRow));
_perRow = currentTilesPerRow; _perRow = currentTilesPerRow;
_scaleFactor = 7.0 - _perRow; _scaleFactor = 7.0 - _perRow;
_baseScaleFactor = _scaleFactor; _baseScaleFactor = _scaleFactor;
@@ -453,7 +453,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
_restoreAssetIndex = targetAssetIndex; _restoreAssetIndex = targetAssetIndex;
}); });
ref.read(settingsProvider.notifier).set(Setting.tilesPerRow, _perRow); ref.read(metadataProvider).write(MetadataKey.timelineTilesPerRow, _perRow);
} }
}; };
}, },
@@ -3,7 +3,7 @@ import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart';
final timelineRepositoryProvider = Provider<DriftTimelineRepository>( final timelineRepositoryProvider = Provider<DriftTimelineRepository>(
@@ -29,7 +29,7 @@ final timelineServiceProvider = Provider<TimelineService>(
final timelineFactoryProvider = Provider<TimelineFactory>( final timelineFactoryProvider = Provider<TimelineFactory>(
(ref) => TimelineFactory( (ref) => TimelineFactory(
timelineRepository: ref.watch(timelineRepositoryProvider), timelineRepository: ref.watch(timelineRepositoryProvider),
settingsService: ref.watch(settingsProvider), metadataRepository: ref.watch(metadataProvider),
), ),
); );
@@ -3,14 +3,11 @@ import 'package:immich_mobile/entities/store.entity.dart';
enum AppSettingsEnum<T> { enum AppSettingsEnum<T> {
loadOriginal<bool>(StoreKey.loadOriginal, "loadOriginal", false), loadOriginal<bool>(StoreKey.loadOriginal, "loadOriginal", false),
tilesPerRow<int>(StoreKey.tilesPerRow, "tilesPerRow", 4),
groupAssetsBy<int>(StoreKey.groupAssetsBy, "groupBy", 0),
uploadErrorNotificationGracePeriod<int>( uploadErrorNotificationGracePeriod<int>(
StoreKey.uploadErrorNotificationGracePeriod, StoreKey.uploadErrorNotificationGracePeriod,
"uploadErrorNotificationGracePeriod", "uploadErrorNotificationGracePeriod",
2, 2,
), ),
storageIndicator<bool>(StoreKey.storageIndicator, "storageIndicator", true),
thumbnailCacheSize<int>(StoreKey.thumbnailCacheSize, "thumbnailCacheSize", 10000), thumbnailCacheSize<int>(StoreKey.thumbnailCacheSize, "thumbnailCacheSize", 10000),
imageCacheSize<int>(StoreKey.imageCacheSize, "imageCacheSize", 350), imageCacheSize<int>(StoreKey.imageCacheSize, "imageCacheSize", 350),
albumThumbnailCacheSize<int>(StoreKey.albumThumbnailCacheSize, "albumThumbnailCacheSize", 200), albumThumbnailCacheSize<int>(StoreKey.albumThumbnailCacheSize, "albumThumbnailCacheSize", 200),
+9
View File
@@ -7,6 +7,7 @@ import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/log.model.dart'; import 'package:immich_mobile/domain/models/log.model.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart'; import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/infrastructure/entities/metadata.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/metadata.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
@@ -75,6 +76,14 @@ Future<void> _migrateTo26(Drift drift) async {
await migrator.migrateBool(StoreKey.legacyMapIncludeArchived, MetadataKey.mapIncludeArchived); await migrator.migrateBool(StoreKey.legacyMapIncludeArchived, MetadataKey.mapIncludeArchived);
await migrator.migrateEnumIndex(StoreKey.legacyMapThemeMode, MetadataKey.mapThemeMode, ThemeMode.values); await migrator.migrateEnumIndex(StoreKey.legacyMapThemeMode, MetadataKey.mapThemeMode, ThemeMode.values);
await migrator.migrateBool(StoreKey.legacyMapwithPartners, MetadataKey.mapWithPartners); await migrator.migrateBool(StoreKey.legacyMapwithPartners, MetadataKey.mapWithPartners);
// Timeline
await migrator.migrateInt(StoreKey.legacyTilesPerRow, MetadataKey.timelineTilesPerRow);
await migrator.migrateEnumIndex(
StoreKey.legacyGroupAssetsBy,
MetadataKey.timelineGroupAssetsBy,
GroupAssetsBy.values,
);
await migrator.migrateBool(StoreKey.legacyStorageIndicator, MetadataKey.timelineStorageIndicator);
await migrator.complete(); await migrator.complete();
} }
@@ -1,12 +1,13 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
import 'package:immich_mobile/widgets/settings/setting_group_title.dart'; import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
import 'package:immich_mobile/widgets/settings/settings_radio_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_radio_list_tile.dart';
@@ -15,18 +16,17 @@ class GroupSettings extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final groupByIndex = useAppSettingsState(AppSettingsEnum.groupAssetsBy); final groupBy = useValueNotifier(ref.watch(appConfigProvider.select((s) => s.timeline.groupAssetsBy)));
final groupBy = GroupAssetsBy.values[groupByIndex.value];
Future<void> updateAppSettings(GroupAssetsBy groupBy) async { Future<void> updateAppSettings(GroupAssetsBy groupBy) async {
await ref.watch(appSettingsServiceProvider).setSetting(AppSettingsEnum.groupAssetsBy, groupBy.index); await ref.read(metadataProvider).write(MetadataKey.timelineGroupAssetsBy, groupBy);
ref.invalidate(appSettingsServiceProvider); ref.invalidate(appSettingsServiceProvider);
} }
void changeGroupValue(GroupAssetsBy? value) { void changeGroupValue(GroupAssetsBy? value) {
if (value != null) { if (value != null) {
groupByIndex.value = value.index; groupBy.value = value;
unawaited(updateAppSettings(groupBy)); unawaited(updateAppSettings(value));
} }
} }
@@ -52,7 +52,7 @@ class GroupSettings extends HookConsumerWidget {
value: GroupAssetsBy.auto, value: GroupAssetsBy.auto,
), ),
], ],
groupBy: groupBy, groupBy: groupBy.value,
onRadioChanged: changeGroupValue, onRadioChanged: changeGroupValue,
), ),
], ],
@@ -1,10 +1,11 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
import 'package:immich_mobile/widgets/settings/setting_group_title.dart'; import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart';
@@ -13,7 +14,10 @@ class LayoutSettings extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final tilesPerRow = useAppSettingsState(AppSettingsEnum.tilesPerRow); final tilesPerRow = useState(ref.read(appConfigProvider.select((s) => s.timeline.tilesPerRow)));
useValueChanged<int, void>(tilesPerRow.value, (_, __) {
ref.read(metadataProvider).write(MetadataKey.timelineTilesPerRow, tilesPerRow.value);
});
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -29,7 +33,9 @@ class LayoutSettings extends HookConsumerWidget {
maxValue: 6, maxValue: 6,
minValue: 2, minValue: 2,
noDivisons: 4, noDivisons: 4,
onChangeEnd: (_) => ref.invalidate(appSettingsServiceProvider), onChangeEnd: (value) {
ref.invalidate(appSettingsServiceProvider);
},
), ),
], ],
); );
@@ -1,10 +1,11 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/metadata_key.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_group_settings.dart'; import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_group_settings.dart';
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_layout_settings.dart'; import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_layout_settings.dart';
import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
@@ -15,13 +16,14 @@ class AssetListSettings extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final showStorageIndicator = useAppSettingsState(AppSettingsEnum.storageIndicator); final storageIndicator = useValueNotifier(ref.watch(appConfigProvider.select((s) => s.timeline.storageIndicator)));
final assetListSetting = [ final assetListSetting = [
SettingsSwitchListTile( SettingsSwitchListTile(
valueNotifier: showStorageIndicator, valueNotifier: storageIndicator,
title: 'theme_setting_asset_list_storage_indicator_title'.tr(), title: 'theme_setting_asset_list_storage_indicator_title'.tr(),
onChanged: (_) { onChanged: (value) {
ref.read(metadataProvider).write(MetadataKey.timelineStorageIndicator, value);
ref.invalidate(appSettingsServiceProvider); ref.invalidate(appSettingsServiceProvider);
ref.invalidate(settingsProvider); ref.invalidate(settingsProvider);
}, },
@@ -10,7 +10,7 @@ import '../../infrastructure/repository.mock.dart';
const _kAccessToken = '#ThisIsAToken'; const _kAccessToken = '#ThisIsAToken';
const _kBackgroundBackup = false; const _kBackgroundBackup = false;
const _kGroupAssetsBy = 2; const _kVersion = 2;
final _kBackupFailedSince = DateTime.utc(2023); final _kBackupFailedSince = DateTime.utc(2023);
void main() { void main() {
@@ -31,7 +31,7 @@ void main() {
(_) async => [ (_) async => [
const StoreDto(StoreKey.accessToken, _kAccessToken), const StoreDto(StoreKey.accessToken, _kAccessToken),
const StoreDto(StoreKey.backgroundBackup, _kBackgroundBackup), const StoreDto(StoreKey.backgroundBackup, _kBackgroundBackup),
const StoreDto(StoreKey.groupAssetsBy, _kGroupAssetsBy), const StoreDto(StoreKey.version, _kVersion),
StoreDto(StoreKey.backupFailedSince, _kBackupFailedSince), StoreDto(StoreKey.backupFailedSince, _kBackupFailedSince),
], ],
); );
@@ -50,7 +50,7 @@ void main() {
verify(() => mockDriftStoreRepo.getAll()).called(1); verify(() => mockDriftStoreRepo.getAll()).called(1);
expect(sut.tryGet(StoreKey.accessToken), _kAccessToken); expect(sut.tryGet(StoreKey.accessToken), _kAccessToken);
expect(sut.tryGet(StoreKey.backgroundBackup), _kBackgroundBackup); expect(sut.tryGet(StoreKey.backgroundBackup), _kBackgroundBackup);
expect(sut.tryGet(StoreKey.groupAssetsBy), _kGroupAssetsBy); expect(sut.tryGet(StoreKey.version), _kVersion);
expect(sut.tryGet(StoreKey.backupFailedSince), _kBackupFailedSince); expect(sut.tryGet(StoreKey.backupFailedSince), _kBackupFailedSince);
// Other keys should be null // Other keys should be null
expect(sut.tryGet(StoreKey.currentUser), isNull); expect(sut.tryGet(StoreKey.currentUser), isNull);
@@ -152,7 +152,7 @@ void main() {
verify(() => mockDriftStoreRepo.deleteAll()).called(1); verify(() => mockDriftStoreRepo.deleteAll()).called(1);
expect(sut.tryGet(StoreKey.accessToken), isNull); expect(sut.tryGet(StoreKey.accessToken), isNull);
expect(sut.tryGet(StoreKey.backgroundBackup), isNull); expect(sut.tryGet(StoreKey.backgroundBackup), isNull);
expect(sut.tryGet(StoreKey.groupAssetsBy), isNull); expect(sut.tryGet(StoreKey.version), isNull);
expect(sut.tryGet(StoreKey.backupFailedSince), isNull); expect(sut.tryGet(StoreKey.backupFailedSince), isNull);
}); });
}); });