diff --git a/i18n/en.json b/i18n/en.json index 1eff23eca9..3bd31638ec 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1247,6 +1247,7 @@ "more": "More", "move": "Move", "move_off_locked_folder": "Move out of locked folder", + "move_to_lock_folder_action_prompt": "{count} added to the locked folder", "move_to_locked_folder": "Move to locked folder", "move_to_locked_folder_confirmation": "These photos and video will be removed from all albums, and only viewable from the locked folder", "moved_to_archive": "Moved {count, plural, one {# asset} other {# assets}} to archive", @@ -1497,6 +1498,7 @@ "remove_deleted_assets": "Remove Deleted Assets", "remove_from_album": "Remove from album", "remove_from_favorites": "Remove from favorites", + "remove_from_lock_folder_action_prompt": "{count} removed from the locked folder", "remove_from_locked_folder": "Remove from locked folder", "remove_from_locked_folder_confirmation": "Are you sure you want to move these photos and videos out of the locked folder? They will be visible in your library.", "remove_from_shared_link": "Remove from shared link", diff --git a/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart index ba93c86109..b150c13359 100644 --- a/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart @@ -1,10 +1,51 @@ import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; class MoveToLockFolderActionButton extends ConsumerWidget { - const MoveToLockFolderActionButton({super.key}); + final ActionSource source; + + const MoveToLockFolderActionButton({super.key, required this.source}); + + void _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + final result = + await ref.read(actionProvider.notifier).moveToLockFolder(source); + await ref.read(timelineServiceProvider).reloadBucket(); + ref.read(multiSelectProvider.notifier).reset(); + + final successMessage = 'move_to_lock_folder_action_prompt'.t( + context: context, + args: {'count': result.count.toString()}, + ); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success + ? successMessage + : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } + } + + void viewerAction(WidgetRef _) { + UnimplementedError( + "Viewer action for move to locked folder is not implemented yet.", + ); + } @override Widget build(BuildContext context, WidgetRef ref) { @@ -12,6 +53,7 @@ class MoveToLockFolderActionButton extends ConsumerWidget { maxWidth: 100.0, iconData: Icons.lock_outline_rounded, label: "move_to_locked_folder".t(context: context), + onPressed: () => _onTap(context, ref), ); } } diff --git a/mobile/lib/presentation/widgets/action_buttons/remove_from_lock_folder_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/remove_from_lock_folder_action_button.widget.dart index 24c84281d4..e17f655f01 100644 --- a/mobile/lib/presentation/widgets/action_buttons/remove_from_lock_folder_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/remove_from_lock_folder_action_button.widget.dart @@ -1,10 +1,51 @@ import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; class RemoveFromLockFolderActionButton extends ConsumerWidget { - const RemoveFromLockFolderActionButton({super.key}); + final ActionSource source; + + const RemoveFromLockFolderActionButton({super.key, required this.source}); + + void _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + final result = + await ref.read(actionProvider.notifier).removeFromLockFolder(source); + await ref.read(timelineServiceProvider).reloadBucket(); + ref.read(multiSelectProvider.notifier).reset(); + + final successMessage = 'remove_from_lock_folder_action_prompt'.t( + context: context, + args: {'count': result.count.toString()}, + ); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success + ? successMessage + : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } + } + + void viewerAction(WidgetRef _) { + UnimplementedError( + "Viewer action for remove from locked folder is not implemented yet.", + ); + } @override Widget build(BuildContext context, WidgetRef ref) { @@ -12,6 +53,7 @@ class RemoveFromLockFolderActionButton extends ConsumerWidget { maxWidth: 100.0, iconData: Icons.lock_open_rounded, label: "remove_from_locked_folder".t(context: context), + onPressed: () => _onTap(context, ref), ); } } diff --git a/mobile/lib/presentation/widgets/bottom_app_bar/home_bottom_app_bar.widget.dart b/mobile/lib/presentation/widgets/bottom_app_bar/home_bottom_app_bar.widget.dart index 5c93d37696..8559cc99ad 100644 --- a/mobile/lib/presentation/widgets/bottom_app_bar/home_bottom_app_bar.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_app_bar/home_bottom_app_bar.widget.dart @@ -44,7 +44,9 @@ class HomeBottomAppBar extends ConsumerWidget { : const DeletePermanentActionButton(), const EditDateTimeActionButton(), const EditLocationActionButton(), - const MoveToLockFolderActionButton(), + const MoveToLockFolderActionButton( + source: ActionSource.timeline, + ), const StackActionButton(), ], if (multiselect.hasLocal) ...[ diff --git a/mobile/lib/providers/infrastructure/action.provider.dart b/mobile/lib/providers/infrastructure/action.provider.dart index 0b7fcd1469..6b4c675929 100644 --- a/mobile/lib/providers/infrastructure/action.provider.dart +++ b/mobile/lib/providers/infrastructure/action.provider.dart @@ -125,4 +125,34 @@ class ActionNotifier extends Notifier { ); } } + + Future moveToLockFolder(ActionSource source) async { + final ids = _getIdsForSource(source); + try { + await _service.moveToLockFolder(ids); + return ActionResult(count: ids.length, success: true); + } catch (error, stack) { + _logger.severe('Failed to move assets to lock folder', error, stack); + return ActionResult( + count: ids.length, + success: false, + error: error.toString(), + ); + } + } + + Future removeFromLockFolder(ActionSource source) async { + final ids = _getIdsForSource(source); + try { + await _service.removeFromLockFolder(ids); + return ActionResult(count: ids.length, success: true); + } catch (error, stack) { + _logger.severe('Failed to remove assets from lock folder', error, stack); + return ActionResult( + count: ids.length, + success: false, + error: error.toString(), + ); + } + } } diff --git a/mobile/lib/services/action.service.dart b/mobile/lib/services/action.service.dart index afc4e3f776..3b7c04ea94 100644 --- a/mobile/lib/services/action.service.dart +++ b/mobile/lib/services/action.service.dart @@ -48,4 +48,26 @@ class ActionService { AssetVisibility.timeline, ); } + + Future moveToLockFolder(List remoteIds) async { + await _assetApiRepository.updateVisibility( + remoteIds, + AssetVisibilityEnum.locked, + ); + await _remoteAssetRepository.updateVisibility( + remoteIds, + AssetVisibility.locked, + ); + } + + Future removeFromLockFolder(List remoteIds) async { + await _assetApiRepository.updateVisibility( + remoteIds, + AssetVisibilityEnum.timeline, + ); + await _remoteAssetRepository.updateVisibility( + remoteIds, + AssetVisibility.timeline, + ); + } }