diff --git a/mobile/lib/domain/models/asset/remote_asset.model.dart b/mobile/lib/domain/models/asset/remote_asset.model.dart index a810877dcc..b370825fdd 100644 --- a/mobile/lib/domain/models/asset/remote_asset.model.dart +++ b/mobile/lib/domain/models/asset/remote_asset.model.dart @@ -11,6 +11,7 @@ class RemoteAsset extends BaseAsset { final String ownerId; final String? stackId; final DateTime? uploadedAt; + final DateTime? deletedAt; const RemoteAsset({ required this.id, @@ -31,6 +32,7 @@ class RemoteAsset extends BaseAsset { super.livePhotoVideoId, this.stackId, required super.isEdited, + this.deletedAt, }) : localAssetId = localId; @override @@ -48,6 +50,8 @@ class RemoteAsset extends BaseAsset { @override bool get isEditable => isImage && !isMotionPhoto && !isAnimatedImage; + bool get isTrashed => deletedAt != null; + @override String toString() { return '''Asset { @@ -86,7 +90,8 @@ class RemoteAsset extends BaseAsset { thumbHash == other.thumbHash && visibility == other.visibility && stackId == other.stackId && - uploadedAt == other.uploadedAt; + uploadedAt == other.uploadedAt && + deletedAt == other.deletedAt; } @override @@ -98,7 +103,8 @@ class RemoteAsset extends BaseAsset { thumbHash.hashCode ^ visibility.hashCode ^ stackId.hashCode ^ - uploadedAt.hashCode; + uploadedAt.hashCode ^ + deletedAt.hashCode; RemoteAsset copyWith({ String? id, @@ -119,6 +125,7 @@ class RemoteAsset extends BaseAsset { String? livePhotoVideoId, String? stackId, bool? isEdited, + DateTime? deletedAt, }) { return RemoteAsset( id: id ?? this.id, @@ -139,6 +146,7 @@ class RemoteAsset extends BaseAsset { livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, stackId: stackId ?? this.stackId, isEdited: isEdited ?? this.isEdited, + deletedAt: deletedAt ?? this.deletedAt, ); } } @@ -156,6 +164,7 @@ class RemoteAssetExif extends RemoteAsset { required super.createdAt, required super.updatedAt, super.uploadedAt, + super.deletedAt, super.width, super.height, super.durationMs, @@ -193,6 +202,7 @@ class RemoteAssetExif extends RemoteAsset { DateTime? createdAt, DateTime? updatedAt, DateTime? uploadedAt, + DateTime? deletedAt, int? width, int? height, int? durationMs, @@ -214,6 +224,7 @@ class RemoteAssetExif extends RemoteAsset { createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, uploadedAt: uploadedAt ?? this.uploadedAt, + deletedAt: deletedAt ?? this.deletedAt, width: width ?? this.width, height: height ?? this.height, durationMs: durationMs ?? this.durationMs, diff --git a/mobile/lib/infrastructure/entities/remote_asset.entity.dart b/mobile/lib/infrastructure/entities/remote_asset.entity.dart index 8644667168..ad1cec5641 100644 --- a/mobile/lib/infrastructure/entities/remote_asset.entity.dart +++ b/mobile/lib/infrastructure/entities/remote_asset.entity.dart @@ -74,5 +74,6 @@ extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData { localId: localId, stackId: stackId, isEdited: isEdited, + deletedAt: deletedAt, ); } diff --git a/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart index d2df013369..267a9f55e6 100644 --- a/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart @@ -18,8 +18,15 @@ class DeletePermanentActionButton extends ConsumerWidget { final ActionSource source; final bool iconOnly; final bool menuItem; + final bool useShortLabel; - const DeletePermanentActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false}); + const DeletePermanentActionButton({ + super.key, + required this.source, + this.iconOnly = false, + this.menuItem = false, + this.useShortLabel = false, + }); void _onTap(BuildContext context, WidgetRef ref) async { if (!context.mounted) { @@ -64,7 +71,7 @@ class DeletePermanentActionButton extends ConsumerWidget { return BaseActionButton( maxWidth: 110.0, iconData: Icons.delete_forever, - label: "delete_permanently".t(context: context), + label: useShortLabel ? "delete".t(context: context) : "delete_permanently".t(context: context), iconOnly: iconOnly, menuItem: menuItem, onPressed: () => _onTap(context, ref), diff --git a/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart index 21401f37e5..e317c598f5 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart @@ -7,6 +7,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/add_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/edit_image_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/restore_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart'; @@ -55,9 +56,12 @@ class ViewerBottomBar extends ConsumerWidget { if (asset.hasRemote) AddActionButton(originalTheme: originalTheme), ], if (isOwner) ...[ - asset.isLocalOnly - ? const DeleteLocalActionButton(source: ActionSource.viewer) - : const DeleteActionButton(source: ActionSource.viewer, showConfirmation: true), + if (asset.isLocalOnly) + const DeleteLocalActionButton(source: ActionSource.viewer) + else if (asset.isTrashed) + const DeletePermanentActionButton(source: ActionSource.viewer, useShortLabel: true) + else + const DeleteActionButton(source: ActionSource.viewer, showConfirmation: true), ], ], ]; diff --git a/mobile/lib/utils/action_button.utils.dart b/mobile/lib/utils/action_button.utils.dart index c38c536136..87588efe65 100644 --- a/mobile/lib/utils/action_button.utils.dart +++ b/mobile/lib/utils/action_button.utils.dart @@ -123,9 +123,8 @@ enum ActionButtonType { context.timelineOrigin == TimelineOrigin.trash, ActionButtonType.deletePermanent => context.isOwner && // - context.asset.hasRemote && // - !context.isTrashEnabled || - context.isInLockedView, + context.asset.hasRemote && // + (!context.isTrashEnabled || context.timelineOrigin == TimelineOrigin.trash || context.isInLockedView), ActionButtonType.delete => context.isOwner && // !context.isInLockedView && // @@ -324,6 +323,7 @@ class ActionButtonBuilder { ActionButtonType.archive, ActionButtonType.unarchive, ActionButtonType.restoreTrash, + ActionButtonType.deletePermanent, }; static List build(ActionButtonContext context) { diff --git a/mobile/test/utils_legacy/action_button_utils_test.dart b/mobile/test/utils_legacy/action_button_utils_test.dart index 8bd078a433..0a6020762a 100644 --- a/mobile/test/utils_legacy/action_button_utils_test.dart +++ b/mobile/test/utils_legacy/action_button_utils_test.dart @@ -38,6 +38,7 @@ RemoteAsset createRemoteAsset({ DateTime? updatedAt, DateTime? uploadedAt, bool isFavorite = false, + DateTime? deletedAt, }) { return RemoteAsset( id: 'remote-id', @@ -51,6 +52,7 @@ RemoteAsset createRemoteAsset({ uploadedAt: uploadedAt ?? DateTime.now(), isFavorite: isFavorite, isEdited: false, + deletedAt: deletedAt, ); } @@ -459,6 +461,24 @@ void main() { expect(ActionButtonType.trash.shouldShow(context), isFalse); }); + + test('should not show when asset is already trashed', () { + final remoteAsset = createRemoteAsset(deletedAt: DateTime(2024)); + final context = ActionButtonContext( + asset: remoteAsset, + isOwner: true, + isArchived: false, + isTrashEnabled: true, + isInLockedView: false, + currentAlbum: null, + advancedTroubleshooting: false, + isStacked: false, + source: ActionSource.viewer, + timelineOrigin: TimelineOrigin.trash, + ); + + expect(ActionButtonType.trash.shouldShow(context), isFalse); + }); }); group('restoreTrash button', () { @@ -533,6 +553,24 @@ void main() { expect(ActionButtonType.deletePermanent.shouldShow(context), isFalse); }); + + test('should show when asset is trashed even with trash enabled', () { + final remoteAsset = createRemoteAsset(deletedAt: DateTime(2024)); + final context = ActionButtonContext( + asset: remoteAsset, + isOwner: true, + isArchived: false, + isTrashEnabled: true, + isInLockedView: false, + currentAlbum: null, + advancedTroubleshooting: false, + isStacked: false, + source: ActionSource.viewer, + timelineOrigin: TimelineOrigin.trash, + ); + + expect(ActionButtonType.deletePermanent.shouldShow(context), isTrue); + }); }); group('delete button', () {