mirror of
https://github.com/immich-app/immich.git
synced 2026-02-12 03:47:51 +03:00
fix(mobile): handle image stream completion when no image is emitted (#25984)
* Fix image cancellation to be stream-scoped instead of widget-scoped * fix(OneFramePlaceholderImageStreamCompleter): make onLastListenerRemoved callback synchronous with removing the last listener * fix(OneFrameMultiImageStreamCompleter): remove unnecessary blank line in code * fix(OneFramePlaceholderImageStreamCompleter): cancel pending requests when only cache listener remains * fix(OneFrameMultiImageStreamCompleter): ensure onLastListenerRemoved callback is invoked only once
This commit is contained in:
@@ -31,7 +31,7 @@ class LocalThumbProvider extends CancellableImageProvider<LocalThumbProvider>
|
||||
DiagnosticsProperty<String>('Id', key.id),
|
||||
DiagnosticsProperty<Size>('Size', key.size),
|
||||
],
|
||||
onDispose: cancel,
|
||||
onLastListenerRemoved: cancel,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ class LocalFullImageProvider extends CancellableImageProvider<LocalFullImageProv
|
||||
DiagnosticsProperty<String>('Id', key.id),
|
||||
DiagnosticsProperty<Size>('Size', key.size),
|
||||
],
|
||||
onDispose: cancel,
|
||||
onLastListenerRemoved: cancel,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,10 @@ import 'package:flutter/painting.dart';
|
||||
|
||||
/// An ImageStreamCompleter with support for loading multiple images.
|
||||
class OneFramePlaceholderImageStreamCompleter extends ImageStreamCompleter {
|
||||
void Function()? _onDispose;
|
||||
void Function()? _onLastListenerRemoved;
|
||||
int _listenerCount = 0;
|
||||
// True once setImage() has been called at least once.
|
||||
bool didProvideImage = false;
|
||||
|
||||
/// The constructor to create an OneFramePlaceholderImageStreamCompleter. The [images]
|
||||
/// should be the primary images to display (typically asynchronously as they load).
|
||||
@@ -19,14 +22,18 @@ class OneFramePlaceholderImageStreamCompleter extends ImageStreamCompleter {
|
||||
Stream<ImageInfo> images, {
|
||||
ImageInfo? initialImage,
|
||||
InformationCollector? informationCollector,
|
||||
void Function()? onDispose,
|
||||
void Function()? onLastListenerRemoved,
|
||||
}) {
|
||||
if (initialImage != null) {
|
||||
didProvideImage = true;
|
||||
setImage(initialImage);
|
||||
}
|
||||
_onDispose = onDispose;
|
||||
_onLastListenerRemoved = onLastListenerRemoved;
|
||||
images.listen(
|
||||
setImage,
|
||||
(image) {
|
||||
didProvideImage = true;
|
||||
setImage(image);
|
||||
},
|
||||
onError: (Object error, StackTrace stack) {
|
||||
reportError(
|
||||
context: ErrorDescription('resolving a single-frame image stream'),
|
||||
@@ -40,12 +47,24 @@ class OneFramePlaceholderImageStreamCompleter extends ImageStreamCompleter {
|
||||
}
|
||||
|
||||
@override
|
||||
void onDisposed() {
|
||||
final onDispose = _onDispose;
|
||||
if (onDispose != null) {
|
||||
_onDispose = null;
|
||||
onDispose();
|
||||
void addListener(ImageStreamListener listener) {
|
||||
super.addListener(listener);
|
||||
_listenerCount = _listenerCount + 1;
|
||||
}
|
||||
|
||||
@override
|
||||
void removeListener(ImageStreamListener listener) {
|
||||
super.removeListener(listener);
|
||||
_listenerCount = _listenerCount - 1;
|
||||
|
||||
final bool onlyCacheListenerLeft = _listenerCount == 1 && !didProvideImage;
|
||||
final bool noListenersAfterImage = _listenerCount == 0 && didProvideImage;
|
||||
|
||||
final onLastListenerRemoved = _onLastListenerRemoved;
|
||||
|
||||
if (onLastListenerRemoved != null && (noListenersAfterImage || onlyCacheListenerLeft)) {
|
||||
_onLastListenerRemoved = null;
|
||||
onLastListenerRemoved();
|
||||
}
|
||||
super.onDisposed();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ class RemoteImageProvider extends CancellableImageProvider<RemoteImageProvider>
|
||||
DiagnosticsProperty<ImageProvider>('Image provider', this),
|
||||
DiagnosticsProperty<String>('URL', key.url),
|
||||
],
|
||||
onDispose: cancel,
|
||||
onLastListenerRemoved: cancel,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
|
||||
DiagnosticsProperty<ImageProvider>('Image provider', this),
|
||||
DiagnosticsProperty<String>('Asset Id', key.assetId),
|
||||
],
|
||||
onDispose: cancel,
|
||||
onLastListenerRemoved: cancel,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ class ThumbHashProvider extends CancellableImageProvider<ThumbHashProvider>
|
||||
|
||||
@override
|
||||
ImageStreamCompleter loadImage(ThumbHashProvider key, ImageDecoderCallback decode) {
|
||||
return OneFramePlaceholderImageStreamCompleter(_loadCodec(key, decode), onDispose: cancel);
|
||||
return OneFramePlaceholderImageStreamCompleter(_loadCodec(key, decode), onLastListenerRemoved: cancel);
|
||||
}
|
||||
|
||||
Stream<ImageInfo> _loadCodec(ThumbHashProvider key, ImageDecoderCallback decode) {
|
||||
|
||||
@@ -233,16 +233,6 @@ class _ThumbnailState extends State<Thumbnail> with SingleTickerProviderStateMix
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
final imageProvider = widget.imageProvider;
|
||||
if (imageProvider is CancellableImageProvider) {
|
||||
imageProvider.cancel();
|
||||
}
|
||||
|
||||
final thumbhashProvider = widget.thumbhashProvider;
|
||||
if (thumbhashProvider is CancellableImageProvider) {
|
||||
thumbhashProvider.cancel();
|
||||
}
|
||||
|
||||
_fadeController.removeStatusListener(_onAnimationStatusChanged);
|
||||
_fadeController.dispose();
|
||||
_stopListeningToStream();
|
||||
|
||||
Reference in New Issue
Block a user