mirror of
https://github.com/immich-app/immich.git
synced 2026-07-14 09:00:59 +00:00
fix(mobile): improve image load cancellation handling (#27624)
fix(image): improve image load cancellation handling
This commit is contained in:
parent
55ab8c65b6
commit
2b0f6c9202
3 changed files with 31 additions and 13 deletions
|
|
@ -19,6 +19,7 @@ mixin CancellableImageProviderMixin<T extends Object> on CancellableImageProvide
|
||||||
static final _log = Logger('CancellableImageProviderMixin');
|
static final _log = Logger('CancellableImageProviderMixin');
|
||||||
|
|
||||||
bool isCancelled = false;
|
bool isCancelled = false;
|
||||||
|
bool isFinished = false;
|
||||||
ImageRequest? request;
|
ImageRequest? request;
|
||||||
CancelableOperation<ImageInfo?>? cachedOperation;
|
CancelableOperation<ImageInfo?>? cachedOperation;
|
||||||
|
|
||||||
|
|
@ -53,13 +54,15 @@ mixin CancellableImageProviderMixin<T extends Object> on CancellableImageProvide
|
||||||
Stream<ImageInfo> loadRequest(ImageRequest request, ImageDecoderCallback decode, {bool evictOnError = true}) async* {
|
Stream<ImageInfo> loadRequest(ImageRequest request, ImageDecoderCallback decode, {bool evictOnError = true}) async* {
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
this.request = null;
|
this.request = null;
|
||||||
PaintingBinding.instance.imageCache.evict(this);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final image = await request.load(decode);
|
final image = await request.load(decode);
|
||||||
if ((image == null && evictOnError) || isCancelled) {
|
if (isCancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (image == null && evictOnError) {
|
||||||
PaintingBinding.instance.imageCache.evict(this);
|
PaintingBinding.instance.imageCache.evict(this);
|
||||||
return;
|
return;
|
||||||
} else if (image == null) {
|
} else if (image == null) {
|
||||||
|
|
@ -67,6 +70,9 @@ mixin CancellableImageProviderMixin<T extends Object> on CancellableImageProvide
|
||||||
}
|
}
|
||||||
yield image;
|
yield image;
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
|
if (isCancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (evictOnError) {
|
if (evictOnError) {
|
||||||
PaintingBinding.instance.imageCache.evict(this);
|
PaintingBinding.instance.imageCache.evict(this);
|
||||||
rethrow;
|
rethrow;
|
||||||
|
|
@ -80,20 +86,24 @@ mixin CancellableImageProviderMixin<T extends Object> on CancellableImageProvide
|
||||||
Future<ui.Codec?> loadCodecRequest(ImageRequest request) async {
|
Future<ui.Codec?> loadCodecRequest(ImageRequest request) async {
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
this.request = null;
|
this.request = null;
|
||||||
PaintingBinding.instance.imageCache.evict(this);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final codec = await request.loadCodec();
|
final codec = await request.loadCodec();
|
||||||
if (codec == null || isCancelled) {
|
if (isCancelled) {
|
||||||
codec?.dispose();
|
codec?.dispose();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (codec == null) {
|
||||||
PaintingBinding.instance.imageCache.evict(this);
|
PaintingBinding.instance.imageCache.evict(this);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return codec;
|
return codec;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
PaintingBinding.instance.imageCache.evict(this);
|
if (!isCancelled) {
|
||||||
|
PaintingBinding.instance.imageCache.evict(this);
|
||||||
|
}
|
||||||
rethrow;
|
rethrow;
|
||||||
} finally {
|
} finally {
|
||||||
this.request = null;
|
this.request = null;
|
||||||
|
|
@ -121,6 +131,8 @@ mixin CancellableImageProviderMixin<T extends Object> on CancellableImageProvide
|
||||||
@override
|
@override
|
||||||
void cancel() {
|
void cancel() {
|
||||||
isCancelled = true;
|
isCancelled = true;
|
||||||
|
final hasActiveWork = !isFinished;
|
||||||
|
|
||||||
final request = this.request;
|
final request = this.request;
|
||||||
if (request != null) {
|
if (request != null) {
|
||||||
this.request = null;
|
this.request = null;
|
||||||
|
|
@ -132,6 +144,10 @@ mixin CancellableImageProviderMixin<T extends Object> on CancellableImageProvide
|
||||||
cachedOperation = null;
|
cachedOperation = null;
|
||||||
operation.cancel();
|
operation.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasActiveWork) {
|
||||||
|
PaintingBinding.instance.imageCache.evict(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,6 @@ class LocalFullImageProvider extends CancellableImageProvider<LocalFullImageProv
|
||||||
yield* initialImageStream();
|
yield* initialImageStream();
|
||||||
|
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
PaintingBinding.instance.imageCache.evict(this);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,24 +112,24 @@ class LocalFullImageProvider extends CancellableImageProvider<LocalFullImageProv
|
||||||
yield* loadRequest(request, decode);
|
yield* loadRequest(request, decode);
|
||||||
|
|
||||||
if (!Store.get(StoreKey.loadOriginal, false)) {
|
if (!Store.get(StoreKey.loadOriginal, false)) {
|
||||||
|
isFinished = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
PaintingBinding.instance.imageCache.evict(this);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
request = this.request = LocalImageRequest(localId: key.id, assetType: key.assetType, size: Size.zero);
|
request = this.request = LocalImageRequest(localId: key.id, assetType: key.assetType, size: Size.zero);
|
||||||
|
|
||||||
yield* loadRequest(request, decode);
|
yield* loadRequest(request, decode);
|
||||||
|
isFinished = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<Object> _animatedCodec(LocalFullImageProvider key, ImageDecoderCallback decode) async* {
|
Stream<Object> _animatedCodec(LocalFullImageProvider key, ImageDecoderCallback decode) async* {
|
||||||
yield* initialImageStream();
|
yield* initialImageStream();
|
||||||
|
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
PaintingBinding.instance.imageCache.evict(this);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,7 +142,6 @@ class LocalFullImageProvider extends CancellableImageProvider<LocalFullImageProv
|
||||||
yield* loadRequest(previewRequest, decode);
|
yield* loadRequest(previewRequest, decode);
|
||||||
|
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
PaintingBinding.instance.imageCache.evict(this);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,9 +149,11 @@ class LocalFullImageProvider extends CancellableImageProvider<LocalFullImageProv
|
||||||
final originalRequest = request = LocalImageRequest(localId: key.id, size: Size.zero, assetType: key.assetType);
|
final originalRequest = request = LocalImageRequest(localId: key.id, size: Size.zero, assetType: key.assetType);
|
||||||
final codec = await loadCodecRequest(originalRequest);
|
final codec = await loadCodecRequest(originalRequest);
|
||||||
if (codec == null) {
|
if (codec == null) {
|
||||||
|
if (isCancelled) return;
|
||||||
throw StateError('Failed to load animated codec for local asset ${key.id}');
|
throw StateError('Failed to load animated codec for local asset ${key.id}');
|
||||||
}
|
}
|
||||||
yield codec;
|
yield codec;
|
||||||
|
isFinished = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,6 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
|
||||||
yield* initialImageStream();
|
yield* initialImageStream();
|
||||||
|
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
PaintingBinding.instance.imageCache.evict(this);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,23 +115,23 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
|
||||||
yield* loadRequest(previewRequest, decode, evictOnError: !loadOriginal);
|
yield* loadRequest(previewRequest, decode, evictOnError: !loadOriginal);
|
||||||
|
|
||||||
if (!loadOriginal) {
|
if (!loadOriginal) {
|
||||||
|
isFinished = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
PaintingBinding.instance.imageCache.evict(this);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final originalRequest = request = RemoteImageRequest(uri: getOriginalUrlForRemoteId(key.assetId));
|
final originalRequest = request = RemoteImageRequest(uri: getOriginalUrlForRemoteId(key.assetId));
|
||||||
yield* loadRequest(originalRequest, decode);
|
yield* loadRequest(originalRequest, decode);
|
||||||
|
isFinished = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<Object> _animatedCodec(RemoteFullImageProvider key, ImageDecoderCallback decode) async* {
|
Stream<Object> _animatedCodec(RemoteFullImageProvider key, ImageDecoderCallback decode) async* {
|
||||||
yield* initialImageStream();
|
yield* initialImageStream();
|
||||||
|
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
PaintingBinding.instance.imageCache.evict(this);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,7 +141,6 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
|
||||||
yield* loadRequest(previewRequest, decode, evictOnError: false);
|
yield* loadRequest(previewRequest, decode, evictOnError: false);
|
||||||
|
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
PaintingBinding.instance.imageCache.evict(this);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,9 +148,13 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
|
||||||
final originalRequest = request = RemoteImageRequest(uri: getOriginalUrlForRemoteId(key.assetId));
|
final originalRequest = request = RemoteImageRequest(uri: getOriginalUrlForRemoteId(key.assetId));
|
||||||
final codec = await loadCodecRequest(originalRequest);
|
final codec = await loadCodecRequest(originalRequest);
|
||||||
if (codec == null) {
|
if (codec == null) {
|
||||||
|
if (isCancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
throw StateError('Failed to load animated codec for asset ${key.assetId}');
|
throw StateError('Failed to load animated codec for asset ${key.assetId}');
|
||||||
}
|
}
|
||||||
yield codec;
|
yield codec;
|
||||||
|
isFinished = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue