mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 10:37:11 -04:00 
			
		
		
		
	feat(mobile): trash and delete action (#19681)
* feat(mobile): trash and delete action * fix lint
This commit is contained in:
		
							parent
							
								
									b8e67d0ef9
								
							
						
					
					
						commit
						a644cabab6
					
				| @ -719,6 +719,7 @@ | ||||
|   "default_locale": "Default Locale", | ||||
|   "default_locale_description": "Format dates and numbers based on your browser locale", | ||||
|   "delete": "Delete", | ||||
|   "delete_action_prompt": "{count} deleted permanently", | ||||
|   "delete_album": "Delete album", | ||||
|   "delete_api_key_prompt": "Are you sure you want to delete this API key?", | ||||
|   "delete_dialog_alert": "These items will be permanently deleted from Immich and from your device", | ||||
| @ -1842,6 +1843,7 @@ | ||||
|   "total": "Total", | ||||
|   "total_usage": "Total usage", | ||||
|   "trash": "Trash", | ||||
|   "trash_action_prompt": "{count} moved to trash", | ||||
|   "trash_all": "Trash All", | ||||
|   "trash_count": "Trash {count, number}", | ||||
|   "trash_delete_asset": "Trash/Delete Asset", | ||||
|  | ||||
| @ -33,6 +33,22 @@ class DriftRemoteAssetRepository extends DriftDatabaseRepository { | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> trash(List<String> ids) { | ||||
|     return _db.batch((batch) async { | ||||
|       for (final id in ids) { | ||||
|         batch.update( | ||||
|           _db.remoteAssetEntity, | ||||
|           RemoteAssetEntityCompanion(deletedAt: Value(DateTime.now())), | ||||
|           where: (e) => e.id.equals(id), | ||||
|         ); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> delete(List<String> ids) { | ||||
|     return _db.remoteAssetEntity.deleteWhere((row) => row.id.isIn(ids)); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> updateLocation(List<String> ids, LatLng location) { | ||||
|     return _db.batch((batch) async { | ||||
|       for (final id in ids) { | ||||
|  | ||||
| @ -1,10 +1,44 @@ | ||||
| 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 DeletePermanentActionButton extends ConsumerWidget { | ||||
|   const DeletePermanentActionButton({super.key}); | ||||
|   final ActionSource source; | ||||
| 
 | ||||
|   const DeletePermanentActionButton({super.key, required this.source}); | ||||
| 
 | ||||
|   void _onTap(BuildContext context, WidgetRef ref) async { | ||||
|     if (!context.mounted) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     final result = await ref.read(actionProvider.notifier).delete(source); | ||||
|     await ref.read(timelineServiceProvider).reloadBucket(); | ||||
|     ref.read(multiSelectProvider.notifier).reset(); | ||||
| 
 | ||||
|     final successMessage = 'delete_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) { | ||||
| @ -12,6 +46,7 @@ class DeletePermanentActionButton extends ConsumerWidget { | ||||
|       maxWidth: 110.0, | ||||
|       iconData: Icons.delete_forever, | ||||
|       label: "delete_dialog_title".t(context: context), | ||||
|       onPressed: () => _onTap(context, ref), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -1,10 +1,44 @@ | ||||
| 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 TrashActionButton extends ConsumerWidget { | ||||
|   const TrashActionButton({super.key}); | ||||
|   final ActionSource source; | ||||
| 
 | ||||
|   const TrashActionButton({super.key, required this.source}); | ||||
| 
 | ||||
|   void _onTap(BuildContext context, WidgetRef ref) async { | ||||
|     if (!context.mounted) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     final result = await ref.read(actionProvider.notifier).trash(source); | ||||
|     await ref.read(timelineServiceProvider).reloadBucket(); | ||||
|     ref.read(multiSelectProvider.notifier).reset(); | ||||
| 
 | ||||
|     final successMessage = 'trash_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) { | ||||
| @ -12,6 +46,7 @@ class TrashActionButton extends ConsumerWidget { | ||||
|       maxWidth: 85.0, | ||||
|       iconData: Icons.delete_outline_rounded, | ||||
|       label: "control_bottom_app_bar_trash_from_immich".t(context: context), | ||||
|       onPressed: () => _onTap(context, ref), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -39,8 +39,10 @@ class HomeBottomAppBar extends ConsumerWidget { | ||||
|           const FavoriteActionButton(source: ActionSource.timeline), | ||||
|           const DownloadActionButton(), | ||||
|           isTrashEnable | ||||
|               ? const TrashActionButton() | ||||
|               : const DeletePermanentActionButton(), | ||||
|               ? const TrashActionButton(source: ActionSource.timeline) | ||||
|               : const DeletePermanentActionButton( | ||||
|                   source: ActionSource.timeline, | ||||
|                 ), | ||||
|           const EditDateTimeActionButton(), | ||||
|           const EditLocationActionButton(source: ActionSource.timeline), | ||||
|           const MoveToLockFolderActionButton( | ||||
|  | ||||
| @ -173,6 +173,36 @@ class ActionNotifier extends Notifier<void> { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Future<ActionResult> trash(ActionSource source) async { | ||||
|     final ids = _getOwnedRemoteForSource(source); | ||||
|     try { | ||||
|       await _service.trash(ids); | ||||
|       return ActionResult(count: ids.length, success: true); | ||||
|     } catch (error, stack) { | ||||
|       _logger.severe('Failed to trash assets', error, stack); | ||||
|       return ActionResult( | ||||
|         count: ids.length, | ||||
|         success: false, | ||||
|         error: error.toString(), | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Future<ActionResult> delete(ActionSource source) async { | ||||
|     final ids = _getOwnedRemoteForSource(source); | ||||
|     try { | ||||
|       await _service.delete(ids); | ||||
|       return ActionResult(count: ids.length, success: true); | ||||
|     } catch (error, stack) { | ||||
|       _logger.severe('Failed to delete assets', error, stack); | ||||
|       return ActionResult( | ||||
|         count: ids.length, | ||||
|         success: false, | ||||
|         error: error.toString(), | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Future<ActionResult?> editLocation( | ||||
|     ActionSource source, | ||||
|     BuildContext context, | ||||
|  | ||||
| @ -48,6 +48,10 @@ class AssetApiRepository extends ApiRepository { | ||||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   Future<void> delete(List<String> ids, bool force) async { | ||||
|     return _api.deleteAssets(AssetBulkDeleteDto(ids: ids, force: force)); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> updateVisibility( | ||||
|     List<String> ids, | ||||
|     AssetVisibilityEnum visibility, | ||||
|  | ||||
| @ -93,6 +93,16 @@ class ActionService { | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> trash(List<String> remoteIds) async { | ||||
|     await _assetApiRepository.delete(remoteIds, false); | ||||
|     await _remoteAssetRepository.trash(remoteIds); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> delete(List<String> remoteIds) async { | ||||
|     await _assetApiRepository.delete(remoteIds, true); | ||||
|     await _remoteAssetRepository.delete(remoteIds); | ||||
|   } | ||||
| 
 | ||||
|   Future<bool> editLocation( | ||||
|     List<String> remoteIds, | ||||
|     BuildContext context, | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user