perf(mobile): remove small thumbnail and cache generated thumbnails (#17682)

* Remove small thumbnail and cache generated thumbnails

* Creating the small thumbnails takes quite some time, which should not be underestimated.
* The time needed to generate the small or big thumbnail is not too different from each other. Therefore there is no real benefit of the small thumbnail and it only adds frustration to the end user experience. That is because the image appeared to have loaded (the visual move from blur to something better) but it's still so bad that it is basically a blur. The better solution is therefore to stay at the blur until the actual thumbnail has loaded.
* Additionaly to the faster generation of the thumbnail, it now also gets cached similarly to the remote thumbnail which already gets cached. This further speeds up the all over usage of the app and prevents a repeatet thumbnail generation when opening the app.

* Decrease quality and use try catch

* Decreased the quality from the default 95 to 80 to provide similar quality with much reduces thumbnail size.
* Use try catch around the read of the cache file.

* Replace ImmutableBuffer.fromUint8List with ImmutableBuffer.fromFilePath

* Removed unnecessary comment

* Replace debugPrint with log.severe for catch of error

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
Toni 2025-04-21 19:51:37 +02:00 committed by GitHub
parent 56a4aa9ffe
commit b71039e83c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -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<ImageChunkEvent>();
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<ui.Codec> _codec(
Asset key,
CacheManager cache,
ImageDecoderCallback decode,
StreamController<ImageChunkEvent> 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