mirror of
https://github.com/immich-app/immich.git
synced 2025-05-31 12:15:47 -04:00
feat(mobile): Remote thumbnails and images use an on-disk image cache (#7929)
* Fixes remote full / thumbnail provider * Adds image cache manager to both remote image providers format format Fix typo in equals remove unused import renames image loader * Adds height and width to the image cache for thumbs format * Uses a separate remote and thumbnail cache format * Fixes key name * Changes uri to string, fixes comment * Chunk events are optional and remote thumbnails don't report chunk events * better exception handling --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
parent
5a589babcb
commit
582cdcab82
@ -3,7 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/datetime_extensions.dart';
|
import 'package:immich_mobile/extensions/datetime_extensions.dart';
|
||||||
import 'package:immich_mobile/modules/activities/models/activity.model.dart';
|
import 'package:immich_mobile/modules/activities/models/activity.model.dart';
|
||||||
import 'package:immich_mobile/modules/asset_viewer/image_providers/immich_remote_image_provider.dart';
|
import 'package:immich_mobile/modules/asset_viewer/image_providers/immich_remote_thumbnail_provider.dart';
|
||||||
import 'package:immich_mobile/modules/asset_viewer/providers/current_asset.provider.dart';
|
import 'package:immich_mobile/modules/asset_viewer/providers/current_asset.provider.dart';
|
||||||
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
|
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
|
||||||
|
|
||||||
@ -106,9 +106,8 @@ class _ActivityAssetThumbnail extends StatelessWidget {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||||
image: DecorationImage(
|
image: DecorationImage(
|
||||||
image: ImmichRemoteImageProvider(
|
image: ImmichRemoteThumbnailProvider(
|
||||||
assetId: assetId,
|
assetId: assetId,
|
||||||
isThumbnail: true,
|
|
||||||
),
|
),
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
),
|
),
|
||||||
|
58
mobile/lib/modules/asset_viewer/image_providers/cache/image_loader.dart
vendored
Normal file
58
mobile/lib/modules/asset_viewer/image_providers/cache/image_loader.dart
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
|
import 'package:immich_mobile/modules/asset_viewer/image_providers/exceptions/image_loading_exception.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
|
|
||||||
|
/// Loads the codec from the URI and sends the events to the [chunkEvents] stream
|
||||||
|
///
|
||||||
|
/// Credit to [flutter_cached_network_image](https://github.com/Baseflow/flutter_cached_network_image/blob/develop/cached_network_image/lib/src/image_provider/_image_loader.dart)
|
||||||
|
/// for this wonderful implementation of their image loader
|
||||||
|
class ImageLoader {
|
||||||
|
static Future<ui.Codec> loadImageFromCache(
|
||||||
|
String uri, {
|
||||||
|
required ImageCacheManager cache,
|
||||||
|
required ImageDecoderCallback decode,
|
||||||
|
StreamController<ImageChunkEvent>? chunkEvents,
|
||||||
|
int? height,
|
||||||
|
int? width,
|
||||||
|
}) async {
|
||||||
|
final headers = {
|
||||||
|
'x-immich-user-token': Store.get(StoreKey.accessToken),
|
||||||
|
};
|
||||||
|
|
||||||
|
final stream = cache.getImageFile(
|
||||||
|
uri,
|
||||||
|
withProgress: true,
|
||||||
|
headers: headers,
|
||||||
|
maxHeight: height,
|
||||||
|
maxWidth: width,
|
||||||
|
);
|
||||||
|
|
||||||
|
await for (final result in stream) {
|
||||||
|
if (result is DownloadProgress) {
|
||||||
|
// We are downloading the file, so update the [chunkEvents]
|
||||||
|
chunkEvents?.add(
|
||||||
|
ImageChunkEvent(
|
||||||
|
cumulativeBytesLoaded: result.downloaded,
|
||||||
|
expectedTotalBytes: result.totalSize,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result is FileInfo) {
|
||||||
|
// We have the file
|
||||||
|
final file = result.file;
|
||||||
|
final bytes = await file.readAsBytes();
|
||||||
|
final buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
|
||||||
|
final decoded = await decode(buffer);
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, the image failed to load from the cache stream
|
||||||
|
throw ImageLoadingException('Could not load image from stream');
|
||||||
|
}
|
||||||
|
}
|
20
mobile/lib/modules/asset_viewer/image_providers/cache/remote_image_cache_manager.dart
vendored
Normal file
20
mobile/lib/modules/asset_viewer/image_providers/cache/remote_image_cache_manager.dart
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
|
|
||||||
|
/// The cache manager for full size images [ImmichRemoteImageProvider]
|
||||||
|
class RemoteImageCacheManager extends CacheManager with ImageCacheManager {
|
||||||
|
static const key = 'remoteImageCacheKey';
|
||||||
|
static final RemoteImageCacheManager _instance = RemoteImageCacheManager._();
|
||||||
|
|
||||||
|
factory RemoteImageCacheManager() {
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteImageCacheManager._()
|
||||||
|
: super(
|
||||||
|
Config(
|
||||||
|
key,
|
||||||
|
maxNrOfCacheObjects: 500,
|
||||||
|
stalePeriod: const Duration(days: 30),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
21
mobile/lib/modules/asset_viewer/image_providers/cache/thumbnail_image_cache_manager.dart
vendored
Normal file
21
mobile/lib/modules/asset_viewer/image_providers/cache/thumbnail_image_cache_manager.dart
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
|
|
||||||
|
/// The cache manager for thumbnail images [ImmichRemoteThumbnailProvider]
|
||||||
|
class ThumbnailImageCacheManager extends CacheManager with ImageCacheManager {
|
||||||
|
static const key = 'thumbnailImageCacheKey';
|
||||||
|
static final ThumbnailImageCacheManager _instance =
|
||||||
|
ThumbnailImageCacheManager._();
|
||||||
|
|
||||||
|
factory ThumbnailImageCacheManager() {
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
ThumbnailImageCacheManager._()
|
||||||
|
: super(
|
||||||
|
Config(
|
||||||
|
key,
|
||||||
|
maxNrOfCacheObjects: 5000,
|
||||||
|
stalePeriod: const Duration(days: 30),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
/// An exception for the [ImageLoader] and the Immich image providers
|
||||||
|
class ImageLoadingException implements Exception {
|
||||||
|
final String message;
|
||||||
|
ImageLoadingException(this.message);
|
||||||
|
}
|
@ -1,8 +1,10 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
|
import 'package:immich_mobile/modules/asset_viewer/image_providers/cache/image_loader.dart';
|
||||||
|
import 'package:immich_mobile/modules/asset_viewer/image_providers/cache/remote_image_cache_manager.dart';
|
||||||
import 'package:openapi/api.dart' as api;
|
import 'package:openapi/api.dart' as api;
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@ -12,24 +14,18 @@ import 'package:immich_mobile/shared/models/asset.dart';
|
|||||||
import 'package:immich_mobile/shared/models/store.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||||
|
|
||||||
/// Our Image Provider HTTP client to make the request
|
/// The remote image provider for full size remote images
|
||||||
final _httpClient = HttpClient()
|
|
||||||
..autoUncompress = false
|
|
||||||
..maxConnectionsPerHost = 10;
|
|
||||||
|
|
||||||
/// The remote image provider
|
|
||||||
class ImmichRemoteImageProvider
|
class ImmichRemoteImageProvider
|
||||||
extends ImageProvider<ImmichRemoteImageProvider> {
|
extends ImageProvider<ImmichRemoteImageProvider> {
|
||||||
/// The [Asset.remoteId] of the asset to fetch
|
/// The [Asset.remoteId] of the asset to fetch
|
||||||
final String assetId;
|
final String assetId;
|
||||||
|
|
||||||
// If this is a thumbnail, we stop at loading the
|
/// The image cache manager
|
||||||
// smallest version of the remote image
|
final ImageCacheManager? cacheManager;
|
||||||
final bool isThumbnail;
|
|
||||||
|
|
||||||
ImmichRemoteImageProvider({
|
ImmichRemoteImageProvider({
|
||||||
required this.assetId,
|
required this.assetId,
|
||||||
this.isThumbnail = false,
|
this.cacheManager,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key
|
/// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key
|
||||||
@ -46,9 +42,10 @@ class ImmichRemoteImageProvider
|
|||||||
ImmichRemoteImageProvider key,
|
ImmichRemoteImageProvider key,
|
||||||
ImageDecoderCallback decode,
|
ImageDecoderCallback decode,
|
||||||
) {
|
) {
|
||||||
|
final cache = cacheManager ?? RemoteImageCacheManager();
|
||||||
final chunkEvents = StreamController<ImageChunkEvent>();
|
final chunkEvents = StreamController<ImageChunkEvent>();
|
||||||
return MultiImageStreamCompleter(
|
return MultiImageStreamCompleter(
|
||||||
codec: _codec(key, decode, chunkEvents),
|
codec: _codec(key, cache, decode, chunkEvents),
|
||||||
scale: 1.0,
|
scale: 1.0,
|
||||||
chunkEvents: chunkEvents.stream,
|
chunkEvents: chunkEvents.stream,
|
||||||
);
|
);
|
||||||
@ -69,82 +66,61 @@ class ImmichRemoteImageProvider
|
|||||||
// Streams in each stage of the image as we ask for it
|
// Streams in each stage of the image as we ask for it
|
||||||
Stream<ui.Codec> _codec(
|
Stream<ui.Codec> _codec(
|
||||||
ImmichRemoteImageProvider key,
|
ImmichRemoteImageProvider key,
|
||||||
|
ImageCacheManager cache,
|
||||||
ImageDecoderCallback decode,
|
ImageDecoderCallback decode,
|
||||||
StreamController<ImageChunkEvent> chunkEvents,
|
StreamController<ImageChunkEvent> chunkEvents,
|
||||||
) async* {
|
) async* {
|
||||||
// Load a preview to the chunk events
|
// Load a preview to the chunk events
|
||||||
if (_loadPreview || key.isThumbnail) {
|
if (_loadPreview) {
|
||||||
final preview = getThumbnailUrlForRemoteId(
|
final preview = getThumbnailUrlForRemoteId(
|
||||||
key.assetId,
|
key.assetId,
|
||||||
type: api.ThumbnailFormat.WEBP,
|
type: api.ThumbnailFormat.WEBP,
|
||||||
);
|
);
|
||||||
|
|
||||||
yield await _loadFromUri(
|
yield await ImageLoader.loadImageFromCache(
|
||||||
Uri.parse(preview),
|
preview,
|
||||||
decode,
|
cache: cache,
|
||||||
chunkEvents,
|
decode: decode,
|
||||||
|
chunkEvents: chunkEvents,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Guard thumnbail rendering
|
|
||||||
if (key.isThumbnail) {
|
|
||||||
await chunkEvents.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the higher resolution version of the image
|
// Load the higher resolution version of the image
|
||||||
final url = getThumbnailUrlForRemoteId(
|
final url = getThumbnailUrlForRemoteId(
|
||||||
key.assetId,
|
key.assetId,
|
||||||
type: api.ThumbnailFormat.JPEG,
|
type: api.ThumbnailFormat.JPEG,
|
||||||
);
|
);
|
||||||
final codec = await _loadFromUri(Uri.parse(url), decode, chunkEvents);
|
final codec = await ImageLoader.loadImageFromCache(
|
||||||
|
url,
|
||||||
|
cache: cache,
|
||||||
|
decode: decode,
|
||||||
|
chunkEvents: chunkEvents,
|
||||||
|
);
|
||||||
yield codec;
|
yield codec;
|
||||||
|
|
||||||
// Load the final remote image
|
// Load the final remote image
|
||||||
if (_useOriginal) {
|
if (_useOriginal) {
|
||||||
// Load the original image
|
// Load the original image
|
||||||
final url = getImageUrlFromId(key.assetId);
|
final url = getImageUrlFromId(key.assetId);
|
||||||
final codec = await _loadFromUri(Uri.parse(url), decode, chunkEvents);
|
final codec = await ImageLoader.loadImageFromCache(
|
||||||
|
url,
|
||||||
|
cache: cache,
|
||||||
|
decode: decode,
|
||||||
|
chunkEvents: chunkEvents,
|
||||||
|
);
|
||||||
yield codec;
|
yield codec;
|
||||||
}
|
}
|
||||||
await chunkEvents.close();
|
await chunkEvents.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loads the codec from the URI and sends the events to the [chunkEvents] stream
|
|
||||||
Future<ui.Codec> _loadFromUri(
|
|
||||||
Uri uri,
|
|
||||||
ImageDecoderCallback decode,
|
|
||||||
StreamController<ImageChunkEvent> chunkEvents,
|
|
||||||
) async {
|
|
||||||
final request = await _httpClient.getUrl(uri);
|
|
||||||
request.headers.add(
|
|
||||||
'x-immich-user-token',
|
|
||||||
Store.get(StoreKey.accessToken),
|
|
||||||
);
|
|
||||||
final response = await request.close();
|
|
||||||
// Chunks of the completed image can be shown
|
|
||||||
final data = await consolidateHttpClientResponseBytes(
|
|
||||||
response,
|
|
||||||
onBytesReceived: (cumulative, total) {
|
|
||||||
chunkEvents.add(
|
|
||||||
ImageChunkEvent(
|
|
||||||
cumulativeBytesLoaded: cumulative,
|
|
||||||
expectedTotalBytes: total,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Decode the response
|
|
||||||
final buffer = await ui.ImmutableBuffer.fromUint8List(data);
|
|
||||||
return decode(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (other is! ImmichRemoteImageProvider) return false;
|
|
||||||
if (identical(this, other)) return true;
|
if (identical(this, other)) return true;
|
||||||
return assetId == other.assetId && isThumbnail == other.isThumbnail;
|
if (other is ImmichRemoteImageProvider) {
|
||||||
|
return assetId == other.assetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -1,30 +1,34 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:immich_mobile/modules/asset_viewer/image_providers/immich_remote_image_provider.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
|
import 'package:immich_mobile/modules/asset_viewer/image_providers/cache/image_loader.dart';
|
||||||
|
import 'package:immich_mobile/modules/asset_viewer/image_providers/cache/thumbnail_image_cache_manager.dart';
|
||||||
import 'package:openapi/api.dart' as api;
|
import 'package:openapi/api.dart' as api;
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/painting.dart';
|
import 'package:flutter/painting.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/models/store.dart';
|
|
||||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||||
|
|
||||||
/// Our HTTP client to make the request
|
|
||||||
final _httpClient = HttpClient()
|
|
||||||
..autoUncompress = false
|
|
||||||
..maxConnectionsPerHost = 100;
|
|
||||||
|
|
||||||
/// The remote image provider
|
/// The remote image provider
|
||||||
class ImmichRemoteThumbnailProvider
|
class ImmichRemoteThumbnailProvider
|
||||||
extends ImageProvider<ImmichRemoteThumbnailProvider> {
|
extends ImageProvider<ImmichRemoteThumbnailProvider> {
|
||||||
/// The [Asset.remoteId] of the asset to fetch
|
/// The [Asset.remoteId] of the asset to fetch
|
||||||
final String assetId;
|
final String assetId;
|
||||||
|
|
||||||
|
final int? height;
|
||||||
|
final int? width;
|
||||||
|
|
||||||
|
/// The image cache manager
|
||||||
|
final ImageCacheManager? cacheManager;
|
||||||
|
|
||||||
ImmichRemoteThumbnailProvider({
|
ImmichRemoteThumbnailProvider({
|
||||||
required this.assetId,
|
required this.assetId,
|
||||||
|
this.height,
|
||||||
|
this.width,
|
||||||
|
this.cacheManager,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key
|
/// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key
|
||||||
@ -41,19 +45,18 @@ class ImmichRemoteThumbnailProvider
|
|||||||
ImmichRemoteThumbnailProvider key,
|
ImmichRemoteThumbnailProvider key,
|
||||||
ImageDecoderCallback decode,
|
ImageDecoderCallback decode,
|
||||||
) {
|
) {
|
||||||
final chunkEvents = StreamController<ImageChunkEvent>();
|
final cache = cacheManager ?? ThumbnailImageCacheManager();
|
||||||
return MultiImageStreamCompleter(
|
return MultiImageStreamCompleter(
|
||||||
codec: _codec(key, decode, chunkEvents),
|
codec: _codec(key, cache, decode),
|
||||||
scale: 1.0,
|
scale: 1.0,
|
||||||
chunkEvents: chunkEvents.stream,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Streams in each stage of the image as we ask for it
|
// Streams in each stage of the image as we ask for it
|
||||||
Stream<ui.Codec> _codec(
|
Stream<ui.Codec> _codec(
|
||||||
ImmichRemoteThumbnailProvider key,
|
ImmichRemoteThumbnailProvider key,
|
||||||
|
ImageCacheManager cache,
|
||||||
ImageDecoderCallback decode,
|
ImageDecoderCallback decode,
|
||||||
StreamController<ImageChunkEvent> chunkEvents,
|
|
||||||
) async* {
|
) async* {
|
||||||
// Load a preview to the chunk events
|
// Load a preview to the chunk events
|
||||||
final preview = getThumbnailUrlForRemoteId(
|
final preview = getThumbnailUrlForRemoteId(
|
||||||
@ -61,52 +64,23 @@ class ImmichRemoteThumbnailProvider
|
|||||||
type: api.ThumbnailFormat.WEBP,
|
type: api.ThumbnailFormat.WEBP,
|
||||||
);
|
);
|
||||||
|
|
||||||
yield await _loadFromUri(
|
yield await ImageLoader.loadImageFromCache(
|
||||||
Uri.parse(preview),
|
preview,
|
||||||
decode,
|
cache: cache,
|
||||||
chunkEvents,
|
decode: decode,
|
||||||
);
|
);
|
||||||
|
|
||||||
await chunkEvents.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loads the codec from the URI and sends the events to the [chunkEvents] stream
|
|
||||||
Future<ui.Codec> _loadFromUri(
|
|
||||||
Uri uri,
|
|
||||||
ImageDecoderCallback decode,
|
|
||||||
StreamController<ImageChunkEvent> chunkEvents,
|
|
||||||
) async {
|
|
||||||
final request = await _httpClient.getUrl(uri);
|
|
||||||
request.headers.add(
|
|
||||||
'x-immich-user-token',
|
|
||||||
Store.get(StoreKey.accessToken),
|
|
||||||
);
|
|
||||||
final response = await request.close();
|
|
||||||
// Chunks of the completed image can be shown
|
|
||||||
final data = await consolidateHttpClientResponseBytes(
|
|
||||||
response,
|
|
||||||
onBytesReceived: (cumulative, total) {
|
|
||||||
chunkEvents.add(
|
|
||||||
ImageChunkEvent(
|
|
||||||
cumulativeBytesLoaded: cumulative,
|
|
||||||
expectedTotalBytes: total,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Decode the response
|
|
||||||
final buffer = await ui.ImmutableBuffer.fromUint8List(data);
|
|
||||||
return decode(buffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (other is! ImmichRemoteImageProvider) return false;
|
|
||||||
if (identical(this, other)) return true;
|
if (identical(this, other)) return true;
|
||||||
|
if (other is ImmichRemoteThumbnailProvider) {
|
||||||
return assetId == other.assetId;
|
return assetId == other.assetId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => assetId.hashCode;
|
int get hashCode => assetId.hashCode;
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,6 @@ class ImmichImage extends StatelessWidget {
|
|||||||
if (asset == null) {
|
if (asset == null) {
|
||||||
return ImmichRemoteImageProvider(
|
return ImmichRemoteImageProvider(
|
||||||
assetId: assetId!,
|
assetId: assetId!,
|
||||||
isThumbnail: false,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +52,6 @@ class ImmichImage extends StatelessWidget {
|
|||||||
} else {
|
} else {
|
||||||
return ImmichRemoteImageProvider(
|
return ImmichRemoteImageProvider(
|
||||||
assetId: asset.remoteId!,
|
assetId: asset.remoteId!,
|
||||||
isThumbnail: false,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import 'dart:typed_data';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:immich_mobile/modules/asset_viewer/image_providers/immich_local_thumbnail_provider.dart';
|
import 'package:immich_mobile/modules/asset_viewer/image_providers/immich_local_thumbnail_provider.dart';
|
||||||
import 'package:immich_mobile/modules/asset_viewer/image_providers/immich_remote_image_provider.dart';
|
import 'package:immich_mobile/modules/asset_viewer/image_providers/immich_remote_thumbnail_provider.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/ui/hooks/blurhash_hook.dart';
|
import 'package:immich_mobile/shared/ui/hooks/blurhash_hook.dart';
|
||||||
import 'package:immich_mobile/shared/ui/immich_image.dart';
|
import 'package:immich_mobile/shared/ui/immich_image.dart';
|
||||||
@ -38,9 +38,8 @@ class ImmichThumbnail extends HookWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (asset == null) {
|
if (asset == null) {
|
||||||
return ImmichRemoteImageProvider(
|
return ImmichRemoteThumbnailProvider(
|
||||||
assetId: assetId!,
|
assetId: assetId!,
|
||||||
isThumbnail: true,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,9 +50,10 @@ class ImmichThumbnail extends HookWidget {
|
|||||||
width: thumbnailSize,
|
width: thumbnailSize,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return ImmichRemoteImageProvider(
|
return ImmichRemoteThumbnailProvider(
|
||||||
assetId: asset.remoteId!,
|
assetId: asset.remoteId!,
|
||||||
isThumbnail: true,
|
height: thumbnailSize,
|
||||||
|
width: thumbnailSize,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user