mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
fix(mobile): use correct delete action (#26575)
fix(mobile): use correct delete for trashed assets When viewing a trashed asset, the viewer bottom bar now shows the permanent delete button instead of the trash button, which had no effect on already-trashed assets.
This commit is contained in:
@@ -11,6 +11,7 @@ class RemoteAsset extends BaseAsset {
|
|||||||
final String ownerId;
|
final String ownerId;
|
||||||
final String? stackId;
|
final String? stackId;
|
||||||
final DateTime? uploadedAt;
|
final DateTime? uploadedAt;
|
||||||
|
final DateTime? deletedAt;
|
||||||
|
|
||||||
const RemoteAsset({
|
const RemoteAsset({
|
||||||
required this.id,
|
required this.id,
|
||||||
@@ -31,6 +32,7 @@ class RemoteAsset extends BaseAsset {
|
|||||||
super.livePhotoVideoId,
|
super.livePhotoVideoId,
|
||||||
this.stackId,
|
this.stackId,
|
||||||
required super.isEdited,
|
required super.isEdited,
|
||||||
|
this.deletedAt,
|
||||||
}) : localAssetId = localId;
|
}) : localAssetId = localId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -48,6 +50,8 @@ class RemoteAsset extends BaseAsset {
|
|||||||
@override
|
@override
|
||||||
bool get isEditable => isImage && !isMotionPhoto && !isAnimatedImage;
|
bool get isEditable => isImage && !isMotionPhoto && !isAnimatedImage;
|
||||||
|
|
||||||
|
bool get isTrashed => deletedAt != null;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return '''Asset {
|
return '''Asset {
|
||||||
@@ -86,7 +90,8 @@ class RemoteAsset extends BaseAsset {
|
|||||||
thumbHash == other.thumbHash &&
|
thumbHash == other.thumbHash &&
|
||||||
visibility == other.visibility &&
|
visibility == other.visibility &&
|
||||||
stackId == other.stackId &&
|
stackId == other.stackId &&
|
||||||
uploadedAt == other.uploadedAt;
|
uploadedAt == other.uploadedAt &&
|
||||||
|
deletedAt == other.deletedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -98,7 +103,8 @@ class RemoteAsset extends BaseAsset {
|
|||||||
thumbHash.hashCode ^
|
thumbHash.hashCode ^
|
||||||
visibility.hashCode ^
|
visibility.hashCode ^
|
||||||
stackId.hashCode ^
|
stackId.hashCode ^
|
||||||
uploadedAt.hashCode;
|
uploadedAt.hashCode ^
|
||||||
|
deletedAt.hashCode;
|
||||||
|
|
||||||
RemoteAsset copyWith({
|
RemoteAsset copyWith({
|
||||||
String? id,
|
String? id,
|
||||||
@@ -119,6 +125,7 @@ class RemoteAsset extends BaseAsset {
|
|||||||
String? livePhotoVideoId,
|
String? livePhotoVideoId,
|
||||||
String? stackId,
|
String? stackId,
|
||||||
bool? isEdited,
|
bool? isEdited,
|
||||||
|
DateTime? deletedAt,
|
||||||
}) {
|
}) {
|
||||||
return RemoteAsset(
|
return RemoteAsset(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
@@ -139,6 +146,7 @@ class RemoteAsset extends BaseAsset {
|
|||||||
livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId,
|
livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId,
|
||||||
stackId: stackId ?? this.stackId,
|
stackId: stackId ?? this.stackId,
|
||||||
isEdited: isEdited ?? this.isEdited,
|
isEdited: isEdited ?? this.isEdited,
|
||||||
|
deletedAt: deletedAt ?? this.deletedAt,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,6 +164,7 @@ class RemoteAssetExif extends RemoteAsset {
|
|||||||
required super.createdAt,
|
required super.createdAt,
|
||||||
required super.updatedAt,
|
required super.updatedAt,
|
||||||
super.uploadedAt,
|
super.uploadedAt,
|
||||||
|
super.deletedAt,
|
||||||
super.width,
|
super.width,
|
||||||
super.height,
|
super.height,
|
||||||
super.durationMs,
|
super.durationMs,
|
||||||
@@ -193,6 +202,7 @@ class RemoteAssetExif extends RemoteAsset {
|
|||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
DateTime? updatedAt,
|
DateTime? updatedAt,
|
||||||
DateTime? uploadedAt,
|
DateTime? uploadedAt,
|
||||||
|
DateTime? deletedAt,
|
||||||
int? width,
|
int? width,
|
||||||
int? height,
|
int? height,
|
||||||
int? durationMs,
|
int? durationMs,
|
||||||
@@ -214,6 +224,7 @@ class RemoteAssetExif extends RemoteAsset {
|
|||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
updatedAt: updatedAt ?? this.updatedAt,
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
uploadedAt: uploadedAt ?? this.uploadedAt,
|
uploadedAt: uploadedAt ?? this.uploadedAt,
|
||||||
|
deletedAt: deletedAt ?? this.deletedAt,
|
||||||
width: width ?? this.width,
|
width: width ?? this.width,
|
||||||
height: height ?? this.height,
|
height: height ?? this.height,
|
||||||
durationMs: durationMs ?? this.durationMs,
|
durationMs: durationMs ?? this.durationMs,
|
||||||
|
|||||||
@@ -74,5 +74,6 @@ extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData {
|
|||||||
localId: localId,
|
localId: localId,
|
||||||
stackId: stackId,
|
stackId: stackId,
|
||||||
isEdited: isEdited,
|
isEdited: isEdited,
|
||||||
|
deletedAt: deletedAt,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-2
@@ -18,8 +18,15 @@ class DeletePermanentActionButton extends ConsumerWidget {
|
|||||||
final ActionSource source;
|
final ActionSource source;
|
||||||
final bool iconOnly;
|
final bool iconOnly;
|
||||||
final bool menuItem;
|
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 {
|
void _onTap(BuildContext context, WidgetRef ref) async {
|
||||||
if (!context.mounted) {
|
if (!context.mounted) {
|
||||||
@@ -64,7 +71,7 @@ class DeletePermanentActionButton extends ConsumerWidget {
|
|||||||
return BaseActionButton(
|
return BaseActionButton(
|
||||||
maxWidth: 110.0,
|
maxWidth: 110.0,
|
||||||
iconData: Icons.delete_forever,
|
iconData: Icons.delete_forever,
|
||||||
label: "delete_permanently".t(context: context),
|
label: useShortLabel ? "delete".t(context: context) : "delete_permanently".t(context: context),
|
||||||
iconOnly: iconOnly,
|
iconOnly: iconOnly,
|
||||||
menuItem: menuItem,
|
menuItem: menuItem,
|
||||||
onPressed: () => _onTap(context, ref),
|
onPressed: () => _onTap(context, ref),
|
||||||
|
|||||||
@@ -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/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_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_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/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/restore_action_button.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/action_buttons/share_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 (asset.hasRemote) AddActionButton(originalTheme: originalTheme),
|
||||||
],
|
],
|
||||||
if (isOwner) ...[
|
if (isOwner) ...[
|
||||||
asset.isLocalOnly
|
if (asset.isLocalOnly)
|
||||||
? const DeleteLocalActionButton(source: ActionSource.viewer)
|
const DeleteLocalActionButton(source: ActionSource.viewer)
|
||||||
: const DeleteActionButton(source: ActionSource.viewer, showConfirmation: true),
|
else if (asset.isTrashed)
|
||||||
|
const DeletePermanentActionButton(source: ActionSource.viewer, useShortLabel: true)
|
||||||
|
else
|
||||||
|
const DeleteActionButton(source: ActionSource.viewer, showConfirmation: true),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -123,9 +123,8 @@ enum ActionButtonType {
|
|||||||
context.timelineOrigin == TimelineOrigin.trash,
|
context.timelineOrigin == TimelineOrigin.trash,
|
||||||
ActionButtonType.deletePermanent =>
|
ActionButtonType.deletePermanent =>
|
||||||
context.isOwner && //
|
context.isOwner && //
|
||||||
context.asset.hasRemote && //
|
context.asset.hasRemote && //
|
||||||
!context.isTrashEnabled ||
|
(!context.isTrashEnabled || context.timelineOrigin == TimelineOrigin.trash || context.isInLockedView),
|
||||||
context.isInLockedView,
|
|
||||||
ActionButtonType.delete =>
|
ActionButtonType.delete =>
|
||||||
context.isOwner && //
|
context.isOwner && //
|
||||||
!context.isInLockedView && //
|
!context.isInLockedView && //
|
||||||
@@ -324,6 +323,7 @@ class ActionButtonBuilder {
|
|||||||
ActionButtonType.archive,
|
ActionButtonType.archive,
|
||||||
ActionButtonType.unarchive,
|
ActionButtonType.unarchive,
|
||||||
ActionButtonType.restoreTrash,
|
ActionButtonType.restoreTrash,
|
||||||
|
ActionButtonType.deletePermanent,
|
||||||
};
|
};
|
||||||
|
|
||||||
static List<Widget> build(ActionButtonContext context) {
|
static List<Widget> build(ActionButtonContext context) {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ RemoteAsset createRemoteAsset({
|
|||||||
DateTime? updatedAt,
|
DateTime? updatedAt,
|
||||||
DateTime? uploadedAt,
|
DateTime? uploadedAt,
|
||||||
bool isFavorite = false,
|
bool isFavorite = false,
|
||||||
|
DateTime? deletedAt,
|
||||||
}) {
|
}) {
|
||||||
return RemoteAsset(
|
return RemoteAsset(
|
||||||
id: 'remote-id',
|
id: 'remote-id',
|
||||||
@@ -51,6 +52,7 @@ RemoteAsset createRemoteAsset({
|
|||||||
uploadedAt: uploadedAt ?? DateTime.now(),
|
uploadedAt: uploadedAt ?? DateTime.now(),
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
isEdited: false,
|
isEdited: false,
|
||||||
|
deletedAt: deletedAt,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,6 +461,24 @@ void main() {
|
|||||||
|
|
||||||
expect(ActionButtonType.trash.shouldShow(context), isFalse);
|
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', () {
|
group('restoreTrash button', () {
|
||||||
@@ -533,6 +553,24 @@ void main() {
|
|||||||
|
|
||||||
expect(ActionButtonType.deletePermanent.shouldShow(context), isFalse);
|
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', () {
|
group('delete button', () {
|
||||||
|
|||||||
Reference in New Issue
Block a user