mirror of
https://github.com/samsonjs/immich.git
synced 2026-03-25 09:15:56 +00:00
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<void>` for asset sharing cancellation --------- Co-authored-by: cmdpromptcritical <cmdpromptcritical@github.com>
This commit is contained in:
parent
9d8efe2685
commit
ad9f3cfa05
4 changed files with 53 additions and 20 deletions
|
|
@ -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<void>();
|
||||
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
|
||||
|
|
|
|||
|
|
@ -405,11 +405,15 @@ class ActionNotifier extends Notifier<void> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<ActionResult> shareAssets(ActionSource source, BuildContext context) async {
|
||||
Future<ActionResult> shareAssets(
|
||||
ActionSource source,
|
||||
BuildContext context, {
|
||||
Completer<void>? 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);
|
||||
|
|
|
|||
|
|
@ -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<String?> 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<void> _cleanupTempFiles(List<File> 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<int> shareAssets(List<BaseAsset> assets, BuildContext context) async {
|
||||
Future<int> shareAssets(List<BaseAsset> assets, BuildContext context, {Completer<void>? cancelCompleter}) async {
|
||||
final downloadedXFiles = <XFile>[];
|
||||
final tempFiles = <File>[];
|
||||
|
||||
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);
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -232,8 +232,8 @@ class ActionService {
|
|||
await _assetApiRepository.unStack(stackIds);
|
||||
}
|
||||
|
||||
Future<int> shareAssets(List<BaseAsset> assets, BuildContext context) {
|
||||
return _assetMediaRepository.shareAssets(assets, context);
|
||||
Future<int> shareAssets(List<BaseAsset> assets, BuildContext context, {Completer<void>? cancelCompleter}) {
|
||||
return _assetMediaRepository.shareAssets(assets, context, cancelCompleter: cancelCompleter);
|
||||
}
|
||||
|
||||
Future<List<bool>> downloadAll(List<RemoteAsset> assets) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue