Skip to content

Commit b71039e

Browse files
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 <[email protected]>
1 parent 56a4aa9 commit b71039e

File tree

1 file changed

+28
-21
lines changed

1 file changed

+28
-21
lines changed

mobile/lib/providers/image/immich_local_thumbnail_provider.dart

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ import 'dart:async';
22
import 'dart:ui' as ui;
33

44
import 'package:cached_network_image/cached_network_image.dart';
5+
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
6+
import 'package:immich_mobile/providers/image/cache/thumbnail_image_cache_manager.dart';
57

68
import 'package:flutter/foundation.dart';
79
import 'package:flutter/painting.dart';
810
import 'package:immich_mobile/entities/asset.entity.dart';
911
import 'package:photo_manager/photo_manager.dart' show ThumbnailSize;
12+
import 'package:logging/logging.dart';
1013

1114
/// The local image provider for an asset
1215
/// Only viable
@@ -15,11 +18,14 @@ class ImmichLocalThumbnailProvider
1518
final Asset asset;
1619
final int height;
1720
final int width;
21+
final CacheManager? cacheManager;
22+
final Logger log = Logger("ImmichLocalThumbnailProvider");
1823

1924
ImmichLocalThumbnailProvider({
2025
required this.asset,
2126
this.height = 256,
2227
this.width = 256,
28+
this.cacheManager,
2329
}) : assert(asset.local != null, 'Only usable when asset.local is set');
2430

2531
/// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key
@@ -36,11 +42,10 @@ class ImmichLocalThumbnailProvider
3642
ImmichLocalThumbnailProvider key,
3743
ImageDecoderCallback decode,
3844
) {
39-
final chunkEvents = StreamController<ImageChunkEvent>();
45+
final cache = cacheManager ?? ThumbnailImageCacheManager();
4046
return MultiImageStreamCompleter(
41-
codec: _codec(key.asset, decode, chunkEvents),
47+
codec: _codec(key.asset, cache, decode),
4248
scale: 1.0,
43-
chunkEvents: chunkEvents.stream,
4449
informationCollector: () sync* {
4550
yield ErrorDescription(asset.fileName);
4651
},
@@ -50,34 +55,36 @@ class ImmichLocalThumbnailProvider
5055
// Streams in each stage of the image as we ask for it
5156
Stream<ui.Codec> _codec(
5257
Asset key,
58+
CacheManager cache,
5359
ImageDecoderCallback decode,
54-
StreamController<ImageChunkEvent> chunkEvents,
5560
) async* {
56-
// Load a small thumbnail
57-
final thumbBytes = await asset.local?.thumbnailDataWithSize(
58-
const ThumbnailSize.square(32),
59-
quality: 75,
60-
);
61-
if (thumbBytes != null) {
62-
final buffer = await ui.ImmutableBuffer.fromUint8List(thumbBytes);
63-
final codec = await decode(buffer);
64-
yield codec;
65-
} else {
66-
debugPrint("Loading thumb for ${asset.fileName} failed");
61+
final cacheKey = '${key.id}_${width}x$height';
62+
final fileFromCache = await cache.getFileFromCache(cacheKey);
63+
if (fileFromCache != null) {
64+
try {
65+
final buffer =
66+
await ui.ImmutableBuffer.fromFilePath(fileFromCache.file.path);
67+
final codec = await decode(buffer);
68+
yield codec;
69+
return;
70+
} catch (error) {
71+
log.severe('Found thumbnail in cache, but loading it failed', error);
72+
}
6773
}
6874

69-
final normalThumbBytes =
70-
await asset.local?.thumbnailDataWithSize(ThumbnailSize(width, height));
71-
if (normalThumbBytes == null) {
75+
final thumbnailBytes = await asset.local?.thumbnailDataWithSize(
76+
ThumbnailSize(width, height),
77+
quality: 80,
78+
);
79+
if (thumbnailBytes == null) {
7280
throw StateError(
7381
"Loading thumb for local photo ${asset.fileName} failed",
7482
);
7583
}
76-
final buffer = await ui.ImmutableBuffer.fromUint8List(normalThumbBytes);
84+
final buffer = await ui.ImmutableBuffer.fromUint8List(thumbnailBytes);
7785
final codec = await decode(buffer);
7886
yield codec;
79-
80-
chunkEvents.close();
87+
await cache.putFile(cacheKey, thumbnailBytes);
8188
}
8289

8390
@override

0 commit comments

Comments
 (0)