2024-02-27 10:51:19 -05:00
|
|
|
import 'dart:async';
|
|
|
|
import 'dart:ui' as ui;
|
|
|
|
|
|
|
|
import 'package:cached_network_image/cached_network_image.dart';
|
2025-04-23 17:31:35 +02:00
|
|
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
|
|
|
import 'package:immich_mobile/providers/image/cache/thumbnail_image_cache_manager.dart';
|
2024-02-27 10:51:19 -05:00
|
|
|
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
import 'package:flutter/painting.dart';
|
2024-04-30 21:36:40 -05:00
|
|
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
2024-09-18 17:15:52 +02:00
|
|
|
import 'package:photo_manager/photo_manager.dart' show ThumbnailSize;
|
2025-04-23 17:31:35 +02:00
|
|
|
import 'package:logging/logging.dart';
|
2024-02-27 10:51:19 -05:00
|
|
|
|
|
|
|
/// The local image provider for an asset
|
|
|
|
/// Only viable
|
2024-03-11 23:04:52 -04:00
|
|
|
class ImmichLocalThumbnailProvider
|
|
|
|
extends ImageProvider<ImmichLocalThumbnailProvider> {
|
2024-02-27 10:51:19 -05:00
|
|
|
final Asset asset;
|
|
|
|
final int height;
|
|
|
|
final int width;
|
2025-04-23 17:31:35 +02:00
|
|
|
final CacheManager? cacheManager;
|
|
|
|
final Logger log = Logger("ImmichLocalThumbnailProvider");
|
|
|
|
final String? userId;
|
2024-02-27 10:51:19 -05:00
|
|
|
|
|
|
|
ImmichLocalThumbnailProvider({
|
|
|
|
required this.asset,
|
|
|
|
this.height = 256,
|
|
|
|
this.width = 256,
|
2025-04-23 17:31:35 +02:00
|
|
|
this.cacheManager,
|
|
|
|
this.userId,
|
2024-02-27 10:51:19 -05:00
|
|
|
}) : assert(asset.local != null, 'Only usable when asset.local is set');
|
|
|
|
|
|
|
|
/// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key
|
|
|
|
/// that describes the precise image to load.
|
|
|
|
@override
|
2024-03-11 23:04:52 -04:00
|
|
|
Future<ImmichLocalThumbnailProvider> obtainKey(
|
|
|
|
ImageConfiguration configuration,
|
|
|
|
) {
|
|
|
|
return SynchronousFuture(this);
|
2024-02-27 10:51:19 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2024-03-11 23:04:52 -04:00
|
|
|
ImageStreamCompleter loadImage(
|
|
|
|
ImmichLocalThumbnailProvider key,
|
|
|
|
ImageDecoderCallback decode,
|
|
|
|
) {
|
2025-04-23 17:31:35 +02:00
|
|
|
final cache = cacheManager ?? ThumbnailImageCacheManager();
|
2024-02-27 10:51:19 -05:00
|
|
|
return MultiImageStreamCompleter(
|
2025-04-23 17:31:35 +02:00
|
|
|
codec: _codec(key.asset, cache, decode),
|
2024-02-27 10:51:19 -05:00
|
|
|
scale: 1.0,
|
|
|
|
informationCollector: () sync* {
|
2025-04-23 09:02:51 -05:00
|
|
|
yield ErrorDescription(key.asset.fileName);
|
2024-02-27 10:51:19 -05:00
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Streams in each stage of the image as we ask for it
|
|
|
|
Stream<ui.Codec> _codec(
|
2025-04-23 09:02:51 -05:00
|
|
|
Asset assetData,
|
2025-04-23 17:31:35 +02:00
|
|
|
CacheManager cache,
|
2024-02-27 10:51:19 -05:00
|
|
|
ImageDecoderCallback decode,
|
|
|
|
) async* {
|
2025-04-23 17:31:35 +02:00
|
|
|
final cacheKey =
|
|
|
|
'$userId${assetData.localId}${assetData.checksum}$width$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 thumbnailBytes = await assetData.local?.thumbnailDataWithSize(
|
|
|
|
ThumbnailSize(width, height),
|
|
|
|
quality: 80,
|
|
|
|
);
|
|
|
|
if (thumbnailBytes == null) {
|
2024-02-27 10:51:19 -05:00
|
|
|
throw StateError(
|
2025-04-23 17:31:35 +02:00
|
|
|
"Loading thumb for local photo ${assetData.fileName} failed",
|
2024-02-27 10:51:19 -05:00
|
|
|
);
|
|
|
|
}
|
2025-04-22 12:15:54 -05:00
|
|
|
|
2025-04-23 17:31:35 +02:00
|
|
|
final buffer = await ui.ImmutableBuffer.fromUint8List(thumbnailBytes);
|
|
|
|
final codec = await decode(buffer);
|
|
|
|
yield codec;
|
|
|
|
await cache.putFile(cacheKey, thumbnailBytes);
|
2024-02-27 10:51:19 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
bool operator ==(Object other) {
|
|
|
|
if (identical(this, other)) return true;
|
2025-04-23 04:32:03 +02:00
|
|
|
if (other is ImmichLocalThumbnailProvider) {
|
|
|
|
return asset.id == other.asset.id && asset.localId == other.asset.localId;
|
|
|
|
}
|
|
|
|
return false;
|
2024-02-27 10:51:19 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2025-04-23 04:32:03 +02:00
|
|
|
int get hashCode => Object.hash(asset.id, asset.localId);
|
2024-02-27 10:51:19 -05:00
|
|
|
}
|