diff --git a/i18n/en.json b/i18n/en.json index d81a6270ad..20eef0a70b 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1498,6 +1498,7 @@ "remove_custom_date_range": "Remove custom date range", "remove_deleted_assets": "Remove Deleted Assets", "remove_from_album": "Remove from album", + "remove_from_album_action_prompt": "{count} removed from the 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", diff --git a/mobile/lib/infrastructure/repositories/remote_album.repository.dart b/mobile/lib/infrastructure/repositories/remote_album.repository.dart index 0e49b45bc5..9bbfd5c85f 100644 --- a/mobile/lib/infrastructure/repositories/remote_album.repository.dart +++ b/mobile/lib/infrastructure/repositories/remote_album.repository.dart @@ -39,6 +39,20 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { query.orderBy(orderings); } + Future removeFromAlbum(List ids, String albumId) { + return _db.batch((batch) { + for (final assetId in ids) { + batch.delete( + _db.remoteAlbumAssetEntity, + RemoteAlbumAssetEntityCompanion( + albumId: Value(albumId), + assetId: Value(assetId), + ), + ); + } + }); + } + return query .map( (row) => row.readTable(_db.remoteAlbumEntity).toDto( diff --git a/mobile/lib/presentation/widgets/action_buttons/remove_from_album_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/remove_from_album_action_button.widget.dart index b18e10ebba..38cf654262 100644 --- a/mobile/lib/presentation/widgets/action_buttons/remove_from_album_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/remove_from_album_action_button.widget.dart @@ -1,16 +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 RemoveFromAlbumActionButton extends ConsumerWidget { - const RemoveFromAlbumActionButton({super.key}); + final ActionSource source; + const RemoveFromAlbumActionButton({super.key, required this.source}); + + void _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + final result = + await ref.read(actionProvider.notifier).removeFromAlbum(source); + await ref.read(timelineServiceProvider).reloadBucket(); + ref.read(multiSelectProvider.notifier).reset(); + + final successMessage = 'remove_from_album_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, + ); + } + } @override Widget build(BuildContext context, WidgetRef ref) { return BaseActionButton( iconData: Icons.remove_circle_outline, label: "remove_from_album".t(context: context), + onPressed: () => _onTap(context, ref); ); } } diff --git a/mobile/lib/providers/infrastructure/action.provider.dart b/mobile/lib/providers/infrastructure/action.provider.dart index 57dde6456e..ea27e20d43 100644 --- a/mobile/lib/providers/infrastructure/action.provider.dart +++ b/mobile/lib/providers/infrastructure/action.provider.dart @@ -173,6 +173,21 @@ class ActionNotifier extends Notifier { } } + Future removeFromAlbum(ActionSource source) async { + final ids = _getOwnedRemoteForSource(source); + try { + await _service.removeFromAlbum(ids); + return ActionResult(count: ids.length, success: true); + } catch (error, stack) { + _logger.severe('Failed to remove assets from album', error, stack); + return ActionResult( + count: ids.length, + success: false, + error: error.toString(), + ); + } + } + Future editLocation( ActionSource source, BuildContext context, diff --git a/mobile/lib/repositories/asset_api.repository.dart b/mobile/lib/repositories/asset_api.repository.dart index 9631428409..fcae58775c 100644 --- a/mobile/lib/repositories/asset_api.repository.dart +++ b/mobile/lib/repositories/asset_api.repository.dart @@ -66,6 +66,15 @@ class AssetApiRepository extends ApiRepository { ); } + Future removeFromAlbum( + List ids, + String albumId + ) async { + return _api.updateAlbum( + + ); + } + Future updateLocation( List ids, LatLng location, diff --git a/mobile/lib/services/action.service.dart b/mobile/lib/services/action.service.dart index b5ddbac270..72efddd825 100644 --- a/mobile/lib/services/action.service.dart +++ b/mobile/lib/services/action.service.dart @@ -5,6 +5,7 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/exif.provider.dart'; import 'package:immich_mobile/repositories/asset_api.repository.dart'; import 'package:immich_mobile/routing/router.dart'; @@ -17,6 +18,7 @@ final actionServiceProvider = Provider( ref.watch(assetApiRepositoryProvider), ref.watch(remoteAssetRepository), ref.watch(remoteExifRepository), + ref.watch(remoteAlbumRepository), ), ); @@ -24,11 +26,13 @@ class ActionService { final AssetApiRepository _assetApiRepository; final DriftRemoteAssetRepository _remoteAssetRepository; final DriftRemoteExifRepository _remoteExifRepository; + final DriftRemoteAlbumRepository _remoteAlbumRepository; const ActionService( this._assetApiRepository, this._remoteAssetRepository, this._remoteExifRepository, + this._remoteAlbumRepository, ); Future shareLink(List remoteIds, BuildContext context) async { @@ -93,6 +97,11 @@ class ActionService { ); } + Future removeFromAlbum(List remoteIds, String albumId) async { + await _assetApiRepository.removeFromAlbum(remoteIds, albumId); + await _remoteAlbumRepository.removeFromAlbum(remoteIds, albumId); + } + Future editLocation( List remoteIds, BuildContext context,