From ad9f3cfa0510d4654437637dfdf0b123b5e837f8 Mon Sep 17 00:00:00 2001 From: cmdPromptCritical <7504765+cmdPromptCritical@users.noreply.github.com> Date: Thu, 5 Feb 2026 14:08:35 -0500 Subject: [PATCH] fix(mobile): cancel share download when dialog is dismissed (#25466) * fix(mobile): cancel share download when dialog is dismissed * refactor: centralize temporary file cleanup logic * refactor: replace `CancellationToken` with `Completer` for asset sharing cancellation --------- Co-authored-by: cmdpromptcritical --- .../share_action_button.widget.dart | 21 +++++++--- .../infrastructure/action.provider.dart | 8 +++- .../repositories/asset_media.repository.dart | 40 ++++++++++++++----- mobile/lib/services/action.service.dart | 4 +- 4 files changed, 53 insertions(+), 20 deletions(-) diff --git a/mobile/lib/presentation/widgets/action_buttons/share_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/share_action_button.widget.dart index 4f272cb99..6fbd6f7df 100644 --- a/mobile/lib/presentation/widgets/action_buttons/share_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/share_action_button.widget.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; @@ -41,16 +42,20 @@ class ShareActionButton extends ConsumerWidget { return; } + final cancelCompleter = Completer(); + const preparingDialog = _SharePreparingDialog(); await showDialog( context: context, builder: (BuildContext buildContext) { - ref.read(actionProvider.notifier).shareAssets(source, context).then((ActionResult result) { - ref.read(multiSelectProvider.notifier).reset(); - - if (!context.mounted) { + ref.read(actionProvider.notifier).shareAssets(source, context, cancelCompleter: cancelCompleter).then(( + ActionResult result, + ) { + if (cancelCompleter.isCompleted || !context.mounted) { return; } + ref.read(multiSelectProvider.notifier).reset(); + if (!result.success) { ImmichToast.show( context: context, @@ -64,11 +69,15 @@ class ShareActionButton extends ConsumerWidget { }); // show a loading spinner with a "Preparing" message - return const _SharePreparingDialog(); + return preparingDialog; }, barrierDismissible: false, useRootNavigator: false, - ); + ).then((_) { + if (!cancelCompleter.isCompleted) { + cancelCompleter.complete(); + } + }); } @override diff --git a/mobile/lib/providers/infrastructure/action.provider.dart b/mobile/lib/providers/infrastructure/action.provider.dart index 924e9c558..75f40ca29 100644 --- a/mobile/lib/providers/infrastructure/action.provider.dart +++ b/mobile/lib/providers/infrastructure/action.provider.dart @@ -405,11 +405,15 @@ class ActionNotifier extends Notifier { } } - Future shareAssets(ActionSource source, BuildContext context) async { + Future shareAssets( + ActionSource source, + BuildContext context, { + Completer? cancelCompleter, + }) async { final ids = _getAssets(source).toList(growable: false); try { - await _service.shareAssets(ids, context); + await _service.shareAssets(ids, context, cancelCompleter: cancelCompleter); return ActionResult(count: ids.length, success: true); } catch (error, stack) { _logger.severe('Failed to share assets', error, stack); diff --git a/mobile/lib/repositories/asset_media.repository.dart b/mobile/lib/repositories/asset_media.repository.dart index 22fa3bdd0..fecfe6df4 100644 --- a/mobile/lib/repositories/asset_media.repository.dart +++ b/mobile/lib/repositories/asset_media.repository.dart @@ -23,7 +23,6 @@ final assetMediaRepositoryProvider = Provider((ref) => AssetMediaRepository(ref. class AssetMediaRepository { final AssetApiRepository _assetApiRepository; - static final Logger _log = Logger("AssetMediaRepository"); const AssetMediaRepository(this._assetApiRepository); @@ -58,6 +57,7 @@ class AssetMediaRepository { static asset_entity.Asset? toAsset(AssetEntity? local) { if (local == null) return null; + final asset_entity.Asset asset = asset_entity.Asset( checksum: "", localId: local.id, @@ -72,19 +72,21 @@ class AssetMediaRepository { height: local.height, isFavorite: local.isFavorite, ); + if (asset.fileCreatedAt.year == 1970) { asset.fileCreatedAt = asset.fileModifiedAt; } + if (local.latitude != null) { asset.exifInfo = ExifInfo(latitude: local.latitude, longitude: local.longitude); } + asset.local = local; return asset; } Future getOriginalFilename(String id) async { final entity = await AssetEntity.fromId(id); - if (entity == null) { return null; } @@ -101,12 +103,31 @@ class AssetMediaRepository { } } + /// Deletes temporary files in parallel + Future _cleanupTempFiles(List tempFiles) async { + await Future.wait( + tempFiles.map((file) async { + try { + await file.delete(); + } catch (e) { + _log.warning("Failed to delete temporary file: ${file.path}", e); + } + }), + ); + } + // TODO: make this more efficient - Future shareAssets(List assets, BuildContext context) async { + Future shareAssets(List assets, BuildContext context, {Completer? cancelCompleter}) async { final downloadedXFiles = []; final tempFiles = []; for (var asset in assets) { + if (cancelCompleter != null && cancelCompleter.isCompleted) { + // if cancelled, delete any temp files created so far + await _cleanupTempFiles(tempFiles); + return 0; + } + final localId = (asset is LocalAsset) ? asset.id : asset is RemoteAsset @@ -146,6 +167,11 @@ class AssetMediaRepository { return 0; } + if (cancelCompleter != null && cancelCompleter.isCompleted) { + await _cleanupTempFiles(tempFiles); + return 0; + } + // we dont want to await the share result since the // "preparing" dialog will not disappear until final size = context.sizeData; @@ -154,13 +180,7 @@ class AssetMediaRepository { downloadedXFiles, sharePositionOrigin: Rect.fromPoints(Offset.zero, Offset(size.width / 3, size.height)), ).then((result) async { - for (var file in tempFiles) { - try { - await file.delete(); - } catch (e) { - _log.warning("Failed to delete temporary file: ${file.path}", e); - } - } + await _cleanupTempFiles(tempFiles); }), ); diff --git a/mobile/lib/services/action.service.dart b/mobile/lib/services/action.service.dart index 13e491f32..3d3ef1494 100644 --- a/mobile/lib/services/action.service.dart +++ b/mobile/lib/services/action.service.dart @@ -232,8 +232,8 @@ class ActionService { await _assetApiRepository.unStack(stackIds); } - Future shareAssets(List assets, BuildContext context) { - return _assetMediaRepository.shareAssets(assets, context); + Future shareAssets(List assets, BuildContext context, {Completer? cancelCompleter}) { + return _assetMediaRepository.shareAssets(assets, context, cancelCompleter: cancelCompleter); } Future> downloadAll(List assets) {