Skip to content

Commit 3a5d82f

Browse files
authored
chore: delete action button (#20261)
1 parent b14c768 commit 3a5d82f

11 files changed

+162
-9
lines changed

i18n/en.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -744,7 +744,8 @@
744744
"default_locale": "Default Locale",
745745
"default_locale_description": "Format dates and numbers based on your browser locale",
746746
"delete": "Delete",
747-
"delete_action_prompt": "{count} deleted permanently",
747+
"delete_action_confirmation_message": "Are you sure you want to delete this asset? This action will move the asset to the server's trash and will prompt if you want to delete it locally",
748+
"delete_action_prompt": "{count} deleted",
748749
"delete_album": "Delete album",
749750
"delete_api_key_prompt": "Are you sure you want to delete this API key?",
750751
"delete_dialog_alert": "These items will be permanently deleted from Immich and from your device",
@@ -762,6 +763,8 @@
762763
"delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only",
763764
"delete_local_dialog_ok_force": "Delete Anyway",
764765
"delete_others": "Delete others",
766+
"delete_permanently": "Delete permanently",
767+
"delete_permanently_action_prompt": "{count} deleted permanently",
765768
"delete_shared_link": "Delete shared link",
766769
"delete_shared_link_dialog_title": "Delete Shared Link",
767770
"delete_tag": "Delete tag",
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:fluttertoast/fluttertoast.dart';
3+
import 'package:hooks_riverpod/hooks_riverpod.dart';
4+
import 'package:immich_mobile/constants/enums.dart';
5+
import 'package:immich_mobile/domain/utils/event_stream.dart';
6+
import 'package:immich_mobile/extensions/build_context_extensions.dart';
7+
import 'package:immich_mobile/extensions/translate_extensions.dart';
8+
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
9+
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
10+
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
11+
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
12+
import 'package:immich_mobile/widgets/common/immich_toast.dart';
13+
14+
/// This delete action has the following behavior:
15+
/// - Set the deletedAt information, put the asset in the trash in the server
16+
/// which will be permanently deleted after the number of days configure by the admin
17+
/// - Prompt to delete the asset locally
18+
class DeleteActionButton extends ConsumerWidget {
19+
final ActionSource source;
20+
final bool showConfirmation;
21+
const DeleteActionButton({super.key, required this.source, this.showConfirmation = false});
22+
23+
void _onTap(BuildContext context, WidgetRef ref) async {
24+
if (!context.mounted) {
25+
return;
26+
}
27+
28+
if (showConfirmation) {
29+
final confirm = await showDialog<bool>(
30+
context: context,
31+
builder: (context) => AlertDialog(
32+
title: Text('delete'.t(context: context)),
33+
content: Text('delete_action_confirmation_message'.t(context: context)),
34+
actions: [
35+
TextButton(
36+
onPressed: () => Navigator.of(context).pop(false),
37+
child: Text('cancel'.t(context: context)),
38+
),
39+
TextButton(
40+
onPressed: () => Navigator.of(context).pop(true),
41+
child: Text(
42+
'confirm'.t(context: context),
43+
style: TextStyle(
44+
color: context.colorScheme.error,
45+
),
46+
),
47+
),
48+
],
49+
),
50+
);
51+
if (confirm != true) return;
52+
}
53+
54+
final result = await ref.read(actionProvider.notifier).trashRemoteAndDeleteLocal(source);
55+
ref.read(multiSelectProvider.notifier).reset();
56+
57+
if (source == ActionSource.viewer) {
58+
EventStream.shared.emit(const ViewerReloadAssetEvent());
59+
}
60+
61+
final successMessage = 'delete_action_prompt'.t(
62+
context: context,
63+
args: {'count': result.count.toString()},
64+
);
65+
66+
if (context.mounted) {
67+
ImmichToast.show(
68+
context: context,
69+
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
70+
gravity: ToastGravity.BOTTOM,
71+
toastType: result.success ? ToastType.success : ToastType.error,
72+
);
73+
}
74+
}
75+
76+
@override
77+
Widget build(BuildContext context, WidgetRef ref) {
78+
return BaseActionButton(
79+
maxWidth: 110.0,
80+
iconData: Icons.delete_sweep_outlined,
81+
label: "delete".t(context: context),
82+
onPressed: () => _onTap(context, ref),
83+
);
84+
}
85+
}

mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
1010
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
1111
import 'package:immich_mobile/widgets/common/immich_toast.dart';
1212

13+
/// This delete action has the following behavior:
14+
/// - Prompt to delete the asset locally
1315
class DeleteLocalActionButton extends ConsumerWidget {
1416
final ActionSource source;
1517

@@ -27,6 +29,10 @@ class DeleteLocalActionButton extends ConsumerWidget {
2729
EventStream.shared.emit(const ViewerReloadAssetEvent());
2830
}
2931

32+
if (result.count == 0) {
33+
return;
34+
}
35+
3036
final successMessage = 'delete_local_action_prompt'.t(
3137
context: context,
3238
args: {'count': result.count.toString()},

mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
1010
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
1111
import 'package:immich_mobile/widgets/common/immich_toast.dart';
1212

13+
/// This delete action has the following behavior:
14+
/// - Delete permanently on the server
15+
/// - Prompt to delete the asset locally
1316
class DeletePermanentActionButton extends ConsumerWidget {
1417
final ActionSource source;
1518

@@ -27,7 +30,7 @@ class DeletePermanentActionButton extends ConsumerWidget {
2730
EventStream.shared.emit(const ViewerReloadAssetEvent());
2831
}
2932

30-
final successMessage = 'delete_action_prompt'.t(
33+
final successMessage = 'delete_permanently_action_prompt'.t(
3134
context: context,
3235
args: {'count': result.count.toString()},
3336
);
@@ -47,7 +50,7 @@ class DeletePermanentActionButton extends ConsumerWidget {
4750
return BaseActionButton(
4851
maxWidth: 110.0,
4952
iconData: Icons.delete_forever,
50-
label: "delete_dialog_title".t(context: context),
53+
label: "delete_permanently".t(context: context),
5154
onPressed: () => _onTap(context, ref),
5255
);
5356
}

mobile/lib/presentation/widgets/action_buttons/delete_trash_action_button.widget.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
77
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
88
import 'package:immich_mobile/widgets/common/immich_toast.dart';
99

10+
/// This delete action has the following behavior:
11+
/// - Delete permanently on the server
12+
/// - Prompt to delete the asset locally
13+
///
14+
/// This action is used when the asset is selected in multi-selection mode in the trash page
1015
class DeleteTrashActionButton extends ConsumerWidget {
1116
final ActionSource source;
1217

mobile/lib/presentation/widgets/action_buttons/trash_action_button.widget.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
1010
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
1111
import 'package:immich_mobile/widgets/common/immich_toast.dart';
1212

13+
/// This delete action has the following behavior:
14+
/// - Set the deletedAt information, put the asset in the trash in the server
15+
/// which will be permanently deleted after the number of days configure by the admin
1316
class TrashActionButton extends ConsumerWidget {
1417
final ActionSource source;
1518

mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import 'package:immich_mobile/constants/enums.dart';
44
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
55
import 'package:immich_mobile/extensions/build_context_extensions.dart';
66
import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart';
7+
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart';
8+
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart';
79
import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart';
810
import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart';
911
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
@@ -39,6 +41,14 @@ class ViewerBottomBar extends ConsumerWidget {
3941
const ShareActionButton(source: ActionSource.viewer),
4042
if (asset.isLocalOnly) const UploadActionButton(source: ActionSource.viewer),
4143
if (asset.hasRemote && isOwner) const ArchiveActionButton(source: ActionSource.viewer),
44+
asset.isLocalOnly
45+
? const DeleteLocalActionButton(
46+
source: ActionSource.viewer,
47+
)
48+
: const DeleteActionButton(
49+
source: ActionSource.viewer,
50+
showConfirmation: true,
51+
),
4252
];
4353

4454
return IgnorePointer(
@@ -60,7 +70,7 @@ class ViewerBottomBar extends ConsumerWidget {
6070
),
6171
),
6272
child: Container(
63-
height: context.padding.bottom + (asset.isVideo ? 160 : 80),
73+
height: context.padding.bottom + (asset.isVideo ? 160 : 90),
6474
color: Colors.black.withAlpha(125),
6575
padding: EdgeInsets.only(bottom: context.padding.bottom),
6676
child: Column(

mobile/lib/presentation/widgets/asset_viewer/bottom_sheet.widget.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:immich_mobile/domain/models/exif.model.dart';
77
import 'package:immich_mobile/extensions/build_context_extensions.dart';
88
import 'package:immich_mobile/extensions/translate_extensions.dart';
99
import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart';
10+
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart';
1011
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart';
1112
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart';
1213
import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart';
@@ -56,6 +57,7 @@ class AssetDetailBottomSheet extends ConsumerWidget {
5657
isTrashEnable
5758
? const TrashActionButton(source: ActionSource.viewer)
5859
: const DeletePermanentActionButton(source: ActionSource.viewer),
60+
const DeleteActionButton(source: ActionSource.viewer),
5961
const MoveToLockFolderActionButton(
6062
source: ActionSource.viewer,
6163
),

mobile/lib/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:immich_mobile/constants/enums.dart';
55
import 'package:immich_mobile/domain/models/album/album.model.dart';
66
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
77
import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart';
8+
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart';
89
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart';
910
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart';
1011
import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart';
@@ -80,6 +81,7 @@ class GeneralBottomSheet extends ConsumerWidget {
8081
: const DeletePermanentActionButton(
8182
source: ActionSource.timeline,
8283
),
84+
const DeleteActionButton(source: ActionSource.timeline),
8385
if (multiselect.hasLocal || multiselect.hasMerged) ...[
8486
const DeleteLocalActionButton(source: ActionSource.timeline),
8587
],

mobile/lib/providers/infrastructure/action.provider.dart

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,22 @@ class ActionNotifier extends Notifier<void> {
230230
}
231231
}
232232

233+
Future<ActionResult> trashRemoteAndDeleteLocal(ActionSource source) async {
234+
final ids = _getOwnedRemoteIdsForSource(source);
235+
final localIds = _getLocalIdsForSource(source);
236+
try {
237+
await _service.trashRemoteAndDeleteLocal(ids, localIds);
238+
return ActionResult(count: ids.length, success: true);
239+
} catch (error, stack) {
240+
_logger.severe('Failed to delete assets', error, stack);
241+
return ActionResult(
242+
count: ids.length,
243+
success: false,
244+
error: error.toString(),
245+
);
246+
}
247+
}
248+
233249
Future<ActionResult> deleteRemoteAndLocal(ActionSource source) async {
234250
final ids = _getOwnedRemoteIdsForSource(source);
235251
final localIds = _getLocalIdsForSource(source);
@@ -249,8 +265,8 @@ class ActionNotifier extends Notifier<void> {
249265
Future<ActionResult> deleteLocal(ActionSource source) async {
250266
final ids = _getLocalIdsForSource(source);
251267
try {
252-
await _service.deleteLocal(ids);
253-
return ActionResult(count: ids.length, success: true);
268+
final deletedCount = await _service.deleteLocal(ids);
269+
return ActionResult(count: deletedCount, success: true);
254270
} catch (error, stack) {
255271
_logger.severe('Failed to delete assets', error, stack);
256272
return ActionResult(

0 commit comments

Comments
 (0)