refactor: update viewer quick action order handling and refactor related utilities
parent
1e5c3d7d37
commit
2d4e901c55
|
|
@ -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<T> {
|
|||
|
||||
autoPlayVideo<bool>._(139),
|
||||
albumGridView<bool>._(140),
|
||||
viewerQuickActionOrder<String>._(141),
|
||||
viewerQuickActionOrder<List<ActionButtonType>>._(141),
|
||||
|
||||
// Experimental stuff
|
||||
photoManagerCustomFilter<bool>._(1000),
|
||||
|
|
|
|||
|
|
@ -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<ActionButtonType>) => 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<ActionButtonType>) => (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<ActionButtonType>) => 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<ActionButtonType>) => (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));
|
||||
|
|
|
|||
|
|
@ -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<ActionButtonType> 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;
|
||||
|
|
|
|||
|
|
@ -55,7 +55,12 @@ enum AppSettingsEnum<T> {
|
|||
readonlyModeEnabled<bool>(StoreKey.readonlyModeEnabled, "readonlyModeEnabled", false),
|
||||
albumGridView<bool>(StoreKey.albumGridView, "albumGridView", false),
|
||||
backupRequireCharging<bool>(StoreKey.backupRequireCharging, null, false),
|
||||
backupTriggerDelay<int>(StoreKey.backupTriggerDelay, null, 30);
|
||||
backupTriggerDelay<int>(StoreKey.backupTriggerDelay, null, 30),
|
||||
viewerQuickActionOrder<List<ActionButtonType>>(
|
||||
StoreKey.viewerQuickActionOrder,
|
||||
null,
|
||||
ActionButtonBuilder.defaultQuickActionSeed,
|
||||
);
|
||||
|
||||
const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue);
|
||||
|
||||
|
|
@ -66,6 +71,7 @@ enum AppSettingsEnum<T> {
|
|||
|
||||
class AppSettingsService {
|
||||
const AppSettingsService();
|
||||
|
||||
T getSetting<T>(AppSettingsEnum<T> setting) {
|
||||
return Store.get(setting.storeKey, setting.defaultValue);
|
||||
}
|
||||
|
|
@ -74,19 +80,7 @@ class AppSettingsService {
|
|||
return Store.put(setting.storeKey, value);
|
||||
}
|
||||
|
||||
List<ActionButtonType> getViewerQuickActionOrder() {
|
||||
final stored = Store.get(StoreKey.viewerQuickActionOrder, ActionButtonBuilder.defaultQuickActionOrderStorageValue);
|
||||
return ActionButtonBuilder.parseQuickActionOrder(stored);
|
||||
}
|
||||
|
||||
Stream<List<ActionButtonType>> watchViewerQuickActionOrder() {
|
||||
return Store.watch(StoreKey.viewerQuickActionOrder).map(
|
||||
(value) =>
|
||||
ActionButtonBuilder.parseQuickActionOrder(value ?? ActionButtonBuilder.defaultQuickActionOrderStorageValue),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> setViewerQuickActionOrder(List<ActionButtonType> order) {
|
||||
return Store.put(StoreKey.viewerQuickActionOrder, ActionButtonBuilder.encodeQuickActionOrder(order));
|
||||
Stream<T> watchSetting<T>(AppSettingsEnum<T> setting) {
|
||||
return Store.watch(setting.storeKey).map((value) => value ?? setting.defaultValue);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ActionButtonType> _actionTypes = ActionButtonType.values;
|
||||
|
||||
static const int defaultQuickActionLimit = 4;
|
||||
static const String quickActionStorageDelimiter = ',';
|
||||
|
||||
static const List<ActionButtonType> _defaultQuickActionSeed = [
|
||||
static const List<ActionButtonType> defaultQuickActionSeed = [
|
||||
ActionButtonType.share,
|
||||
ActionButtonType.upload,
|
||||
ActionButtonType.edit,
|
||||
|
|
@ -184,47 +185,14 @@ class ActionButtonBuilder {
|
|||
ActionButtonType.likeActivity,
|
||||
];
|
||||
|
||||
static final Set<ActionButtonType> _quickActionSet = Set<ActionButtonType>.unmodifiable(_defaultQuickActionSeed);
|
||||
static final Set<ActionButtonType> _quickActionSet = Set<ActionButtonType>.unmodifiable(defaultQuickActionSeed);
|
||||
|
||||
static final List<ActionButtonType> defaultQuickActionOrder = List<ActionButtonType>.unmodifiable(
|
||||
_defaultQuickActionSeed,
|
||||
defaultQuickActionSeed,
|
||||
);
|
||||
|
||||
static final String defaultQuickActionOrderStorageValue = defaultQuickActionOrder
|
||||
.map((type) => type.name)
|
||||
.join(quickActionStorageDelimiter);
|
||||
|
||||
static List<ActionButtonType> get quickActionOptions => defaultQuickActionOrder;
|
||||
|
||||
static List<ActionButtonType> parseQuickActionOrder(String? stored) {
|
||||
final parsed = <ActionButtonType>[];
|
||||
|
||||
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<ActionButtonType> order) {
|
||||
final unique = <ActionButtonType>{};
|
||||
final buffer = <String>[];
|
||||
|
||||
for (final type in order) {
|
||||
if (unique.add(type)) {
|
||||
buffer.add(type.name);
|
||||
}
|
||||
}
|
||||
|
||||
final result = buffer.join(quickActionStorageDelimiter);
|
||||
return result;
|
||||
}
|
||||
|
||||
static List<ActionButtonType> buildQuickActionTypes(
|
||||
ActionButtonContext context, {
|
||||
List<ActionButtonType>? 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<Widget> 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Reference in New Issue