Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 28 additions & 21 deletions mobile/lib/providers/image/immich_local_thumbnail_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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);
},
Expand All @@ -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
Expand Down