diff --git a/mobile/lib/modules/asset_viewer/ui/gallery_app_bar.dart b/mobile/lib/modules/asset_viewer/ui/gallery_app_bar.dart index a16f1f04d6..31ba06d31b 100644 --- a/mobile/lib/modules/asset_viewer/ui/gallery_app_bar.dart +++ b/mobile/lib/modules/asset_viewer/ui/gallery_app_bar.dart @@ -1,5 +1,6 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/album/providers/current_album.provider.dart'; import 'package:immich_mobile/modules/album/ui/add_to_album_bottom_sheet.dart'; @@ -7,12 +8,14 @@ import 'package:immich_mobile/modules/asset_viewer/providers/image_viewer_page_s import 'package:immich_mobile/modules/asset_viewer/providers/show_controls.provider.dart'; import 'package:immich_mobile/modules/asset_viewer/ui/top_control_app_bar.dart'; import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart'; +import 'package:immich_mobile/modules/trash/providers/trashed_asset.provider.dart'; import 'package:immich_mobile/modules/home/ui/upload_dialog.dart'; import 'package:immich_mobile/modules/partner/providers/partner.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/providers/asset.provider.dart'; import 'package:immich_mobile/shared/providers/user.provider.dart'; +import 'package:immich_mobile/shared/ui/immich_toast.dart'; class GalleryAppBar extends ConsumerWidget { final Asset asset; @@ -47,6 +50,18 @@ class GalleryAppBar extends ConsumerWidget { } } + handleRestore(Asset asset) async { + final result = await ref.read(trashProvider.notifier).restoreAsset(asset); + + if (result && context.mounted) { + ImmichToast.show( + context: context, + msg: 'asset restored successfully', + gravity: ToastGravity.BOTTOM, + ); + } + } + handleUpload(Asset asset) { showDialog( context: context, @@ -91,6 +106,7 @@ class GalleryAppBar extends ConsumerWidget { asset: asset, onMoreInfoPressed: showInfo, onFavorite: toggleFavorite, + onRestorePressed: () => handleRestore(asset), onUploadPressed: asset.isLocal ? () => handleUpload(asset) : null, onDownloadPressed: asset.isLocal ? null diff --git a/mobile/lib/modules/asset_viewer/ui/top_control_app_bar.dart b/mobile/lib/modules/asset_viewer/ui/top_control_app_bar.dart index 5d7258fb24..be4a3c8b82 100644 --- a/mobile/lib/modules/asset_viewer/ui/top_control_app_bar.dart +++ b/mobile/lib/modules/asset_viewer/ui/top_control_app_bar.dart @@ -13,6 +13,7 @@ class TopControlAppBar extends HookConsumerWidget { required this.onMoreInfoPressed, required this.onDownloadPressed, required this.onAddToAlbumPressed, + required this.onRestorePressed, required this.onToggleMotionVideo, required this.isPlayingMotionVideo, required this.onFavorite, @@ -28,6 +29,7 @@ class TopControlAppBar extends HookConsumerWidget { final VoidCallback? onDownloadPressed; final VoidCallback onToggleMotionVideo; final VoidCallback onAddToAlbumPressed; + final VoidCallback onRestorePressed; final VoidCallback onActivitiesPressed; final Function(Asset) onFavorite; final bool isPlayingMotionVideo; @@ -94,7 +96,7 @@ class TopControlAppBar extends HookConsumerWidget { ); } - Widget buildAddToAlbumButtom() { + Widget buildAddToAlbumButton() { return IconButton( onPressed: () { onAddToAlbumPressed(); @@ -106,6 +108,18 @@ class TopControlAppBar extends HookConsumerWidget { ); } + Widget buildRestoreButton() { + return IconButton( + onPressed: () { + onRestorePressed(); + }, + icon: Icon( + Icons.history_rounded, + color: Colors.grey[200], + ), + ); + } + Widget buildActivitiesButton() { return IconButton( onPressed: () { @@ -170,7 +184,9 @@ class TopControlAppBar extends HookConsumerWidget { if (asset.isLocal && !asset.isRemote) buildUploadButton(), if (asset.isRemote && !asset.isLocal && !asset.isOffline && isOwner) buildDownloadButton(), - if (asset.isRemote && (isOwner || isPartner)) buildAddToAlbumButtom(), + if (asset.isRemote && (isOwner || isPartner) && !asset.isTrashed) + buildAddToAlbumButton(), + if (asset.isTrashed) buildRestoreButton(), if (album != null && album.shared) buildActivitiesButton(), buildMoreInfoButton(), ], diff --git a/mobile/lib/modules/trash/providers/trashed_asset.provider.dart b/mobile/lib/modules/trash/providers/trashed_asset.provider.dart index 165d1c0f74..6d842a286b 100644 --- a/mobile/lib/modules/trash/providers/trashed_asset.provider.dart +++ b/mobile/lib/modules/trash/providers/trashed_asset.provider.dart @@ -75,6 +75,28 @@ class TrashNotifier extends StateNotifier { return false; } + Future restoreAsset(Asset asset) async { + try { + final result = await _trashService.restoreAsset(asset); + + if (result) { + final remoteAsset = asset.isRemote; + + asset.isTrashed = false; + + if (remoteAsset) { + await _db.writeTxn(() async { + await _db.assets.put(asset); + }); + } + return true; + } + } catch (error, stack) { + _log.severe("Cannot restore asset", error, stack); + } + return false; + } + Future restoreAssets(Iterable assetList) async { try { final result = await _trashService.restoreAssets(assetList); diff --git a/mobile/lib/modules/trash/services/trash.service.dart b/mobile/lib/modules/trash/services/trash.service.dart index 96b07ca20f..a5ee055efa 100644 --- a/mobile/lib/modules/trash/services/trash.service.dart +++ b/mobile/lib/modules/trash/services/trash.service.dart @@ -30,6 +30,20 @@ class TrashService { } } + Future restoreAsset(Asset asset) async { + try { + if (asset.isRemote) { + List remoteId = [asset.remoteId!]; + + await _apiService.trashApi.restoreAssets(BulkIdsDto(ids: remoteId)); + } + return true; + } catch (error, stack) { + _log.severe("Cannot restore assets", error, stack); + return false; + } + } + Future emptyTrash() async { try { await _apiService.trashApi.emptyTrash();