fix(mobile): stop trashing assets after deletion cancellation (#32522)

* fix(mobile): stop trashing assets after deletion cancellation

* fix(mobile): handle Android system delete cancellation

* fix(mobile): simplify

* simplify test

(cherry picked from commit 3517973b8a316c8fb729bf82fe4bbf95884b9146)
This commit is contained in:
gitTurboflieger
2026-10-22 20:43:53 +03:00
committed by Codex
parent 9a66f16102
commit 3aa65809b1
2 changed files with 64 additions and 3 deletions
@@ -121,14 +121,14 @@ class DeleteAction extends AssetActionBuilder {
required bool isLocalOnly,
}) async {
final assetService = ref.read(assetServiceProvider);
final message = context.t.trash_action_prompt(count: remoteIds.length);
if (localIds.isNotEmpty) {
await _cleanupLocalAssets(context, ref, localIds, isLocalOnly: isLocalOnly);
if (!context.mounted) {
final count = await _cleanupLocalAssets(context, ref, localIds, isLocalOnly: isLocalOnly);
if (count <= 0) {
return null;
}
}
final message = context.t.trash_action_prompt(count: remoteIds.length);
await assetService.trash(remoteIds);
return message;
}
@@ -97,6 +97,7 @@ void main() {
});
testWidgets('trashes a merged asset and removes its device copy', (tester) async {
when(() => cleanupService.deleteLocalAssets(['local'])).thenAnswer((_) async => 1);
final asset = owned(localId: 'local');
await pumpDelete(tester, {asset});
@@ -106,6 +107,65 @@ void main() {
verify(() => assetService.trash([asset.id])).called(1);
});
testWidgets('does not trash a merged asset when the MANAGE_MEDIA prompt is cancelled', (tester) async {
debugDefaultTargetPlatformOverride = TargetPlatform.android;
await StoreService.I.put(StoreKey.manageLocalMediaAndroid, true);
final asset = owned(localId: 'local');
await pumpDelete(tester, {asset});
await respondToDialog(tester, confirm: false);
verifyNever(() => cleanupService.deleteLocalAssets(any()));
verifyNever(() => assetService.trash(any()));
verifyNever(() => assetService.delete(any()));
debugDefaultTargetPlatformOverride = null;
});
testWidgets('does not trash a merged asset when the Android system prompt is cancelled', (tester) async {
debugDefaultTargetPlatformOverride = TargetPlatform.android;
when(context.repository.permission.getAndroidSdkVersion).thenAnswer((_) async => 30);
when(() => cleanupService.deleteLocalAssets(['local'])).thenAnswer((_) async => 0);
final asset = owned(localId: 'local');
await pumpDelete(tester, {asset});
await tester.pumpAndSettle();
verify(() => cleanupService.deleteLocalAssets(['local'])).called(1);
verifyNever(() => assetService.trash(any()));
verifyNever(() => assetService.delete(any()));
debugDefaultTargetPlatformOverride = null;
});
testWidgets('finishes trashing when the action context is disposed during local cleanup', (tester) async {
final asset = owned(localId: 'local');
final cleanupResult = Completer<int>();
when(() => cleanupService.deleteLocalAssets(['local'])).thenAnswer((_) => cleanupResult.future);
var showAction = true;
late StateSetter updateHost;
await tester.pumpTestWidget(
context,
StatefulBuilder(
builder: (_, setState) {
updateHost = setState;
return showAction
? const ActionIconButton(action: DeleteAction(source: .timeline))
: const SizedBox.shrink();
},
),
overrides: context.selected({asset}),
);
await tester.tap(find.byType(ImmichIconButton));
await tester.pump();
updateHost(() => showAction = false);
await tester.pump();
cleanupResult.complete(1);
await tester.pumpAndSettle();
verify(() => assetService.trash([asset.id])).called(1);
});
testWidgets('offers an undo that restores the trashed assets', (tester) async {
final asset = owned();
@@ -283,6 +343,7 @@ void main() {
when(context.repository.permission.getAndroidSdkVersion).thenAnswer((_) async => 28);
final backedUp = owned(localId: 'local');
final localOnly = LocalAssetFactory.create();
when(() => cleanupService.deleteLocalAssets(['local', localOnly.id])).thenAnswer((_) async => 1);
await pumpDelete(tester, {backedUp, localOnly});
await tester.pump(const Duration(milliseconds: 300));