From 2d4e901c55e8740a036f71a78633cbc3e04685de Mon Sep 17 00:00:00 2001 From: idubnori Date: Tue, 9 Dec 2025 14:56:57 +0900 Subject: [PATCH] refactor: update viewer quick action order handling and refactor related utilities --- mobile/lib/domain/models/store.model.dart | 3 +- .../repositories/store.repository.dart | 7 +++ .../viewer_quick_action_order.provider.dart | 9 ++- mobile/lib/services/app_settings.service.dart | 24 +++----- mobile/lib/utils/action_button.utils.dart | 58 ++----------------- .../test/utils/action_button_utils_test.dart | 18 ------ 6 files changed, 30 insertions(+), 89 deletions(-) diff --git a/mobile/lib/domain/models/store.model.dart b/mobile/lib/domain/models/store.model.dart index ae0112acb3..c27b1f1ca8 100644 --- a/mobile/lib/domain/models/store.model.dart +++ b/mobile/lib/domain/models/store.model.dart @@ -1,4 +1,5 @@ import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/utils/action_button.utils.dart'; /// Key for each possible value in the `Store`. /// Defines the data type for each value @@ -72,7 +73,7 @@ enum StoreKey { autoPlayVideo._(139), albumGridView._(140), - viewerQuickActionOrder._(141), + viewerQuickActionOrder>._(141), // Experimental stuff photoManagerCustomFilter._(1000), diff --git a/mobile/lib/infrastructure/repositories/store.repository.dart b/mobile/lib/infrastructure/repositories/store.repository.dart index d4e34a02f5..0050eb9911 100644 --- a/mobile/lib/infrastructure/repositories/store.repository.dart +++ b/mobile/lib/infrastructure/repositories/store.repository.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:drift/drift.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; @@ -5,6 +7,7 @@ import 'package:immich_mobile/infrastructure/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; +import 'package:immich_mobile/utils/action_button.utils.dart'; import 'package:isar/isar.dart'; // Temporary interface until Isar is removed to make the service work with both Isar and Sqlite @@ -84,6 +87,7 @@ class IsarStoreRepository extends IsarDatabaseRepository implements IStoreReposi const (DateTime) => entity.intValue == null ? null : DateTime.fromMillisecondsSinceEpoch(entity.intValue!), const (UserDto) => entity.strValue == null ? null : await IsarUserRepository(_db).getByUserId(entity.strValue!), + const (List) => jsonDecode(entity.strValue ?? '[]') as T, _ => null, } as T?; @@ -95,6 +99,7 @@ class IsarStoreRepository extends IsarDatabaseRepository implements IStoreReposi const (bool) => ((value as bool) ? 1 : 0, null), const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null), const (UserDto) => (null, (await IsarUserRepository(_db).update(value as UserDto)).id), + const (List) => (null, jsonEncode(value)), _ => throw UnsupportedError("Unsupported primitive type: ${key.type} for key: ${key.name}"), }; return StoreValue(key.id, intValue: intValue, strValue: strValue); @@ -174,6 +179,7 @@ class DriftStoreRepository extends DriftDatabaseRepository implements IStoreRepo const (DateTime) => entity.intValue == null ? null : DateTime.fromMillisecondsSinceEpoch(entity.intValue!), const (UserDto) => entity.stringValue == null ? null : await DriftAuthUserRepository(_db).get(entity.stringValue!), + const (List) => jsonDecode(entity.stringValue ?? '[]') as T, _ => null, } as T?; @@ -185,6 +191,7 @@ class DriftStoreRepository extends DriftDatabaseRepository implements IStoreRepo const (bool) => ((value as bool) ? 1 : 0, null), const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null), const (UserDto) => (null, (await DriftAuthUserRepository(_db).upsert(value as UserDto)).id), + const (List) => (null, jsonEncode(value)), _ => throw UnsupportedError("Unsupported primitive type: ${key.type} for key: ${key.name}"), }; return StoreEntityCompanion(id: Value(key.id), intValue: Value(intValue), stringValue: Value(strValue)); diff --git a/mobile/lib/providers/infrastructure/viewer_quick_action_order.provider.dart b/mobile/lib/providers/infrastructure/viewer_quick_action_order.provider.dart index 10d2a8835c..c9e48d63c6 100644 --- a/mobile/lib/providers/infrastructure/viewer_quick_action_order.provider.dart +++ b/mobile/lib/providers/infrastructure/viewer_quick_action_order.provider.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/utils/action_button.utils.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -13,9 +14,11 @@ class ViewerQuickActionOrder extends _$ViewerQuickActionOrder { @override List build() { final service = ref.watch(appSettingsServiceProvider); - final initial = ActionButtonBuilder.normalizeQuickActionOrder(service.getViewerQuickActionOrder()); + final initial = ActionButtonBuilder.normalizeQuickActionOrder( + service.getSetting(AppSettingsEnum.viewerQuickActionOrder), + ); - _subscription ??= service.watchViewerQuickActionOrder().listen((order) { + _subscription ??= service.watchSetting(AppSettingsEnum.viewerQuickActionOrder).listen((order) { state = ActionButtonBuilder.normalizeQuickActionOrder(order); }); @@ -38,7 +41,7 @@ class ViewerQuickActionOrder extends _$ViewerQuickActionOrder { state = normalized; try { - await ref.read(appSettingsServiceProvider).setViewerQuickActionOrder(normalized); + await ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.viewerQuickActionOrder, normalized); } catch (error) { state = previous; rethrow; diff --git a/mobile/lib/services/app_settings.service.dart b/mobile/lib/services/app_settings.service.dart index 1c48d9e868..dc2c78331f 100644 --- a/mobile/lib/services/app_settings.service.dart +++ b/mobile/lib/services/app_settings.service.dart @@ -55,7 +55,12 @@ enum AppSettingsEnum { readonlyModeEnabled(StoreKey.readonlyModeEnabled, "readonlyModeEnabled", false), albumGridView(StoreKey.albumGridView, "albumGridView", false), backupRequireCharging(StoreKey.backupRequireCharging, null, false), - backupTriggerDelay(StoreKey.backupTriggerDelay, null, 30); + backupTriggerDelay(StoreKey.backupTriggerDelay, null, 30), + viewerQuickActionOrder>( + StoreKey.viewerQuickActionOrder, + null, + ActionButtonBuilder.defaultQuickActionSeed, + ); const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue); @@ -66,6 +71,7 @@ enum AppSettingsEnum { class AppSettingsService { const AppSettingsService(); + T getSetting(AppSettingsEnum setting) { return Store.get(setting.storeKey, setting.defaultValue); } @@ -74,19 +80,7 @@ class AppSettingsService { return Store.put(setting.storeKey, value); } - List getViewerQuickActionOrder() { - final stored = Store.get(StoreKey.viewerQuickActionOrder, ActionButtonBuilder.defaultQuickActionOrderStorageValue); - return ActionButtonBuilder.parseQuickActionOrder(stored); - } - - Stream> watchViewerQuickActionOrder() { - return Store.watch(StoreKey.viewerQuickActionOrder).map( - (value) => - ActionButtonBuilder.parseQuickActionOrder(value ?? ActionButtonBuilder.defaultQuickActionOrderStorageValue), - ); - } - - Future setViewerQuickActionOrder(List order) { - return Store.put(StoreKey.viewerQuickActionOrder, ActionButtonBuilder.encodeQuickActionOrder(order)); + Stream watchSetting(AppSettingsEnum setting) { + return Store.watch(setting.storeKey).map((value) => value ?? setting.defaultValue); } } diff --git a/mobile/lib/utils/action_button.utils.dart b/mobile/lib/utils/action_button.utils.dart index 8d07e23cbf..b10dc7542b 100644 --- a/mobile/lib/utils/action_button.utils.dart +++ b/mobile/lib/utils/action_button.utils.dart @@ -67,6 +67,8 @@ enum ActionButtonType { unstack, likeActivity; + dynamic toJson() => name; + bool shouldShow(ActionButtonContext context) { return switch (this) { ActionButtonType.advancedInfo => context.advancedTroubleshooting, @@ -171,9 +173,8 @@ class ActionButtonBuilder { static const List _actionTypes = ActionButtonType.values; static const int defaultQuickActionLimit = 4; - static const String quickActionStorageDelimiter = ','; - static const List _defaultQuickActionSeed = [ + static const List defaultQuickActionSeed = [ ActionButtonType.share, ActionButtonType.upload, ActionButtonType.edit, @@ -184,47 +185,14 @@ class ActionButtonBuilder { ActionButtonType.likeActivity, ]; - static final Set _quickActionSet = Set.unmodifiable(_defaultQuickActionSeed); + static final Set _quickActionSet = Set.unmodifiable(defaultQuickActionSeed); static final List defaultQuickActionOrder = List.unmodifiable( - _defaultQuickActionSeed, + defaultQuickActionSeed, ); - static final String defaultQuickActionOrderStorageValue = defaultQuickActionOrder - .map((type) => type.name) - .join(quickActionStorageDelimiter); - static List get quickActionOptions => defaultQuickActionOrder; - static List parseQuickActionOrder(String? stored) { - final parsed = []; - - if (stored != null && stored.trim().isNotEmpty) { - for (final name in stored.split(quickActionStorageDelimiter)) { - final type = _typeByName(name.trim()); - if (type != null) { - parsed.add(type); - } - } - } - - return normalizeQuickActionOrder(parsed); - } - - static String encodeQuickActionOrder(List order) { - final unique = {}; - final buffer = []; - - for (final type in order) { - if (unique.add(type)) { - buffer.add(type.name); - } - } - - final result = buffer.join(quickActionStorageDelimiter); - return result; - } - static List buildQuickActionTypes( ActionButtonContext context, { List? quickActionOrder, @@ -265,20 +233,6 @@ class ActionButtonBuilder { return types.map((type) => type.buildButton(context)).toList(); } - static ActionButtonType? _typeByName(String name) { - if (name.isEmpty) { - return null; - } - - for (final type in ActionButtonType.values) { - if (type.name == name) { - return type; - } - } - - return null; - } - static List build(ActionButtonContext context) { return _actionTypes.where((type) => type.shouldShow(context)).map((type) => type.buildButton(context)).toList(); } @@ -292,7 +246,7 @@ class ActionButtonBuilder { } } - ordered.addAll(_defaultQuickActionSeed); + ordered.addAll(defaultQuickActionSeed); return ordered.toList(growable: false); } diff --git a/mobile/test/utils/action_button_utils_test.dart b/mobile/test/utils/action_button_utils_test.dart index ed5691dafd..bfc572624f 100644 --- a/mobile/test/utils/action_button_utils_test.dart +++ b/mobile/test/utils/action_button_utils_test.dart @@ -1015,24 +1015,6 @@ void main() { expect(nonArchivedWidgets, isNotEmpty); }); - test('should encode and parse quick action order consistently', () { - final encoded = ActionButtonBuilder.encodeQuickActionOrder([ - ActionButtonType.edit, - ActionButtonType.share, - ActionButtonType.archive, - ]); - - final decoded = ActionButtonBuilder.parseQuickActionOrder(encoded); - - final expectedOrder = ActionButtonBuilder.normalizeQuickActionOrder([ - ActionButtonType.edit, - ActionButtonType.share, - ActionButtonType.archive, - ]); - - expect(decoded, expectedOrder); - }); - test('should build quick actions honoring custom order', () { final remoteAsset = createRemoteAsset(); final context = ActionButtonContext(