From d4f7c2ae0a24dd312f993dbd31ef5d571039fa14 Mon Sep 17 00:00:00 2001 From: LeLunZ <31982496+LeLunZ@users.noreply.github.com> Date: Thu, 9 Apr 2026 18:53:13 +0200 Subject: [PATCH] feat(mobile): implement load preview setting in asset viewer --- mobile/lib/domain/models/setting.model.dart | 1 + .../widgets/images/image_provider.dart | 21 ++++++- .../widgets/images/local_image_provider.dart | 59 ++++++++++--------- .../widgets/images/remote_image_provider.dart | 40 +++++++------ 4 files changed, 74 insertions(+), 47 deletions(-) diff --git a/mobile/lib/domain/models/setting.model.dart b/mobile/lib/domain/models/setting.model.dart index 2c46507331..de1bc37215 100644 --- a/mobile/lib/domain/models/setting.model.dart +++ b/mobile/lib/domain/models/setting.model.dart @@ -4,6 +4,7 @@ enum Setting { tilesPerRow(StoreKey.tilesPerRow, 4), groupAssetsBy(StoreKey.groupAssetsBy, 0), showStorageIndicator(StoreKey.storageIndicator, true), + loadPreview(StoreKey.loadPreview, true), loadOriginal(StoreKey.loadOriginal, false), loadOriginalVideo(StoreKey.loadOriginalVideo, false), autoPlayVideo(StoreKey.autoPlayVideo, true), diff --git a/mobile/lib/presentation/widgets/images/image_provider.dart b/mobile/lib/presentation/widgets/images/image_provider.dart index 47ebd37014..0640c341fd 100644 --- a/mobile/lib/presentation/widgets/images/image_provider.dart +++ b/mobile/lib/presentation/widgets/images/image_provider.dart @@ -106,18 +106,33 @@ mixin CancellableImageProviderMixin on CancellableImageProvide } } - Stream initialImageStream() async* { + Stream initialImageStream({required bool isFinal}) async* { final cachedOperation = this.cachedOperation; + if (isCancelled) { + return; + } if (cachedOperation == null) { + // image resolved synchronously + isFinished = isFinal; return; } try { final cachedImage = await cachedOperation.valueOrCancellation(); - if (cachedImage != null && !isCancelled) { - yield cachedImage; + if (isCancelled || cachedImage == null) { + return; } + isFinished = isFinal; + yield cachedImage; } catch (e, stack) { + if (isCancelled) { + return; + } + if (isFinal) { + isFinished = true; + PaintingBinding.instance.imageCache.evict(this); + rethrow; + } _log.severe('Error loading initial image', e, stack); } finally { this.cachedOperation = null; diff --git a/mobile/lib/presentation/widgets/images/local_image_provider.dart b/mobile/lib/presentation/widgets/images/local_image_provider.dart index d29a1cd56d..39fa3dde58 100644 --- a/mobile/lib/presentation/widgets/images/local_image_provider.dart +++ b/mobile/lib/presentation/widgets/images/local_image_provider.dart @@ -1,8 +1,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/domain/models/setting.model.dart'; +import 'package:immich_mobile/domain/services/setting.service.dart'; import 'package:immich_mobile/infrastructure/loaders/image_request.dart'; import 'package:immich_mobile/presentation/widgets/images/animated_image_stream_completer.dart'; import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; @@ -97,51 +97,56 @@ class LocalFullImageProvider extends CancellableImageProvider _codec(LocalFullImageProvider key, ImageDecoderCallback decode) async* { - yield* initialImageStream(); + final loadOriginal = AppSetting.get(Setting.loadOriginal); + final loadPreview = AppSetting.get(Setting.loadPreview); + yield* initialImageStream(isFinal: !loadOriginal && !loadPreview); if (isCancelled) { return; } - final loadOriginal = Store.get(StoreKey.loadOriginal, false); - final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio; - var request = this.request = LocalImageRequest( - localId: key.id, - size: Size(size.width * devicePixelRatio, size.height * devicePixelRatio), - assetType: key.assetType, - ); - yield* loadRequest(request, decode, isFinal: !loadOriginal); + if (loadPreview) { + final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio; + final previewRequest = request = LocalImageRequest( + localId: key.id, + size: Size(size.width * devicePixelRatio, size.height * devicePixelRatio), + assetType: key.assetType, + ); + yield* loadRequest(previewRequest, decode, isFinal: !loadOriginal); + + if (isCancelled) { + return; + } + } if (!loadOriginal) { return; } - if (isCancelled) { - return; - } + final originalRequest = 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, isFinal: true); + yield* loadRequest(originalRequest, decode, isFinal: true); } Stream _animatedCodec(LocalFullImageProvider key, ImageDecoderCallback decode) async* { - yield* initialImageStream(); + yield* initialImageStream(isFinal: false); if (isCancelled) { return; } - final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio; - final previewRequest = request = LocalImageRequest( - localId: key.id, - size: Size(size.width * devicePixelRatio, size.height * devicePixelRatio), - assetType: key.assetType, - ); - yield* loadRequest(previewRequest, decode, isFinal: false); + if (AppSetting.get(Setting.loadPreview)) { + final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio; + final previewRequest = request = LocalImageRequest( + localId: key.id, + size: Size(size.width * devicePixelRatio, size.height * devicePixelRatio), + assetType: key.assetType, + ); + yield* loadRequest(previewRequest, decode, isFinal: false); - if (isCancelled) { - return; + if (isCancelled) { + return; + } } // always try original for animated, since previews don't support animation diff --git a/mobile/lib/presentation/widgets/images/remote_image_provider.dart b/mobile/lib/presentation/widgets/images/remote_image_provider.dart index d9cc053ccf..2d4b2b1c28 100644 --- a/mobile/lib/presentation/widgets/images/remote_image_provider.dart +++ b/mobile/lib/presentation/widgets/images/remote_image_provider.dart @@ -102,44 +102,50 @@ class RemoteFullImageProvider extends CancellableImageProvider _codec(RemoteFullImageProvider key, ImageDecoderCallback decode) async* { - yield* initialImageStream(); + final isImage = assetType == AssetType.image; + final loadOriginal = isImage && AppSetting.get(Setting.loadOriginal); + final loadPreview = isImage && AppSetting.get(Setting.loadPreview); + yield* initialImageStream(isFinal: !loadOriginal && !loadPreview); if (isCancelled) { return; } - final previewRequest = request = RemoteImageRequest( - uri: getThumbnailUrlForRemoteId(key.assetId, type: AssetMediaSize.preview, thumbhash: key.thumbhash), - ); - final loadOriginal = assetType == AssetType.image && AppSetting.get(Setting.loadOriginal); - yield* loadRequest(previewRequest, decode, isFinal: !loadOriginal); + if (loadPreview) { + final previewRequest = request = RemoteImageRequest( + uri: getThumbnailUrlForRemoteId(key.assetId, type: AssetMediaSize.preview, thumbhash: key.thumbhash), + ); + yield* loadRequest(previewRequest, decode, isFinal: !loadOriginal); + + if (isCancelled) { + return; + } + } if (!loadOriginal) { return; } - if (isCancelled) { - return; - } - final originalRequest = request = RemoteImageRequest(uri: getOriginalUrlForRemoteId(key.assetId)); yield* loadRequest(originalRequest, decode, isFinal: true); } Stream _animatedCodec(RemoteFullImageProvider key, ImageDecoderCallback decode) async* { - yield* initialImageStream(); + yield* initialImageStream(isFinal: false); if (isCancelled) { return; } - final previewRequest = request = RemoteImageRequest( - uri: getThumbnailUrlForRemoteId(key.assetId, type: AssetMediaSize.preview, thumbhash: key.thumbhash), - ); - yield* loadRequest(previewRequest, decode, isFinal: false); + if (AppSetting.get(Setting.loadPreview)) { + final previewRequest = request = RemoteImageRequest( + uri: getThumbnailUrlForRemoteId(key.assetId, type: AssetMediaSize.preview, thumbhash: key.thumbhash), + ); + yield* loadRequest(previewRequest, decode, isFinal: false); - if (isCancelled) { - return; + if (isCancelled) { + return; + } } // always try original for animated, since previews don't support animation