refactor: clean up

feature/rearrange-buttons-2
idubnori 2025-12-10 01:28:31 +07:00
parent 7473b959dc
commit 17361d189c
8 changed files with 38 additions and 86 deletions

@ -1,53 +1,28 @@
import 'package:immich_mobile/infrastructure/repositories/action_button_order.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/action_button_order.repository.dart';
import 'package:immich_mobile/utils/action_button.utils.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 { class QuickActionService {
final ActionButtonOrderRepository _repository; final ActionButtonOrderRepository _repository;
const QuickActionService(this._repository); const QuickActionService(this._repository);
// Constants static final Set<ActionButtonType> _quickActionSet = Set<ActionButtonType>.unmodifiable(
static const int defaultQuickActionLimit = 4; ActionButtonBuilder.defaultQuickActionOrder,
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() { List<ActionButtonType> get() {
return _repository.get(); return _repository.get();
} }
/// Set the quick action order
Future<void> set(List<ActionButtonType> order) async { Future<void> set(List<ActionButtonType> order) async {
final normalized = _normalizeQuickActionOrder(order); final normalized = _normalizeQuickActionOrder(order);
await _repository.set(normalized); await _repository.set(normalized);
} }
/// Watch for changes to quick action order
Stream<List<ActionButtonType>> watch() { Stream<List<ActionButtonType>> watch() {
return _repository.watch(); return _repository.watch();
} }
/// Normalize quick action order by filtering valid types and ensuring all defaults are included
List<ActionButtonType> _normalizeQuickActionOrder(List<ActionButtonType> order) { List<ActionButtonType> _normalizeQuickActionOrder(List<ActionButtonType> order) {
final ordered = <ActionButtonType>{}; final ordered = <ActionButtonType>{};
@ -57,19 +32,20 @@ class QuickActionService {
} }
} }
ordered.addAll(defaultQuickActionSeed); ordered.addAll(ActionButtonBuilder.defaultQuickActionOrder);
return ordered.toList(growable: false); return ordered.toList(growable: false);
} }
/// Build a list of quick action types based on context and custom order
List<ActionButtonType> buildQuickActionTypes( List<ActionButtonType> buildQuickActionTypes(
ActionButtonContext context, { ActionButtonContext context, {
List<ActionButtonType>? quickActionOrder, List<ActionButtonType>? quickActionOrder,
int limit = defaultQuickActionLimit, int limit = ActionButtonBuilder.defaultQuickActionLimit,
}) { }) {
final normalized = _normalizeQuickActionOrder( final normalized = _normalizeQuickActionOrder(
quickActionOrder == null || quickActionOrder.isEmpty ? defaultQuickActionOrder : quickActionOrder, quickActionOrder == null || quickActionOrder.isEmpty
? ActionButtonBuilder.defaultQuickActionOrder
: quickActionOrder,
); );
final seen = <ActionButtonType>{}; final seen = <ActionButtonType>{};

@ -1,60 +1,42 @@
import 'dart:convert'; import 'dart:convert';
import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/utils/action_button.utils.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 { class ActionButtonOrderRepository {
const ActionButtonOrderRepository(); const ActionButtonOrderRepository();
/// Default order for quick actions static const storeKey = StoreKey.viewerQuickActionOrder;
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() { List<ActionButtonType> get() {
final json = Store.tryGet(StoreKey.viewerQuickActionOrder); final json = Store.tryGet(storeKey);
if (json == null || json.isEmpty) { if (json == null || json.isEmpty) {
return defaultOrder; return ActionButtonBuilder.defaultQuickActionOrder;
} }
final deserialized = _deserialize(json); final deserialized = _deserialize(json);
return deserialized.isEmpty ? defaultOrder : deserialized; return deserialized.isEmpty ? ActionButtonBuilder.defaultQuickActionOrder : deserialized;
} }
/// Save quick action order to storage
Future<void> set(List<ActionButtonType> order) async { Future<void> set(List<ActionButtonType> order) async {
final json = _serialize(order); final json = _serialize(order);
await Store.put(StoreKey.viewerQuickActionOrder, json); await Store.put(storeKey, json);
} }
/// Watch for changes to quick action order
Stream<List<ActionButtonType>> watch() { Stream<List<ActionButtonType>> watch() {
return Store.watch(StoreKey.viewerQuickActionOrder).map((json) { return Store.watch(storeKey).map((json) {
if (json == null || json.isEmpty) { if (json == null || json.isEmpty) {
return defaultOrder; return ActionButtonBuilder.defaultQuickActionOrder;
} }
final deserialized = _deserialize(json); final deserialized = _deserialize(json);
return deserialized.isEmpty ? defaultOrder : deserialized; return deserialized.isEmpty ? ActionButtonBuilder.defaultQuickActionOrder : deserialized;
}); });
} }
/// Serialize a list of ActionButtonType to JSON string
String _serialize(List<ActionButtonType> order) { String _serialize(List<ActionButtonType> order) {
return jsonEncode(order.map((type) => type.name).toList()); return jsonEncode(order.map((type) => type.name).toList());
} }
/// Deserialize a JSON string to a list of ActionButtonType
List<ActionButtonType> _deserialize(String json) { List<ActionButtonType> _deserialize(String json) {
try { try {
final list = jsonDecode(json) as List<dynamic>; final list = jsonDecode(json) as List<dynamic>;

@ -77,10 +77,10 @@ class ViewerBottomBar extends ConsumerWidget {
}); });
} }
final actions = ActionButtonBuilder.buildQuickActions( final actions = quickActionTypes
buttonContext, .map((type) => type.buildButton(buttonContext))
quickActionTypes: quickActionTypes, .map((widget) => GestureDetector(onLongPress: openConfigurator, child: widget))
).map((widget) => GestureDetector(onLongPress: openConfigurator, child: widget)).toList(growable: false); .toList(growable: false);
return IgnorePointer( return IgnorePointer(
ignoring: opacity < 255, ignoring: opacity < 255,

@ -3,7 +3,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_reorderable_grid_view/widgets/reorderable_builder.dart'; import 'package:flutter_reorderable_grid_view/widgets/reorderable_builder.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/providers/infrastructure/viewer_quick_action_order.provider.dart';
import 'package:immich_mobile/utils/action_button.utils.dart'; import 'package:immich_mobile/utils/action_button.utils.dart';
import 'package:immich_mobile/utils/action_button_visuals.dart'; import 'package:immich_mobile/utils/action_button_visuals.dart';
@ -42,7 +41,7 @@ class _QuickActionConfiguratorState extends ConsumerState<QuickActionConfigurato
void _resetToDefault() { void _resetToDefault() {
setState(() { setState(() {
_order = List<ActionButtonType>.from(QuickActionService.defaultQuickActionOrder); _order = List<ActionButtonType>.from(ActionButtonBuilder.defaultQuickActionOrder);
_hasLocalChanges = true; _hasLocalChanges = true;
}); });
} }
@ -90,7 +89,7 @@ class _QuickActionConfiguratorState extends ConsumerState<QuickActionConfigurato
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'quick_actions_settings_description'.tr( 'quick_actions_settings_description'.tr(
namedArgs: {'count': QuickActionService.defaultQuickActionLimit.toString()}, namedArgs: {'count': ActionButtonBuilder.defaultQuickActionLimit.toString()},
), ),
style: theme.textTheme.bodyMedium, style: theme.textTheme.bodyMedium,
textAlign: TextAlign.center, textAlign: TextAlign.center,

@ -54,8 +54,7 @@ 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<String>(StoreKey.viewerQuickActionOrder, null, '');
const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue); const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue);
@ -74,8 +73,4 @@ class AppSettingsService {
Future<void> setSetting<T>(AppSettingsEnum<T> setting, T value) { Future<void> setSetting<T>(AppSettingsEnum<T> setting, T value) {
return Store.put(setting.storeKey, value); return Store.put(setting.storeKey, value);
} }
Stream<T> watchSetting<T>(AppSettingsEnum<T> setting) {
return Store.watch(setting.storeKey).map((value) => value ?? setting.defaultValue);
}
} }

@ -169,22 +169,22 @@ 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 { class ActionButtonBuilder {
static const List<ActionButtonType> _actionTypes = ActionButtonType.values; static const List<ActionButtonType> _actionTypes = ActionButtonType.values;
/// Build a list of quick action widgets based on context and custom order. static const int defaultQuickActionLimit = 4;
/// Uses QuickActionService for business logic.
static List<Widget> buildQuickActions( static const List<ActionButtonType> defaultQuickActionOrder = [
ActionButtonContext context, { ActionButtonType.share,
required List<ActionButtonType> quickActionTypes, ActionButtonType.upload,
}) { ActionButtonType.edit,
return quickActionTypes.map((type) => type.buildButton(context)).toList(); ActionButtonType.add,
} ActionButtonType.archive,
ActionButtonType.delete,
ActionButtonType.removeFromAlbum,
ActionButtonType.likeActivity,
];
/// Build all available action button widgets for the given context.
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();
} }

@ -46,7 +46,7 @@ void main() {
final types = service.buildQuickActionTypes(context, quickActionOrder: customOrder); final types = service.buildQuickActionTypes(context, quickActionOrder: customOrder);
expect(types.length, lessThanOrEqualTo(QuickActionService.defaultQuickActionLimit)); expect(types.length, lessThanOrEqualTo(ActionButtonBuilder.defaultQuickActionLimit));
expect(types.first, ActionButtonType.archive); expect(types.first, ActionButtonType.archive);
expect(types[1], ActionButtonType.share); expect(types[1], ActionButtonType.share);
}); });
@ -140,7 +140,7 @@ void main() {
final types = service.buildQuickActionTypes( final types = service.buildQuickActionTypes(
context, context,
quickActionOrder: QuickActionService.defaultQuickActionOrder, quickActionOrder: ActionButtonBuilder.defaultQuickActionOrder,
limit: 2, limit: 2,
); );

@ -1042,9 +1042,9 @@ void main() {
], ],
); );
final quickActions = ActionButtonBuilder.buildQuickActions(context, quickActionTypes: quickActionTypes); final quickActions = quickActionTypes.map((type) => type.buildButton(context)).toList();
expect(quickActions.length, QuickActionService.defaultQuickActionLimit); expect(quickActions.length, ActionButtonBuilder.defaultQuickActionLimit);
expect(quickActions.first, isA<ArchiveActionButton>()); expect(quickActions.first, isA<ArchiveActionButton>());
expect(quickActions[1], isA<ShareActionButton>()); expect(quickActions[1], isA<ShareActionButton>());
expect(quickActions[2], isA<EditImageActionButton>()); expect(quickActions[2], isA<EditImageActionButton>());