diff --git a/mobile/lib/providers/image/immich_local_thumbnail_provider.dart b/mobile/lib/providers/image/immich_local_thumbnail_provider.dart index 69cdb105c0..93245bd78e 100644 --- a/mobile/lib/providers/image/immich_local_thumbnail_provider.dart +++ b/mobile/lib/providers/image/immich_local_thumbnail_provider.dart @@ -2,11 +2,14 @@ import 'dart:async'; import 'dart:ui' as ui; import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter_cache_manager/flutter_cache_manager.dart'; +import 'package:immich_mobile/providers/image/cache/thumbnail_image_cache_manager.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/painting.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:photo_manager/photo_manager.dart' show ThumbnailSize; +import 'package:logging/logging.dart'; /// The local image provider for an asset /// Only viable @@ -15,11 +18,14 @@ class ImmichLocalThumbnailProvider final Asset asset; final int height; final int width; + final CacheManager? cacheManager; + final Logger log = Logger("ImmichLocalThumbnailProvider"); ImmichLocalThumbnailProvider({ required this.asset, this.height = 256, this.width = 256, + this.cacheManager, }) : assert(asset.local != null, 'Only usable when asset.local is set'); /// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key @@ -36,11 +42,10 @@ class ImmichLocalThumbnailProvider ImmichLocalThumbnailProvider key, ImageDecoderCallback decode, ) { - final chunkEvents = StreamController(); + final cache = cacheManager ?? ThumbnailImageCacheManager(); return MultiImageStreamCompleter( - codec: _codec(key.asset, decode, chunkEvents), + codec: _codec(key.asset, cache, decode), scale: 1.0, - chunkEvents: chunkEvents.stream, informationCollector: () sync* { yield ErrorDescription(asset.fileName); }, @@ -50,34 +55,36 @@ class ImmichLocalThumbnailProvider // Streams in each stage of the image as we ask for it Stream _codec( Asset key, + CacheManager cache, ImageDecoderCallback decode, - StreamController chunkEvents, ) async* { - // Load a small thumbnail - final thumbBytes = await asset.local?.thumbnailDataWithSize( - const ThumbnailSize.square(32), - quality: 75, - ); - if (thumbBytes != null) { - final buffer = await ui.ImmutableBuffer.fromUint8List(thumbBytes); - final codec = await decode(buffer); - yield codec; - } else { - debugPrint("Loading thumb for ${asset.fileName} failed"); + final cacheKey = '${key.id}_${width}x$height'; + final fileFromCache = await cache.getFileFromCache(cacheKey); + if (fileFromCache != null) { + try { + final buffer = + await ui.ImmutableBuffer.fromFilePath(fileFromCache.file.path); + final codec = await decode(buffer); + yield codec; + return; + } catch (error) { + log.severe('Found thumbnail in cache, but loading it failed', error); + } } - final normalThumbBytes = - await asset.local?.thumbnailDataWithSize(ThumbnailSize(width, height)); - if (normalThumbBytes == null) { + final thumbnailBytes = await asset.local?.thumbnailDataWithSize( + ThumbnailSize(width, height), + quality: 80, + ); + if (thumbnailBytes == null) { throw StateError( "Loading thumb for local photo ${asset.fileName} failed", ); } - final buffer = await ui.ImmutableBuffer.fromUint8List(normalThumbBytes); + final buffer = await ui.ImmutableBuffer.fromUint8List(thumbnailBytes); final codec = await decode(buffer); yield codec; - - chunkEvents.close(); + await cache.putFile(cacheKey, thumbnailBytes); } @override