refactor: proper layer archtecture

This commit is contained in:
idubnori
2025-12-10 01:06:07 +09:00
parent f874c12bee
commit 7473b959dc
12 changed files with 392 additions and 176 deletions

View File

@@ -1,5 +1,4 @@
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
@@ -73,7 +72,7 @@ enum StoreKey<T> {
autoPlayVideo<bool>._(139),
albumGridView<bool>._(140),
viewerQuickActionOrder<List<ActionButtonType>>._(141),
viewerQuickActionOrder<String>._(141),
// Experimental stuff
photoManagerCustomFilter<bool>._(1000),

View File

@@ -0,0 +1,109 @@
import 'package:immich_mobile/infrastructure/repositories/action_button_order.repository.dart';
import 'package:immich_mobile/utils/action_button.utils.dart';
/// Service for managing quick action configurations.
/// Provides business logic for building quick action types based on context.
class QuickActionService {
final ActionButtonOrderRepository _repository;
const QuickActionService(this._repository);
// Constants
static const int defaultQuickActionLimit = 4;
static const List<ActionButtonType> defaultQuickActionSeed = [
ActionButtonType.share,
ActionButtonType.upload,
ActionButtonType.edit,
ActionButtonType.add,
ActionButtonType.archive,
ActionButtonType.delete,
ActionButtonType.removeFromAlbum,
ActionButtonType.likeActivity,
];
static final Set<ActionButtonType> _quickActionSet = Set<ActionButtonType>.unmodifiable(defaultQuickActionSeed);
static final List<ActionButtonType> defaultQuickActionOrder = List<ActionButtonType>.unmodifiable(
defaultQuickActionSeed,
);
/// Get the list of available quick action options
// static List<ActionButtonType> get quickActionOptions => defaultQuickActionOrder;
/// Get the current quick action order
List<ActionButtonType> get() {
return _repository.get();
}
/// Set the quick action order
Future<void> set(List<ActionButtonType> order) async {
final normalized = _normalizeQuickActionOrder(order);
await _repository.set(normalized);
}
/// Watch for changes to quick action order
Stream<List<ActionButtonType>> watch() {
return _repository.watch();
}
/// Normalize quick action order by filtering valid types and ensuring all defaults are included
List<ActionButtonType> _normalizeQuickActionOrder(List<ActionButtonType> order) {
final ordered = <ActionButtonType>{};
for (final type in order) {
if (_quickActionSet.contains(type)) {
ordered.add(type);
}
}
ordered.addAll(defaultQuickActionSeed);
return ordered.toList(growable: false);
}
/// Build a list of quick action types based on context and custom order
List<ActionButtonType> buildQuickActionTypes(
ActionButtonContext context, {
List<ActionButtonType>? quickActionOrder,
int limit = defaultQuickActionLimit,
}) {
final normalized = _normalizeQuickActionOrder(
quickActionOrder == null || quickActionOrder.isEmpty ? defaultQuickActionOrder : quickActionOrder,
);
final seen = <ActionButtonType>{};
final result = <ActionButtonType>[];
for (final type in normalized) {
if (!_quickActionSet.contains(type)) {
continue;
}
final resolved = _resolveQuickActionType(type, context);
if (!seen.add(resolved) || !resolved.shouldShow(context)) {
continue;
}
result.add(resolved);
if (result.length >= limit) {
break;
}
}
return result;
}
/// Resolve quick action type based on context (e.g., archive -> unarchive)
ActionButtonType _resolveQuickActionType(ActionButtonType type, ActionButtonContext context) {
if (type == ActionButtonType.archive && context.isArchived) {
return ActionButtonType.unarchive;
}
if (type == ActionButtonType.delete && context.asset.isLocalOnly) {
return ActionButtonType.deleteLocal;
}
return type;
}
}

View File

@@ -0,0 +1,76 @@
import 'dart:convert';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/utils/action_button.utils.dart';
/// Repository for managing quick action button order persistence.
/// Handles serialization, deserialization, and storage operations.
class ActionButtonOrderRepository {
const ActionButtonOrderRepository();
/// Default order for quick actions
static const List<ActionButtonType> defaultOrder = [
ActionButtonType.share,
ActionButtonType.upload,
ActionButtonType.edit,
ActionButtonType.add,
ActionButtonType.archive,
ActionButtonType.delete,
ActionButtonType.removeFromAlbum,
ActionButtonType.likeActivity,
];
/// Get the current quick action order from storage
List<ActionButtonType> get() {
final json = Store.tryGet(StoreKey.viewerQuickActionOrder);
if (json == null || json.isEmpty) {
return defaultOrder;
}
final deserialized = _deserialize(json);
return deserialized.isEmpty ? defaultOrder : deserialized;
}
/// Save quick action order to storage
Future<void> set(List<ActionButtonType> order) async {
final json = _serialize(order);
await Store.put(StoreKey.viewerQuickActionOrder, json);
}
/// Watch for changes to quick action order
Stream<List<ActionButtonType>> watch() {
return Store.watch(StoreKey.viewerQuickActionOrder).map((json) {
if (json == null || json.isEmpty) {
return defaultOrder;
}
final deserialized = _deserialize(json);
return deserialized.isEmpty ? defaultOrder : deserialized;
});
}
/// Serialize a list of ActionButtonType to JSON string
String _serialize(List<ActionButtonType> order) {
return jsonEncode(order.map((type) => type.name).toList());
}
/// Deserialize a JSON string to a list of ActionButtonType
List<ActionButtonType> _deserialize(String json) {
try {
final list = jsonDecode(json) as List<dynamic>;
return list
.whereType<String>()
.map((name) {
try {
return ActionButtonType.values.byName(name);
} catch (e) {
return null;
}
})
.whereType<ActionButtonType>()
.toList();
} catch (e) {
return [];
}
}
}

View File

@@ -1,4 +1,3 @@
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';
@@ -6,7 +5,6 @@ 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
@@ -86,11 +84,6 @@ 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 List<dynamic>)
.map<ActionButtonType>((d) => ActionButtonType.values.byName(d))
.toList()
as T,
_ => null,
}
as T?;
@@ -102,7 +95,6 @@ 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);
@@ -182,11 +174,6 @@ 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 List<dynamic>)
.map<ActionButtonType>((d) => ActionButtonType.values.byName(d))
.toList()
as T,
_ => null,
}
as T?;
@@ -198,7 +185,6 @@ 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));

View File

@@ -56,7 +56,8 @@ class ViewerBottomBar extends ConsumerWidget {
source: ActionSource.viewer,
);
final quickActionTypes = ActionButtonBuilder.buildQuickActionTypes(
final quickActionService = ref.watch(quickActionServiceProvider);
final quickActionTypes = quickActionService.buildQuickActionTypes(
buttonContext,
quickActionOrder: quickActionOrder,
);
@@ -76,9 +77,10 @@ class ViewerBottomBar extends ConsumerWidget {
});
}
final actions = quickActionTypes
.map((type) => GestureDetector(onLongPress: openConfigurator, child: type.buildButton(buttonContext)))
.toList(growable: false);
final actions = ActionButtonBuilder.buildQuickActions(
buttonContext,
quickActionTypes: quickActionTypes,
).map((widget) => GestureDetector(onLongPress: openConfigurator, child: widget)).toList(growable: false);
return IgnorePointer(
ignoring: opacity < 255,

View File

@@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_reorderable_grid_view/widgets/reorderable_builder.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/services/quick_action.service.dart';
import 'package:immich_mobile/providers/infrastructure/viewer_quick_action_order.provider.dart';
import 'package:immich_mobile/utils/action_button.utils.dart';
import 'package:immich_mobile/utils/action_button_visuals.dart';
@@ -41,7 +42,7 @@ class _QuickActionConfiguratorState extends ConsumerState<QuickActionConfigurato
void _resetToDefault() {
setState(() {
_order = List<ActionButtonType>.from(ActionButtonBuilder.defaultQuickActionOrder);
_order = List<ActionButtonType>.from(QuickActionService.defaultQuickActionOrder);
_hasLocalChanges = true;
});
}
@@ -49,9 +50,7 @@ class _QuickActionConfiguratorState extends ConsumerState<QuickActionConfigurato
void _cancel() => Navigator.of(context).pop();
Future<void> _save() async {
final normalized = ActionButtonBuilder.normalizeQuickActionOrder(_order);
await ref.read(viewerQuickActionOrderProvider.notifier).setOrder(normalized);
await ref.read(viewerQuickActionOrderProvider.notifier).setOrder(_order);
_hasLocalChanges = false;
if (mounted) {
Navigator.of(context).pop();
@@ -69,8 +68,8 @@ class _QuickActionConfiguratorState extends ConsumerState<QuickActionConfigurato
if (!_hasLocalChanges && !listEquals(_order, currentOrder)) {
_order = List<ActionButtonType>.from(currentOrder);
}
final normalizedSelection = ActionButtonBuilder.normalizeQuickActionOrder(_order);
final hasChanges = !listEquals(currentOrder, normalizedSelection);
final hasChanges = !listEquals(currentOrder, _order);
return SafeArea(
child: Padding(
@@ -91,7 +90,7 @@ class _QuickActionConfiguratorState extends ConsumerState<QuickActionConfigurato
const SizedBox(height: 8),
Text(
'quick_actions_settings_description'.tr(
namedArgs: {'count': ActionButtonBuilder.defaultQuickActionLimit.toString()},
namedArgs: {'count': QuickActionService.defaultQuickActionLimit.toString()},
),
style: theme.textTheme.bodyMedium,
textAlign: TextAlign.center,

View File

@@ -1,53 +1,51 @@
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:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/services/quick_action.service.dart';
import 'package:immich_mobile/infrastructure/repositories/action_button_order.repository.dart';
import 'package:immich_mobile/utils/action_button.utils.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'viewer_quick_action_order.provider.g.dart';
@Riverpod(keepAlive: true)
class ViewerQuickActionOrder extends _$ViewerQuickActionOrder {
StreamSubscription<List<ActionButtonType>>? _subscription;
@override
List<ActionButtonType> build() {
final service = ref.watch(appSettingsServiceProvider);
final initial = ActionButtonBuilder.normalizeQuickActionOrder(
service.getSetting(AppSettingsEnum.viewerQuickActionOrder),
final actionButtonOrderRepositoryProvider = Provider<ActionButtonOrderRepository>(
(ref) => const ActionButtonOrderRepository(),
);
_subscription ??= service.watchSetting(AppSettingsEnum.viewerQuickActionOrder).listen((order) {
state = ActionButtonBuilder.normalizeQuickActionOrder(order);
});
final quickActionServiceProvider = Provider<QuickActionService>(
(ref) => QuickActionService(ref.watch(actionButtonOrderRepositoryProvider)),
);
ref.onDispose(() {
final viewerQuickActionOrderProvider = StateNotifierProvider<ViewerQuickActionOrderNotifier, List<ActionButtonType>>(
(ref) => ViewerQuickActionOrderNotifier(ref.watch(quickActionServiceProvider)),
);
class ViewerQuickActionOrderNotifier extends StateNotifier<List<ActionButtonType>> {
final QuickActionService _service;
StreamSubscription<List<ActionButtonType>>? _subscription;
ViewerQuickActionOrderNotifier(this._service) : super(_service.get()) {
_subscription = _service.watch().listen((order) {
state = order;
});
}
@override
void dispose() {
_subscription?.cancel();
_subscription = null;
});
return initial;
super.dispose();
}
Future<void> setOrder(List<ActionButtonType> order) async {
final normalized = ActionButtonBuilder.normalizeQuickActionOrder(order);
if (listEquals(state, normalized)) {
if (listEquals(state, order)) {
return;
}
final previous = state;
state = normalized;
state = order;
try {
await ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.viewerQuickActionOrder, normalized);
await _service.set(order);
} catch (error) {
state = previous;
rethrow;
}
}
}
/// Mock class for testing
abstract class ViewerQuickActionOrderInternal extends _$ViewerQuickActionOrder {}

View File

@@ -1,27 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'viewer_quick_action_order.provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$viewerQuickActionOrderHash() =>
r'd539bc6ba5fae4fa07a7c30c42d9f6aee1488f97';
/// See also [ViewerQuickActionOrder].
@ProviderFor(ViewerQuickActionOrder)
final viewerQuickActionOrderProvider =
NotifierProvider<ViewerQuickActionOrder, List<ActionButtonType>>.internal(
ViewerQuickActionOrder.new,
name: r'viewerQuickActionOrderProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$viewerQuickActionOrderHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$ViewerQuickActionOrder = Notifier<List<ActionButtonType>>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -1,7 +1,6 @@
import 'package:immich_mobile/constants/colors.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/utils/action_button.utils.dart';
enum AppSettingsEnum<T> {
loadPreview<bool>(StoreKey.loadPreview, "loadPreview", true),
@@ -56,11 +55,7 @@ enum AppSettingsEnum<T> {
albumGridView<bool>(StoreKey.albumGridView, "albumGridView", false),
backupRequireCharging<bool>(StoreKey.backupRequireCharging, null, false),
backupTriggerDelay<int>(StoreKey.backupTriggerDelay, null, 30),
viewerQuickActionOrder<List<ActionButtonType>>(
StoreKey.viewerQuickActionOrder,
null,
ActionButtonBuilder.defaultQuickActionSeed,
);
viewerQuickActionOrder<String>(StoreKey.viewerQuickActionOrder, null, '');
const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue);

View File

@@ -169,99 +169,23 @@ enum ActionButtonType {
}
}
/// Builder class for creating action button widgets.
/// This class provides simple factory methods for building action button widgets
/// from ActionButtonContext. Business logic for quick actions is handled by QuickActionService.
class ActionButtonBuilder {
static const List<ActionButtonType> _actionTypes = ActionButtonType.values;
static const int defaultQuickActionLimit = 4;
static const List<ActionButtonType> defaultQuickActionSeed = [
ActionButtonType.share,
ActionButtonType.upload,
ActionButtonType.edit,
ActionButtonType.add,
ActionButtonType.archive,
ActionButtonType.delete,
ActionButtonType.removeFromAlbum,
ActionButtonType.likeActivity,
];
static final Set<ActionButtonType> _quickActionSet = Set<ActionButtonType>.unmodifiable(defaultQuickActionSeed);
static final List<ActionButtonType> defaultQuickActionOrder = List<ActionButtonType>.unmodifiable(
defaultQuickActionSeed,
);
static List<ActionButtonType> get quickActionOptions => defaultQuickActionOrder;
static List<ActionButtonType> buildQuickActionTypes(
ActionButtonContext context, {
List<ActionButtonType>? quickActionOrder,
int limit = defaultQuickActionLimit,
}) {
final normalized = normalizeQuickActionOrder(
quickActionOrder == null || quickActionOrder.isEmpty ? defaultQuickActionOrder : quickActionOrder,
);
final seen = <ActionButtonType>{};
final result = <ActionButtonType>[];
for (final type in normalized) {
if (!_quickActionSet.contains(type)) {
continue;
}
final resolved = _resolveQuickActionType(type, context);
if (!seen.add(resolved) || !resolved.shouldShow(context)) {
continue;
}
result.add(resolved);
if (result.length >= limit) {
break;
}
}
return result;
}
/// Build a list of quick action widgets based on context and custom order.
/// Uses QuickActionService for business logic.
static List<Widget> buildQuickActions(
ActionButtonContext context, {
List<ActionButtonType>? quickActionOrder,
int limit = defaultQuickActionLimit,
required List<ActionButtonType> quickActionTypes,
}) {
final types = buildQuickActionTypes(context, quickActionOrder: quickActionOrder, limit: limit);
return types.map((type) => type.buildButton(context)).toList();
return quickActionTypes.map((type) => type.buildButton(context)).toList();
}
/// Build all available action button widgets for the given context.
static List<Widget> build(ActionButtonContext context) {
return _actionTypes.where((type) => type.shouldShow(context)).map((type) => type.buildButton(context)).toList();
}
static List<ActionButtonType> normalizeQuickActionOrder(List<ActionButtonType> order) {
final ordered = <ActionButtonType>{};
for (final type in order) {
if (_quickActionSet.contains(type)) {
ordered.add(type);
}
}
ordered.addAll(defaultQuickActionSeed);
return ordered.toList(growable: false);
}
static ActionButtonType _resolveQuickActionType(ActionButtonType type, ActionButtonContext context) {
if (type == ActionButtonType.archive && context.isArchived) {
return ActionButtonType.unarchive;
}
if (type == ActionButtonType.delete && context.asset.isLocalOnly) {
return ActionButtonType.deleteLocal;
}
return type;
}
static bool isSupportedQuickAction(ActionButtonType type) => _quickActionSet.contains(type);
}

View File

@@ -0,0 +1,150 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/services/quick_action.service.dart';
import 'package:immich_mobile/infrastructure/repositories/action_button_order.repository.dart';
import 'package:immich_mobile/utils/action_button.utils.dart';
void main() {
group('QuickActionService', () {
late QuickActionService service;
setUp(() {
// Use repository with default behavior for testing
service = const QuickActionService(ActionButtonOrderRepository());
});
test('buildQuickActionTypes should respect custom order', () {
final remoteAsset = RemoteAsset(
id: 'test-id',
name: 'test.jpg',
checksum: 'checksum',
type: AssetType.image,
ownerId: 'owner-id',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
final context = ActionButtonContext(
asset: remoteAsset,
isOwner: true,
isArchived: false,
isTrashEnabled: true,
isInLockedView: false,
currentAlbum: null,
advancedTroubleshooting: false,
isStacked: false,
source: ActionSource.viewer,
);
final customOrder = [
ActionButtonType.archive,
ActionButtonType.share,
ActionButtonType.edit,
ActionButtonType.delete,
];
final types = service.buildQuickActionTypes(context, quickActionOrder: customOrder);
expect(types.length, lessThanOrEqualTo(QuickActionService.defaultQuickActionLimit));
expect(types.first, ActionButtonType.archive);
expect(types[1], ActionButtonType.share);
});
test('buildQuickActionTypes should resolve archive to unarchive when archived', () {
final remoteAsset = RemoteAsset(
id: 'test-id',
name: 'test.jpg',
checksum: 'checksum',
type: AssetType.image,
ownerId: 'owner-id',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
final context = ActionButtonContext(
asset: remoteAsset,
isOwner: true,
isArchived: true, // archived
isTrashEnabled: true,
isInLockedView: false,
currentAlbum: null,
advancedTroubleshooting: false,
isStacked: false,
source: ActionSource.viewer,
);
final customOrder = [ActionButtonType.archive];
final types = service.buildQuickActionTypes(context, quickActionOrder: customOrder);
expect(types.contains(ActionButtonType.unarchive), isTrue);
expect(types.contains(ActionButtonType.archive), isFalse);
});
test('buildQuickActionTypes should filter types that shouldShow returns false', () {
final localAsset = LocalAsset(
id: 'local-id',
name: 'test.jpg',
checksum: 'checksum',
type: AssetType.image,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
final context = ActionButtonContext(
asset: localAsset,
isOwner: true,
isArchived: false,
isTrashEnabled: true,
isInLockedView: false,
currentAlbum: null,
advancedTroubleshooting: false,
isStacked: false,
source: ActionSource.viewer,
);
final customOrder = [
ActionButtonType.archive, // should not show for local-only asset
ActionButtonType.share,
];
final types = service.buildQuickActionTypes(context, quickActionOrder: customOrder);
expect(types.contains(ActionButtonType.archive), isFalse);
expect(types.contains(ActionButtonType.share), isTrue);
});
test('buildQuickActionTypes should respect limit', () {
final remoteAsset = RemoteAsset(
id: 'test-id',
name: 'test.jpg',
checksum: 'checksum',
type: AssetType.image,
ownerId: 'owner-id',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
final context = ActionButtonContext(
asset: remoteAsset,
isOwner: true,
isArchived: false,
isTrashEnabled: true,
isInLockedView: false,
currentAlbum: null,
advancedTroubleshooting: false,
isStacked: false,
source: ActionSource.viewer,
);
final types = service.buildQuickActionTypes(
context,
quickActionOrder: QuickActionService.defaultQuickActionOrder,
limit: 2,
);
expect(types.length, 2);
});
});
}

View File

@@ -3,6 +3,8 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/services/quick_action.service.dart';
import 'package:immich_mobile/infrastructure/repositories/action_button_order.repository.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/edit_image_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart';
@@ -1029,7 +1031,8 @@ void main() {
source: ActionSource.viewer,
);
final quickActions = ActionButtonBuilder.buildQuickActions(
final quickActionService = const QuickActionService(ActionButtonOrderRepository());
final quickActionTypes = quickActionService.buildQuickActionTypes(
context,
quickActionOrder: const [
ActionButtonType.archive,
@@ -1039,7 +1042,9 @@ void main() {
],
);
expect(quickActions.length, ActionButtonBuilder.defaultQuickActionLimit);
final quickActions = ActionButtonBuilder.buildQuickActions(context, quickActionTypes: quickActionTypes);
expect(quickActions.length, QuickActionService.defaultQuickActionLimit);
expect(quickActions.first, isA<ArchiveActionButton>());
expect(quickActions[1], isA<ShareActionButton>());
expect(quickActions[2], isA<EditImageActionButton>());