mirror of
https://github.com/immich-app/immich.git
synced 2025-12-22 17:24:56 +03:00
refactor: update viewer quick action order handling and refactor related utilities
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
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`.
|
/// Key for each possible value in the `Store`.
|
||||||
/// Defines the data type for each value
|
/// Defines the data type for each value
|
||||||
@@ -72,7 +73,7 @@ enum StoreKey<T> {
|
|||||||
|
|
||||||
autoPlayVideo<bool>._(139),
|
autoPlayVideo<bool>._(139),
|
||||||
albumGridView<bool>._(140),
|
albumGridView<bool>._(140),
|
||||||
viewerQuickActionOrder<String>._(141),
|
viewerQuickActionOrder<List<ActionButtonType>>._(141),
|
||||||
|
|
||||||
// Experimental stuff
|
// Experimental stuff
|
||||||
photoManagerCustomFilter<bool>._(1000),
|
photoManagerCustomFilter<bool>._(1000),
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.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/user.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/entities/store.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/user.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';
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
// Temporary interface until Isar is removed to make the service work with both Isar and Sqlite
|
// 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 (DateTime) => entity.intValue == null ? null : DateTime.fromMillisecondsSinceEpoch(entity.intValue!),
|
||||||
const (UserDto) =>
|
const (UserDto) =>
|
||||||
entity.strValue == null ? null : await IsarUserRepository(_db).getByUserId(entity.strValue!),
|
entity.strValue == null ? null : await IsarUserRepository(_db).getByUserId(entity.strValue!),
|
||||||
|
const (List<ActionButtonType>) => jsonDecode(entity.strValue ?? '[]') as T,
|
||||||
_ => null,
|
_ => null,
|
||||||
}
|
}
|
||||||
as T?;
|
as T?;
|
||||||
@@ -95,6 +99,7 @@ class IsarStoreRepository extends IsarDatabaseRepository implements IStoreReposi
|
|||||||
const (bool) => ((value as bool) ? 1 : 0, null),
|
const (bool) => ((value as bool) ? 1 : 0, null),
|
||||||
const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null),
|
const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null),
|
||||||
const (UserDto) => (null, (await IsarUserRepository(_db).update(value as UserDto)).id),
|
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}"),
|
_ => throw UnsupportedError("Unsupported primitive type: ${key.type} for key: ${key.name}"),
|
||||||
};
|
};
|
||||||
return StoreValue(key.id, intValue: intValue, strValue: strValue);
|
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 (DateTime) => entity.intValue == null ? null : DateTime.fromMillisecondsSinceEpoch(entity.intValue!),
|
||||||
const (UserDto) =>
|
const (UserDto) =>
|
||||||
entity.stringValue == null ? null : await DriftAuthUserRepository(_db).get(entity.stringValue!),
|
entity.stringValue == null ? null : await DriftAuthUserRepository(_db).get(entity.stringValue!),
|
||||||
|
const (List<ActionButtonType>) => jsonDecode(entity.stringValue ?? '[]') as T,
|
||||||
_ => null,
|
_ => null,
|
||||||
}
|
}
|
||||||
as T?;
|
as T?;
|
||||||
@@ -185,6 +191,7 @@ class DriftStoreRepository extends DriftDatabaseRepository implements IStoreRepo
|
|||||||
const (bool) => ((value as bool) ? 1 : 0, null),
|
const (bool) => ((value as bool) ? 1 : 0, null),
|
||||||
const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null),
|
const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null),
|
||||||
const (UserDto) => (null, (await DriftAuthUserRepository(_db).upsert(value as UserDto)).id),
|
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}"),
|
_ => throw UnsupportedError("Unsupported primitive type: ${key.type} for key: ${key.name}"),
|
||||||
};
|
};
|
||||||
return StoreEntityCompanion(id: Value(key.id), intValue: Value(intValue), stringValue: Value(strValue));
|
return StoreEntityCompanion(id: Value(key.id), intValue: Value(intValue), stringValue: Value(strValue));
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.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/utils/action_button.utils.dart';
|
import 'package:immich_mobile/utils/action_button.utils.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
@@ -13,9 +14,11 @@ class ViewerQuickActionOrder extends _$ViewerQuickActionOrder {
|
|||||||
@override
|
@override
|
||||||
List<ActionButtonType> build() {
|
List<ActionButtonType> build() {
|
||||||
final service = ref.watch(appSettingsServiceProvider);
|
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);
|
state = ActionButtonBuilder.normalizeQuickActionOrder(order);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -38,7 +41,7 @@ class ViewerQuickActionOrder extends _$ViewerQuickActionOrder {
|
|||||||
state = normalized;
|
state = normalized;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await ref.read(appSettingsServiceProvider).setViewerQuickActionOrder(normalized);
|
await ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.viewerQuickActionOrder, normalized);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
state = previous;
|
state = previous;
|
||||||
rethrow;
|
rethrow;
|
||||||
|
|||||||
@@ -55,7 +55,12 @@ enum AppSettingsEnum<T> {
|
|||||||
readonlyModeEnabled<bool>(StoreKey.readonlyModeEnabled, "readonlyModeEnabled", false),
|
readonlyModeEnabled<bool>(StoreKey.readonlyModeEnabled, "readonlyModeEnabled", false),
|
||||||
albumGridView<bool>(StoreKey.albumGridView, "albumGridView", false),
|
albumGridView<bool>(StoreKey.albumGridView, "albumGridView", false),
|
||||||
backupRequireCharging<bool>(StoreKey.backupRequireCharging, null, 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);
|
const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue);
|
||||||
|
|
||||||
@@ -66,6 +71,7 @@ enum AppSettingsEnum<T> {
|
|||||||
|
|
||||||
class AppSettingsService {
|
class AppSettingsService {
|
||||||
const AppSettingsService();
|
const AppSettingsService();
|
||||||
|
|
||||||
T getSetting<T>(AppSettingsEnum<T> setting) {
|
T getSetting<T>(AppSettingsEnum<T> setting) {
|
||||||
return Store.get(setting.storeKey, setting.defaultValue);
|
return Store.get(setting.storeKey, setting.defaultValue);
|
||||||
}
|
}
|
||||||
@@ -74,19 +80,7 @@ class AppSettingsService {
|
|||||||
return Store.put(setting.storeKey, value);
|
return Store.put(setting.storeKey, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ActionButtonType> getViewerQuickActionOrder() {
|
Stream<T> watchSetting<T>(AppSettingsEnum<T> setting) {
|
||||||
final stored = Store.get(StoreKey.viewerQuickActionOrder, ActionButtonBuilder.defaultQuickActionOrderStorageValue);
|
return Store.watch(setting.storeKey).map((value) => value ?? setting.defaultValue);
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ enum ActionButtonType {
|
|||||||
unstack,
|
unstack,
|
||||||
likeActivity;
|
likeActivity;
|
||||||
|
|
||||||
|
dynamic toJson() => name;
|
||||||
|
|
||||||
bool shouldShow(ActionButtonContext context) {
|
bool shouldShow(ActionButtonContext context) {
|
||||||
return switch (this) {
|
return switch (this) {
|
||||||
ActionButtonType.advancedInfo => context.advancedTroubleshooting,
|
ActionButtonType.advancedInfo => context.advancedTroubleshooting,
|
||||||
@@ -171,9 +173,8 @@ class ActionButtonBuilder {
|
|||||||
static const List<ActionButtonType> _actionTypes = ActionButtonType.values;
|
static const List<ActionButtonType> _actionTypes = ActionButtonType.values;
|
||||||
|
|
||||||
static const int defaultQuickActionLimit = 4;
|
static const int defaultQuickActionLimit = 4;
|
||||||
static const String quickActionStorageDelimiter = ',';
|
|
||||||
|
|
||||||
static const List<ActionButtonType> _defaultQuickActionSeed = [
|
static const List<ActionButtonType> defaultQuickActionSeed = [
|
||||||
ActionButtonType.share,
|
ActionButtonType.share,
|
||||||
ActionButtonType.upload,
|
ActionButtonType.upload,
|
||||||
ActionButtonType.edit,
|
ActionButtonType.edit,
|
||||||
@@ -184,47 +185,14 @@ class ActionButtonBuilder {
|
|||||||
ActionButtonType.likeActivity,
|
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(
|
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> 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(
|
static List<ActionButtonType> buildQuickActionTypes(
|
||||||
ActionButtonContext context, {
|
ActionButtonContext context, {
|
||||||
List<ActionButtonType>? quickActionOrder,
|
List<ActionButtonType>? quickActionOrder,
|
||||||
@@ -265,20 +233,6 @@ class ActionButtonBuilder {
|
|||||||
return types.map((type) => type.buildButton(context)).toList();
|
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) {
|
static List<Widget> build(ActionButtonContext context) {
|
||||||
return _actionTypes.where((type) => type.shouldShow(context)).map((type) => type.buildButton(context)).toList();
|
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);
|
return ordered.toList(growable: false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1015,24 +1015,6 @@ void main() {
|
|||||||
expect(nonArchivedWidgets, isNotEmpty);
|
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', () {
|
test('should build quick actions honoring custom order', () {
|
||||||
final remoteAsset = createRemoteAsset();
|
final remoteAsset = createRemoteAsset();
|
||||||
final context = ActionButtonContext(
|
final context = ActionButtonContext(
|
||||||
|
|||||||
Reference in New Issue
Block a user