import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/painting.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/domain/services/setting.service.dart'; import 'package:immich_mobile/infrastructure/repositories/asset_media.repository.dart'; import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; import 'package:immich_mobile/presentation/widgets/images/one_frame_multi_image_stream_completer.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; class RemoteThumbProvider extends ImageProvider with CancellableImageProviderMixin { final String assetId; final CacheManager? cacheManager; RemoteThumbProvider({required this.assetId, this.cacheManager}); @override Future obtainKey(ImageConfiguration configuration) { return SynchronousFuture(this); } @override ImageStreamCompleter loadImage(RemoteThumbProvider key, ImageDecoderCallback decode) { final completer = OneFramePlaceholderImageStreamCompleter( _codec(key, decode), informationCollector: () => [ DiagnosticsProperty('Image provider', this), DiagnosticsProperty('Asset Id', key.assetId), ], ); completer.addOnLastListenerRemovedCallback(cancel); return completer; } Stream _codec(RemoteThumbProvider key, ImageDecoderCallback decode) async* { final preview = getThumbnailUrlForRemoteId(key.assetId); final request = this.request = RemoteImageRequest(uri: preview, headers: ApiService.getRequestHeaders()); try { final image = await request.load(decode); if (image != null) { yield image; } } finally { this.request = null; } } @override bool operator ==(Object other) { if (identical(this, other)) return true; if (other is RemoteThumbProvider) { return assetId == other.assetId; } return false; } @override int get hashCode => assetId.hashCode; } class RemoteFullImageProvider extends ImageProvider with CancellableImageProviderMixin { final String assetId; final CacheManager? cacheManager; RemoteFullImageProvider({required this.assetId, this.cacheManager}); @override Future obtainKey(ImageConfiguration configuration) { return SynchronousFuture(this); } @override ImageStreamCompleter loadImage(RemoteFullImageProvider key, ImageDecoderCallback decode) { final completer = OneFramePlaceholderImageStreamCompleter( _codec(key, decode), initialImage: getCachedImage(RemoteThumbProvider(assetId: assetId)), informationCollector: () => [ DiagnosticsProperty('Image provider', this), DiagnosticsProperty('Asset Id', key.assetId), ], ); completer.addOnLastListenerRemovedCallback(cancel); return completer; } Stream _codec(RemoteFullImageProvider key, ImageDecoderCallback decode) async* { try { final request = this.request = RemoteImageRequest( uri: getPreviewUrlForRemoteId(key.assetId), headers: ApiService.getRequestHeaders(), ); final image = await request.load(decode); if (image == null) { return; } yield image; } finally { request = null; } if (AppSetting.get(Setting.loadOriginal)) { try { final request = this.request = RemoteImageRequest( uri: getOriginalUrlForRemoteId(key.assetId), headers: ApiService.getRequestHeaders(), ); final image = await request.load(decode); if (image != null) { yield image; } } finally { request = null; } } } @override bool operator ==(Object other) { if (identical(this, other)) return true; if (other is RemoteFullImageProvider) { return assetId == other.assetId; } return false; } @override int get hashCode => assetId.hashCode; }