From 51e7dcf0e8225b17860da51ce2f8e77809f7a049 Mon Sep 17 00:00:00 2001 From: mertalev <101130780+mertalev@users.noreply.github.com> Date: Fri, 30 Jan 2026 22:57:22 -0500 Subject: [PATCH] remote url image provider remove cached_network_image formatting linting remove thumb provider formatting --- .../backup/failed_backup_status.page.dart | 5 +- .../common/gallery_stacked_children.dart | 4 +- mobile/lib/pages/library/library.page.dart | 9 +- .../people/people_collection.page.dart | 5 +- .../places/places_collection.page.dart | 11 +-- .../lib/pages/search/person_result.page.dart | 7 +- .../pages/drift_library.page.dart | 9 +- .../pages/drift_people_collection.page.dart | 5 +- .../sheet_people_details.widget.dart | 6 +- .../widgets/images/image_provider.dart | 2 +- .../widgets/images/remote_image_provider.dart | 34 ++++--- .../widgets/images/thumbnail.widget.dart | 2 +- .../people/partner_user_avatar.widget.dart | 9 +- .../image/immich_local_image_provider.dart | 94 ------------------- .../immich_local_thumbnail_provider.dart | 88 ----------------- .../image/immich_remote_image_provider.dart | 82 ---------------- .../immich_remote_thumbnail_provider.dart | 61 ------------ .../lib/utils/cache/custom_image_cache.dart | 11 +-- .../lib/widgets/activities/activity_tile.dart | 4 +- .../widgets/activities/comment_bubble.dart | 4 +- .../album/album_thumbnail_listtile.dart | 15 ++- mobile/lib/widgets/common/immich_image.dart | 19 +++- .../lib/widgets/common/immich_thumbnail.dart | 21 +++-- .../widgets/common/person_sliver_app_bar.dart | 7 +- mobile/lib/widgets/common/user_avatar.dart | 9 +- .../widgets/common/user_circle_avatar.dart | 14 +-- .../map/positioned_asset_marker_icon.dart | 11 +-- .../widgets/search/curated_people_row.dart | 5 +- .../search/search_filter/people_picker.dart | 5 +- .../widgets/search/thumbnail_with_info.dart | 13 +-- mobile/pubspec.lock | 32 +------ mobile/pubspec.yaml | 1 - 32 files changed, 99 insertions(+), 505 deletions(-) delete mode 100644 mobile/lib/providers/image/immich_local_image_provider.dart delete mode 100644 mobile/lib/providers/image/immich_local_thumbnail_provider.dart delete mode 100644 mobile/lib/providers/image/immich_remote_image_provider.dart delete mode 100644 mobile/lib/providers/image/immich_remote_thumbnail_provider.dart diff --git a/mobile/lib/pages/backup/failed_backup_status.page.dart b/mobile/lib/pages/backup/failed_backup_status.page.dart index b533895cd7..a97a133b89 100644 --- a/mobile/lib/pages/backup/failed_backup_status.page.dart +++ b/mobile/lib/pages/backup/failed_backup_status.page.dart @@ -2,9 +2,10 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/images/local_image_provider.dart'; import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart'; -import 'package:immich_mobile/providers/image/immich_local_thumbnail_provider.dart'; import 'package:intl/intl.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' as base_asset; @RoutePage() class FailedBackupStatusPage extends HookConsumerWidget { @@ -58,7 +59,7 @@ class FailedBackupStatusPage extends HookConsumerWidget { clipBehavior: Clip.hardEdge, child: Image( fit: BoxFit.cover, - image: ImmichLocalThumbnailProvider(asset: errorAsset.asset, height: 512, width: 512), + image: LocalThumbProvider(id: errorAsset.asset.localId!, assetType: base_asset.AssetType.video), ), ), ), diff --git a/mobile/lib/pages/common/gallery_stacked_children.dart b/mobile/lib/pages/common/gallery_stacked_children.dart index 7145bc2553..68123509ae 100644 --- a/mobile/lib/pages/common/gallery_stacked_children.dart +++ b/mobile/lib/pages/common/gallery_stacked_children.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/providers/asset_viewer/asset_stack.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart'; -import 'package:immich_mobile/providers/image/immich_remote_image_provider.dart'; class GalleryStackedChildren extends HookConsumerWidget { final ValueNotifier stackIndex; @@ -70,7 +70,7 @@ class GalleryStackedChildren extends HookConsumerWidget { borderRadius: const BorderRadius.all(Radius.circular(4)), child: Image( fit: BoxFit.cover, - image: ImmichRemoteImageProvider(assetId: assetId), + image: RemoteImageProvider.thumbnail(assetId: assetId, thumbhash: asset.thumbhash ?? ""), ), ), ), diff --git a/mobile/lib/pages/library/library.page.dart b/mobile/lib/pages/library/library.page.dart index 483427d2de..6332a662b9 100644 --- a/mobile/lib/pages/library/library.page.dart +++ b/mobile/lib/pages/library/library.page.dart @@ -11,7 +11,7 @@ import 'package:immich_mobile/providers/partner.provider.dart'; import 'package:immich_mobile/providers/search/people.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/widgets/album/album_thumbnail_card.dart'; import 'package:immich_mobile/widgets/common/immich_app_bar.dart'; @@ -221,12 +221,7 @@ class PeopleCollectionCard extends ConsumerWidget { mainAxisSpacing: 8, physics: const NeverScrollableScrollPhysics(), children: people.take(4).map((person) { - return CircleAvatar( - backgroundImage: NetworkImage( - getFaceThumbnailUrl(person.id), - headers: ApiService.getRequestHeaders(), - ), - ); + return CircleAvatar(backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(person.id))); }).toList(), ); }, diff --git a/mobile/lib/pages/library/people/people_collection.page.dart b/mobile/lib/pages/library/people/people_collection.page.dart index 375d4d2a96..bff52df6da 100644 --- a/mobile/lib/pages/library/people/people_collection.page.dart +++ b/mobile/lib/pages/library/people/people_collection.page.dart @@ -5,8 +5,8 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/search/people.provider.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/widgets/common/search_field.dart'; import 'package:immich_mobile/widgets/search/person_name_edit_form.dart'; @@ -17,7 +17,6 @@ class PeopleCollectionPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final people = ref.watch(getAllPeopleProvider); - final headers = ApiService.getRequestHeaders(); final formFocus = useFocusNode(); final ValueNotifier search = useState(null); @@ -88,7 +87,7 @@ class PeopleCollectionPage extends HookConsumerWidget { elevation: 3, child: CircleAvatar( maxRadius: isTablet ? 120 / 2 : 96 / 2, - backgroundImage: NetworkImage(getFaceThumbnailUrl(person.id), headers: headers), + backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(person.id)), ), ), ), diff --git a/mobile/lib/pages/library/places/places_collection.page.dart b/mobile/lib/pages/library/places/places_collection.page.dart index d6511cb25b..a4a6f66915 100644 --- a/mobile/lib/pages/library/places/places_collection.page.dart +++ b/mobile/lib/pages/library/places/places_collection.page.dart @@ -1,5 +1,4 @@ import 'package:auto_route/auto_route.dart'; -import 'package:cached_network_image/cached_network_image.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; @@ -10,9 +9,10 @@ import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart'; import 'package:immich_mobile/pages/common/large_leading_tile.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; +import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/providers/search/search_page_state.provider.dart'; import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/widgets/common/search_field.dart'; import 'package:immich_mobile/widgets/map/map_thumbnail.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; @@ -125,13 +125,10 @@ class PlaceTile extends StatelessWidget { title: Text(name, style: context.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500)), leading: ClipRRect( borderRadius: const BorderRadius.all(Radius.circular(20)), - child: CachedNetworkImage( + child: SizedBox( width: 80, height: 80, - fit: BoxFit.cover, - imageUrl: thumbnailUrl, - httpHeaders: ApiService.getRequestHeaders(), - errorWidget: (context, url, error) => const Icon(Icons.image_not_supported_outlined), + child: Thumbnail(imageProvider: RemoteImageProvider(url: thumbnailUrl)), ), ), ); diff --git a/mobile/lib/pages/search/person_result.page.dart b/mobile/lib/pages/search/person_result.page.dart index 7d2e612d25..8375eb14fd 100644 --- a/mobile/lib/pages/search/person_result.page.dart +++ b/mobile/lib/pages/search/person_result.page.dart @@ -4,8 +4,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/providers/search/people.provider.dart'; -import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/widgets/search/person_name_edit_form.dart'; import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; @@ -88,10 +88,7 @@ class PersonResultPage extends HookConsumerWidget { padding: const EdgeInsets.only(left: 8.0, top: 24), child: Row( children: [ - CircleAvatar( - radius: 36, - backgroundImage: NetworkImage(getFaceThumbnailUrl(personId), headers: ApiService.getRequestHeaders()), - ), + CircleAvatar(radius: 36, backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(personId))), Expanded( child: Padding(padding: const EdgeInsets.only(left: 16.0, right: 16.0), child: buildTitleBlock()), ), diff --git a/mobile/lib/presentation/pages/drift_library.page.dart b/mobile/lib/presentation/pages/drift_library.page.dart index d1d663e4f4..4708b5e615 100644 --- a/mobile/lib/presentation/pages/drift_library.page.dart +++ b/mobile/lib/presentation/pages/drift_library.page.dart @@ -12,8 +12,8 @@ import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/partner.provider.dart'; import 'package:immich_mobile/providers/infrastructure/people.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart'; import 'package:immich_mobile/widgets/map/map_thumbnail.dart'; @@ -179,12 +179,7 @@ class _PeopleCollectionCard extends ConsumerWidget { mainAxisSpacing: 8, physics: const NeverScrollableScrollPhysics(), children: people.take(4).map((person) { - return CircleAvatar( - backgroundImage: NetworkImage( - getFaceThumbnailUrl(person.id), - headers: ApiService.getRequestHeaders(), - ), - ); + return CircleAvatar(backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(person.id))); }).toList(), ); }, diff --git a/mobile/lib/presentation/pages/drift_people_collection.page.dart b/mobile/lib/presentation/pages/drift_people_collection.page.dart index ca4e20aad0..f73dac3af2 100644 --- a/mobile/lib/presentation/pages/drift_people_collection.page.dart +++ b/mobile/lib/presentation/pages/drift_people_collection.page.dart @@ -4,8 +4,8 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/infrastructure/people.provider.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/utils/people.utils.dart'; import 'package:immich_mobile/widgets/common/search_field.dart'; @@ -31,7 +31,6 @@ class _DriftPeopleCollectionPageState extends ConsumerState diff --git a/mobile/lib/presentation/widgets/images/remote_image_provider.dart b/mobile/lib/presentation/widgets/images/remote_image_provider.dart index 08d1ef13db..6cb68c1442 100644 --- a/mobile/lib/presentation/widgets/images/remote_image_provider.dart +++ b/mobile/lib/presentation/widgets/images/remote_image_provider.dart @@ -10,50 +10,48 @@ import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:openapi/api.dart'; -class RemoteThumbProvider extends CancellableImageProvider - with CancellableImageProviderMixin { - final String assetId; - final String thumbhash; +class RemoteImageProvider extends CancellableImageProvider + with CancellableImageProviderMixin { + final String url; - RemoteThumbProvider({required this.assetId, required this.thumbhash}); + RemoteImageProvider({required this.url}); + + RemoteImageProvider.thumbnail({required String assetId, required String thumbhash}) + : url = getThumbnailUrlForRemoteId(assetId, thumbhash: thumbhash); @override - Future obtainKey(ImageConfiguration configuration) { + Future obtainKey(ImageConfiguration configuration) { return SynchronousFuture(this); } @override - ImageStreamCompleter loadImage(RemoteThumbProvider key, ImageDecoderCallback decode) { + ImageStreamCompleter loadImage(RemoteImageProvider key, ImageDecoderCallback decode) { return OneFramePlaceholderImageStreamCompleter( _codec(key, decode), informationCollector: () => [ DiagnosticsProperty('Image provider', this), - DiagnosticsProperty('Asset Id', key.assetId), + DiagnosticsProperty('URL', key.url), ], onDispose: cancel, ); } - Stream _codec(RemoteThumbProvider key, ImageDecoderCallback decode) { - final request = this.request = RemoteImageRequest( - uri: getThumbnailUrlForRemoteId(key.assetId, thumbhash: key.thumbhash), - headers: ApiService.getRequestHeaders(), - ); + Stream _codec(RemoteImageProvider key, ImageDecoderCallback decode) { + final request = this.request = RemoteImageRequest(uri: key.url, headers: ApiService.getRequestHeaders()); return loadRequest(request, decode); } @override bool operator ==(Object other) { if (identical(this, other)) return true; - if (other is RemoteThumbProvider) { - return assetId == other.assetId && thumbhash == other.thumbhash; + if (other is RemoteImageProvider) { + return url == other.url; } - return false; } @override - int get hashCode => assetId.hashCode ^ thumbhash.hashCode; + int get hashCode => url.hashCode; } class RemoteFullImageProvider extends CancellableImageProvider @@ -73,7 +71,7 @@ class RemoteFullImageProvider extends CancellableImageProvider [ DiagnosticsProperty('Image provider', this), DiagnosticsProperty('Asset Id', key.assetId), diff --git a/mobile/lib/presentation/widgets/images/thumbnail.widget.dart b/mobile/lib/presentation/widgets/images/thumbnail.widget.dart index f878c214a9..d35dd181db 100644 --- a/mobile/lib/presentation/widgets/images/thumbnail.widget.dart +++ b/mobile/lib/presentation/widgets/images/thumbnail.widget.dart @@ -27,7 +27,7 @@ class Thumbnail extends StatefulWidget { this.fit = BoxFit.cover, Size size = kThumbnailResolution, super.key, - }) : imageProvider = RemoteThumbProvider(assetId: remoteId, thumbhash: thumbhash), + }) : imageProvider = RemoteImageProvider.thumbnail(assetId: remoteId, thumbhash: thumbhash), thumbhashProvider = null; Thumbnail.fromAsset({ diff --git a/mobile/lib/presentation/widgets/people/partner_user_avatar.widget.dart b/mobile/lib/presentation/widgets/people/partner_user_avatar.widget.dart index 8cdf1ed286..8b391d50c6 100644 --- a/mobile/lib/presentation/widgets/people/partner_user_avatar.widget.dart +++ b/mobile/lib/presentation/widgets/people/partner_user_avatar.widget.dart @@ -1,10 +1,9 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; class PartnerUserAvatar extends StatelessWidget { const PartnerUserAvatar({super.key, required this.partner}); @@ -18,11 +17,7 @@ class PartnerUserAvatar extends StatelessWidget { return CircleAvatar( radius: 16, backgroundColor: context.primaryColor.withAlpha(50), - foregroundImage: CachedNetworkImageProvider( - url, - headers: ApiService.getRequestHeaders(), - cacheKey: "user-${partner.id}-profile", - ), + foregroundImage: RemoteImageProvider(url: url), // silence errors if user has no profile image, use initials as fallback onForegroundImageError: (exception, stackTrace) {}, child: Text(nameFirstLetter.toUpperCase()), diff --git a/mobile/lib/providers/image/immich_local_image_provider.dart b/mobile/lib/providers/image/immich_local_image_provider.dart deleted file mode 100644 index b9e09eb357..0000000000 --- a/mobile/lib/providers/image/immich_local_image_provider.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'dart:async'; -import 'dart:io'; -import 'dart:ui' as ui; - -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/painting.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:logging/logging.dart'; -import 'package:photo_manager/photo_manager.dart' show ThumbnailSize; - -/// The local image provider for an asset -class ImmichLocalImageProvider extends ImageProvider { - final Asset asset; - // only used for videos - final double width; - final double height; - final Logger log = Logger('ImmichLocalImageProvider'); - - ImmichLocalImageProvider({required this.asset, required this.width, required this.height}) - : 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 - Future obtainKey(ImageConfiguration configuration) { - return SynchronousFuture(this); - } - - @override - ImageStreamCompleter loadImage(ImmichLocalImageProvider key, ImageDecoderCallback decode) { - final chunkEvents = StreamController(); - return MultiImageStreamCompleter( - codec: _codec(key.asset, decode, chunkEvents), - scale: 1.0, - chunkEvents: chunkEvents.stream, - informationCollector: () sync* { - yield ErrorDescription(asset.fileName); - }, - ); - } - - // Streams in each stage of the image as we ask for it - Stream _codec( - Asset asset, - ImageDecoderCallback decode, - StreamController chunkEvents, - ) async* { - try { - final local = asset.local; - if (local == null) { - throw StateError('Asset ${asset.fileName} has no local data'); - } - - switch (asset.type) { - case AssetType.image: - final File? file = await local.originFile; - if (file == null) { - throw StateError("Opening file for asset ${asset.fileName} failed"); - } - final buffer = await ui.ImmutableBuffer.fromFilePath(file.path); - yield await decode(buffer); - break; - case AssetType.video: - final size = ThumbnailSize(width.ceil(), height.ceil()); - final thumbBytes = await local.thumbnailDataWithSize(size); - if (thumbBytes == null) { - throw StateError("Failed to load preview for ${asset.fileName}"); - } - final buffer = await ui.ImmutableBuffer.fromUint8List(thumbBytes); - yield await decode(buffer); - break; - default: - throw StateError('Unsupported asset type ${asset.type}'); - } - } catch (error, stack) { - log.severe('Error loading local image ${asset.fileName}', error, stack); - } finally { - unawaited(chunkEvents.close()); - } - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - if (other is ImmichLocalImageProvider) { - return asset.id == other.asset.id && asset.localId == other.asset.localId; - } - return false; - } - - @override - int get hashCode => Object.hash(asset.id, asset.localId); -} diff --git a/mobile/lib/providers/image/immich_local_thumbnail_provider.dart b/mobile/lib/providers/image/immich_local_thumbnail_provider.dart deleted file mode 100644 index 5edb0fc79e..0000000000 --- a/mobile/lib/providers/image/immich_local_thumbnail_provider.dart +++ /dev/null @@ -1,88 +0,0 @@ -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 -class ImmichLocalThumbnailProvider extends ImageProvider { - final Asset asset; - final int height; - final int width; - final CacheManager? cacheManager; - final Logger log = Logger("ImmichLocalThumbnailProvider"); - final String? userId; - - ImmichLocalThumbnailProvider({ - required this.asset, - this.height = 256, - this.width = 256, - this.cacheManager, - this.userId, - }) : 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 - Future obtainKey(ImageConfiguration configuration) { - return SynchronousFuture(this); - } - - @override - ImageStreamCompleter loadImage(ImmichLocalThumbnailProvider key, ImageDecoderCallback decode) { - final cache = cacheManager ?? ThumbnailImageCacheManager(); - return MultiImageStreamCompleter( - codec: _codec(key.asset, cache, decode), - scale: 1.0, - informationCollector: () sync* { - yield ErrorDescription(key.asset.fileName); - }, - ); - } - - // Streams in each stage of the image as we ask for it - Stream _codec(Asset assetData, CacheManager cache, ImageDecoderCallback decode) async* { - 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) { - throw StateError("Loading thumb for local photo ${assetData.fileName} failed"); - } - - final buffer = await ui.ImmutableBuffer.fromUint8List(thumbnailBytes); - final codec = await decode(buffer); - yield codec; - await cache.putFile(cacheKey, thumbnailBytes); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - if (other is ImmichLocalThumbnailProvider) { - return asset.id == other.asset.id && asset.localId == other.asset.localId; - } - return false; - } - - @override - int get hashCode => Object.hash(asset.id, asset.localId); -} diff --git a/mobile/lib/providers/image/immich_remote_image_provider.dart b/mobile/lib/providers/image/immich_remote_image_provider.dart deleted file mode 100644 index 16d5312e4c..0000000000 --- a/mobile/lib/providers/image/immich_remote_image_provider.dart +++ /dev/null @@ -1,82 +0,0 @@ -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/image_loader.dart'; -import 'package:immich_mobile/providers/image/cache/remote_image_cache_manager.dart'; -import 'package:openapi/api.dart' as api; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/painting.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/utils/image_url_builder.dart'; - -/// The remote image provider for full size remote images -class ImmichRemoteImageProvider extends ImageProvider { - /// The [Asset.remoteId] of the asset to fetch - final String assetId; - - /// The image cache manager - final CacheManager? cacheManager; - - const ImmichRemoteImageProvider({required this.assetId, this.cacheManager}); - - /// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key - /// that describes the precise image to load. - @override - Future obtainKey(ImageConfiguration configuration) { - return SynchronousFuture(this); - } - - @override - ImageStreamCompleter loadImage(ImmichRemoteImageProvider key, ImageDecoderCallback decode) { - final cache = cacheManager ?? RemoteImageCacheManager(); - final chunkEvents = StreamController(); - return MultiImageStreamCompleter( - codec: _codec(key, cache, decode, chunkEvents), - scale: 1.0, - chunkEvents: chunkEvents.stream, - ); - } - - /// Whether to show the original file or load a compressed version - bool get _useOriginal => Store.get(AppSettingsEnum.loadOriginal.storeKey, AppSettingsEnum.loadOriginal.defaultValue); - - // Streams in each stage of the image as we ask for it - Stream _codec( - ImmichRemoteImageProvider key, - CacheManager cache, - ImageDecoderCallback decode, - StreamController chunkEvents, - ) async* { - // Load the higher resolution version of the image - final url = getThumbnailUrlForRemoteId(key.assetId, type: api.AssetMediaSize.preview); - final codec = await ImageLoader.loadImageFromCache(url, cache: cache, decode: decode, chunkEvents: chunkEvents); - yield codec; - - // Load the final remote image - if (_useOriginal) { - // Load the original image - final url = getOriginalUrlForRemoteId(key.assetId); - final codec = await ImageLoader.loadImageFromCache(url, cache: cache, decode: decode, chunkEvents: chunkEvents); - yield codec; - } - await chunkEvents.close(); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - if (other is ImmichRemoteImageProvider) { - return assetId == other.assetId; - } - - return false; - } - - @override - int get hashCode => assetId.hashCode; -} diff --git a/mobile/lib/providers/image/immich_remote_thumbnail_provider.dart b/mobile/lib/providers/image/immich_remote_thumbnail_provider.dart deleted file mode 100644 index 08ee4325e8..0000000000 --- a/mobile/lib/providers/image/immich_remote_thumbnail_provider.dart +++ /dev/null @@ -1,61 +0,0 @@ -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/image_loader.dart'; -import 'package:immich_mobile/providers/image/cache/thumbnail_image_cache_manager.dart'; -import 'package:openapi/api.dart' as api; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/painting.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/utils/image_url_builder.dart'; - -/// The remote image provider -class ImmichRemoteThumbnailProvider extends ImageProvider { - /// The [Asset.remoteId] of the asset to fetch - final String assetId; - - final int? height; - final int? width; - - /// The image cache manager - final CacheManager? cacheManager; - - const ImmichRemoteThumbnailProvider({required this.assetId, this.height, this.width, this.cacheManager}); - - /// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key - /// that describes the precise image to load. - @override - Future obtainKey(ImageConfiguration configuration) { - return SynchronousFuture(this); - } - - @override - ImageStreamCompleter loadImage(ImmichRemoteThumbnailProvider key, ImageDecoderCallback decode) { - final cache = cacheManager ?? ThumbnailImageCacheManager(); - return MultiImageStreamCompleter(codec: _codec(key, cache, decode), scale: 1.0); - } - - // Streams in each stage of the image as we ask for it - Stream _codec(ImmichRemoteThumbnailProvider key, CacheManager cache, ImageDecoderCallback decode) async* { - // Load a preview to the chunk events - final preview = getThumbnailUrlForRemoteId(key.assetId, type: api.AssetMediaSize.thumbnail); - - yield await ImageLoader.loadImageFromCache(preview, cache: cache, decode: decode); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - if (other is ImmichRemoteThumbnailProvider) { - return assetId == other.assetId; - } - - return false; - } - - @override - int get hashCode => assetId.hashCode; -} diff --git a/mobile/lib/utils/cache/custom_image_cache.dart b/mobile/lib/utils/cache/custom_image_cache.dart index a3905baf9b..99ce0db57c 100644 --- a/mobile/lib/utils/cache/custom_image_cache.dart +++ b/mobile/lib/utils/cache/custom_image_cache.dart @@ -2,10 +2,6 @@ import 'package:flutter/painting.dart'; import 'package:immich_mobile/presentation/widgets/images/local_image_provider.dart'; import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/presentation/widgets/images/thumb_hash_provider.dart'; -import 'package:immich_mobile/providers/image/immich_local_image_provider.dart'; -import 'package:immich_mobile/providers/image/immich_local_thumbnail_provider.dart'; -import 'package:immich_mobile/providers/image/immich_remote_image_provider.dart'; -import 'package:immich_mobile/providers/image/immich_remote_thumbnail_provider.dart'; /// [ImageCache] that uses two caches for small and large images /// so that a single large image does not evict all small images @@ -39,14 +35,9 @@ final class CustomImageCache implements ImageCache { } /// Gets the cache for the given key - /// [_large] is used for [ImmichLocalImageProvider] and [ImmichRemoteImageProvider] - /// [_small] is used for [ImmichLocalThumbnailProvider] and [ImmichRemoteThumbnailProvider] ImageCache _cacheForKey(Object key) { return switch (key) { - ImmichLocalImageProvider() || - ImmichRemoteImageProvider() || - LocalFullImageProvider() || - RemoteFullImageProvider() => _large, + LocalFullImageProvider() || RemoteFullImageProvider() => _large, ThumbHashProvider() => _thumbhash, _ => _small, }; diff --git a/mobile/lib/widgets/activities/activity_tile.dart b/mobile/lib/widgets/activities/activity_tile.dart index 76c0b7bf2a..e0eccbff21 100644 --- a/mobile/lib/widgets/activities/activity_tile.dart +++ b/mobile/lib/widgets/activities/activity_tile.dart @@ -4,8 +4,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/datetime_extensions.dart'; import 'package:immich_mobile/models/activities/activity.model.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/providers/activity_service.provider.dart'; -import 'package:immich_mobile/providers/image/immich_remote_thumbnail_provider.dart'; import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; import 'package:immich_mobile/widgets/common/user_circle_avatar.dart'; @@ -102,7 +102,7 @@ class _ActivityAssetThumbnail extends StatelessWidget { decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(4)), image: DecorationImage( - image: ImmichRemoteThumbnailProvider(assetId: assetId), + image: RemoteImageProvider.thumbnail(assetId: assetId, thumbhash: ""), fit: BoxFit.cover, ), ), diff --git a/mobile/lib/widgets/activities/comment_bubble.dart b/mobile/lib/widgets/activities/comment_bubble.dart index 3dd46cd92a..5f060833a7 100644 --- a/mobile/lib/widgets/activities/comment_bubble.dart +++ b/mobile/lib/widgets/activities/comment_bubble.dart @@ -4,9 +4,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/datetime_extensions.dart'; import 'package:immich_mobile/models/activities/activity.model.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/providers/activity.provider.dart'; import 'package:immich_mobile/providers/activity_service.provider.dart'; -import 'package:immich_mobile/providers/image/immich_remote_thumbnail_provider.dart'; import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/widgets/activities/dismissible_activity.dart'; @@ -56,7 +56,7 @@ class CommentBubble extends ConsumerWidget { child: ClipRRect( borderRadius: const BorderRadius.all(Radius.circular(10)), child: Image( - image: ImmichRemoteThumbnailProvider(assetId: activity.assetId!), + image: RemoteImageProvider.thumbnail(assetId: activity.assetId!, thumbhash: ""), fit: BoxFit.cover, ), ), diff --git a/mobile/lib/widgets/album/album_thumbnail_listtile.dart b/mobile/lib/widgets/album/album_thumbnail_listtile.dart index 423410eedf..386084b034 100644 --- a/mobile/lib/widgets/album/album_thumbnail_listtile.dart +++ b/mobile/lib/widgets/album/album_thumbnail_listtile.dart @@ -1,12 +1,12 @@ import 'package:auto_route/auto_route.dart'; -import 'package:cached_network_image/cached_network_image.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; +import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:openapi/api.dart'; @@ -32,15 +32,12 @@ class AlbumThumbnailListTile extends StatelessWidget { } buildAlbumThumbnail() { - return CachedNetworkImage( + return SizedBox( width: cardSize, height: cardSize, - fit: BoxFit.cover, - fadeInDuration: const Duration(milliseconds: 200), - imageUrl: getAlbumThumbnailUrl(album, type: AssetMediaSize.thumbnail), - httpHeaders: ApiService.getRequestHeaders(), - cacheKey: getAlbumThumbNailCacheKey(album, type: AssetMediaSize.thumbnail), - errorWidget: (context, url, error) => const Icon(Icons.image_not_supported_outlined), + child: Thumbnail( + imageProvider: RemoteImageProvider(url: getAlbumThumbnailUrl(album, type: AssetMediaSize.thumbnail)), + ), ); } diff --git a/mobile/lib/widgets/common/immich_image.dart b/mobile/lib/widgets/common/immich_image.dart index c8bc9c1f6a..141a2ac7d4 100644 --- a/mobile/lib/widgets/common/immich_image.dart +++ b/mobile/lib/widgets/common/immich_image.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' as base_asset; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/providers/image/immich_local_image_provider.dart'; -import 'package:immich_mobile/providers/image/immich_remote_image_provider.dart'; +import 'package:immich_mobile/presentation/widgets/images/local_image_provider.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/widgets/asset_grid/thumbnail_placeholder.dart'; import 'package:octo_image/octo_image.dart'; @@ -34,13 +35,21 @@ class ImmichImage extends StatelessWidget { } if (asset == null) { - return ImmichRemoteImageProvider(assetId: assetId!); + return RemoteFullImageProvider(assetId: assetId!, thumbhash: '', assetType: base_asset.AssetType.video); } if (useLocal(asset)) { - return ImmichLocalImageProvider(asset: asset, width: width, height: height); + return LocalFullImageProvider( + id: asset.localId!, + assetType: base_asset.AssetType.video, + size: Size(width, height), + ); } else { - return ImmichRemoteImageProvider(assetId: asset.remoteId!); + return RemoteFullImageProvider( + assetId: asset.remoteId!, + thumbhash: asset.thumbhash ?? '', + assetType: base_asset.AssetType.video, + ); } } diff --git a/mobile/lib/widgets/common/immich_thumbnail.dart b/mobile/lib/widgets/common/immich_thumbnail.dart index 612a6a4bd0..f17353c3aa 100644 --- a/mobile/lib/widgets/common/immich_thumbnail.dart +++ b/mobile/lib/widgets/common/immich_thumbnail.dart @@ -2,15 +2,15 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/providers/image/immich_local_thumbnail_provider.dart'; -import 'package:immich_mobile/providers/image/immich_remote_thumbnail_provider.dart'; +import 'package:immich_mobile/presentation/widgets/images/local_image_provider.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/utils/hooks/blurhash_hook.dart'; import 'package:immich_mobile/utils/thumbnail_utils.dart'; import 'package:immich_mobile/widgets/common/immich_image.dart'; import 'package:immich_mobile/widgets/common/thumbhash_placeholder.dart'; import 'package:octo_image/octo_image.dart'; -import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' as base_asset; class ImmichThumbnail extends HookConsumerWidget { const ImmichThumbnail({this.asset, this.width = 250, this.height = 250, this.fit = BoxFit.cover, super.key}); @@ -24,26 +24,29 @@ class ImmichThumbnail extends HookConsumerWidget { /// either by using the asset ID or the asset itself /// [asset] is the Asset to request, or else use [assetId] to get a remote /// image provider - static ImageProvider imageProvider({Asset? asset, String? assetId, String? userId, int thumbnailSize = 256}) { + static ImageProvider imageProvider({Asset? asset, String? assetId, int thumbnailSize = 256}) { if (asset == null && assetId == null) { throw Exception('Must supply either asset or assetId'); } if (asset == null) { - return ImmichRemoteThumbnailProvider(assetId: assetId!); + return RemoteImageProvider.thumbnail(assetId: assetId!, thumbhash: ""); } if (ImmichImage.useLocal(asset)) { - return ImmichLocalThumbnailProvider(asset: asset, height: thumbnailSize, width: thumbnailSize, userId: userId); + return LocalThumbProvider( + id: asset.localId!, + assetType: base_asset.AssetType.video, + size: Size(thumbnailSize.toDouble(), thumbnailSize.toDouble()), + ); } else { - return ImmichRemoteThumbnailProvider(assetId: asset.remoteId!, height: thumbnailSize, width: thumbnailSize); + return RemoteImageProvider.thumbnail(assetId: asset.remoteId!, thumbhash: asset.thumbhash ?? ""); } } @override Widget build(BuildContext context, WidgetRef ref) { Uint8List? blurhash = useBlurHashRef(asset).value; - final userId = ref.watch(currentUserProvider)?.id; if (asset == null) { return Container( @@ -56,7 +59,7 @@ class ImmichThumbnail extends HookConsumerWidget { final assetAltText = getAltText(asset!.exifInfo, asset!.fileCreatedAt, asset!.type, []); - final thumbnailProviderInstance = ImmichThumbnail.imageProvider(asset: asset, userId: userId); + final thumbnailProviderInstance = ImmichThumbnail.imageProvider(asset: asset); customErrorBuilder(BuildContext ctx, Object error, StackTrace? stackTrace) { thumbnailProviderInstance.evict(); diff --git a/mobile/lib/widgets/common/person_sliver_app_bar.dart b/mobile/lib/widgets/common/person_sliver_app_bar.dart index d5a7ea7cd9..a2a9d1bdbd 100644 --- a/mobile/lib/widgets/common/person_sliver_app_bar.dart +++ b/mobile/lib/widgets/common/person_sliver_app_bar.dart @@ -14,8 +14,8 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; -import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/people.utils.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; @@ -230,10 +230,7 @@ class _ExpandedBackgroundState extends ConsumerState<_ExpandedBackground> with S elevation: 3, child: CircleAvatar( maxRadius: 84 / 2, - backgroundImage: NetworkImage( - getFaceThumbnailUrl(widget.person.id), - headers: ApiService.getRequestHeaders(), - ), + backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(widget.person.id)), ), ), ), diff --git a/mobile/lib/widgets/common/user_avatar.dart b/mobile/lib/widgets/common/user_avatar.dart index ff0e39f371..911d6a9f10 100644 --- a/mobile/lib/widgets/common/user_avatar.dart +++ b/mobile/lib/widgets/common/user_avatar.dart @@ -1,10 +1,9 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; Widget userAvatar(BuildContext context, UserDto u, {double? radius}) { final url = "${Store.get(StoreKey.serverEndpoint)}/users/${u.id}/profile-image"; @@ -12,11 +11,7 @@ Widget userAvatar(BuildContext context, UserDto u, {double? radius}) { return CircleAvatar( radius: radius, backgroundColor: context.primaryColor.withAlpha(50), - foregroundImage: CachedNetworkImageProvider( - url, - headers: ApiService.getRequestHeaders(), - cacheKey: "user-${u.id}-profile", - ), + foregroundImage: RemoteImageProvider(url: url), // silence errors if user has no profile image, use initials as fallback onForegroundImageError: (exception, stackTrace) {}, child: Text(nameFirstLetter.toUpperCase()), diff --git a/mobile/lib/widgets/common/user_circle_avatar.dart b/mobile/lib/widgets/common/user_circle_avatar.dart index b46f560122..0e6d6761e3 100644 --- a/mobile/lib/widgets/common/user_circle_avatar.dart +++ b/mobile/lib/widgets/common/user_circle_avatar.dart @@ -1,13 +1,11 @@ import 'dart:math'; -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/services/api.service.dart'; -import 'package:immich_mobile/widgets/common/transparent_image.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; // ignore: must_be_immutable class UserCircleAvatar extends ConsumerWidget { @@ -46,16 +44,12 @@ class UserCircleAvatar extends ConsumerWidget { child: user.hasProfileImage ? ClipRRect( borderRadius: const BorderRadius.all(Radius.circular(50)), - child: CachedNetworkImage( + child: Image( fit: BoxFit.cover, - cacheKey: '${user.id}-${user.profileChangedAt.toIso8601String()}', width: size, height: size, - placeholder: (_, __) => Image.memory(kTransparentImage), - imageUrl: profileImageUrl, - httpHeaders: ApiService.getRequestHeaders(), - fadeInDuration: const Duration(milliseconds: 300), - errorWidget: (context, error, stackTrace) => textIcon, + image: RemoteImageProvider(url: profileImageUrl), + errorBuilder: (context, error, stackTrace) => textIcon, ), ) : textIcon, diff --git a/mobile/lib/widgets/map/positioned_asset_marker_icon.dart b/mobile/lib/widgets/map/positioned_asset_marker_icon.dart index becef728da..95b127f5b7 100644 --- a/mobile/lib/widgets/map/positioned_asset_marker_icon.dart +++ b/mobile/lib/widgets/map/positioned_asset_marker_icon.dart @@ -1,10 +1,9 @@ import 'dart:io'; import 'dart:math'; -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; class PositionedAssetMarkerIcon extends StatelessWidget { @@ -53,7 +52,6 @@ class _AssetMarkerIcon extends StatelessWidget { @override Widget build(BuildContext context) { final imageUrl = getThumbnailUrlForRemoteId(id); - final cacheKey = getThumbnailCacheKeyForRemoteId(id, thumbhash); return LayoutBuilder( builder: (context, constraints) { return Stack( @@ -79,12 +77,7 @@ class _AssetMarkerIcon extends StatelessWidget { backgroundColor: context.colorScheme.onSurface, child: CircleAvatar( radius: constraints.maxHeight * 0.37, - backgroundImage: CachedNetworkImageProvider( - imageUrl, - cacheKey: cacheKey, - headers: ApiService.getRequestHeaders(), - errorListener: (_) => const Icon(Icons.image_not_supported_outlined), - ), + backgroundImage: RemoteImageProvider(url: imageUrl), ), ), ), diff --git a/mobile/lib/widgets/search/curated_people_row.dart b/mobile/lib/widgets/search/curated_people_row.dart index 74fc3e1c34..9155de2131 100644 --- a/mobile/lib/widgets/search/curated_people_row.dart +++ b/mobile/lib/widgets/search/curated_people_row.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/search/search_curated_content.model.dart'; -import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; class CuratedPeopleRow extends StatelessWidget { @@ -29,7 +29,6 @@ class CuratedPeopleRow extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: List.generate(content.length, (index) { final person = content[index]; - final headers = ApiService.getRequestHeaders(); return Padding( padding: const EdgeInsets.only(right: 16.0), child: Column( @@ -44,7 +43,7 @@ class CuratedPeopleRow extends StatelessWidget { elevation: 3, child: CircleAvatar( maxRadius: imageSize / 2, - backgroundImage: NetworkImage(getFaceThumbnailUrl(person.id), headers: headers), + backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(person.id)), ), ), ), diff --git a/mobile/lib/widgets/search/search_filter/people_picker.dart b/mobile/lib/widgets/search/search_filter/people_picker.dart index b2a7a18c7c..978b70239c 100644 --- a/mobile/lib/widgets/search/search_filter/people_picker.dart +++ b/mobile/lib/widgets/search/search_filter/people_picker.dart @@ -6,8 +6,8 @@ import 'package:immich_mobile/domain/models/person.model.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/pages/common/large_leading_tile.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/providers/search/people.provider.dart'; -import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/widgets/common/search_field.dart'; @@ -23,7 +23,6 @@ class PeoplePicker extends HookConsumerWidget { final imageSize = 60.0; final searchQuery = useState(''); final people = ref.watch(getAllPeopleProvider); - final headers = ApiService.getRequestHeaders(); final selectedPeople = useState>(filter ?? {}); return Column( @@ -75,7 +74,7 @@ class PeoplePicker extends HookConsumerWidget { elevation: 3, child: CircleAvatar( maxRadius: imageSize / 2, - backgroundImage: NetworkImage(getFaceThumbnailUrl(person.id), headers: headers), + backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(person.id)), ), ), ), diff --git a/mobile/lib/widgets/search/thumbnail_with_info.dart b/mobile/lib/widgets/search/thumbnail_with_info.dart index af9460f929..7ba8257c8a 100644 --- a/mobile/lib/widgets/search/thumbnail_with_info.dart +++ b/mobile/lib/widgets/search/thumbnail_with_info.dart @@ -1,8 +1,8 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; +import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/widgets/search/thumbnail_with_info_container.dart'; -import 'package:immich_mobile/services/api.service.dart'; class ThumbnailWithInfo extends StatelessWidget { const ThumbnailWithInfo({ @@ -30,14 +30,7 @@ class ThumbnailWithInfo extends StatelessWidget { child: imageUrl != null ? ClipRRect( borderRadius: BorderRadius.circular(borderRadius), - child: CachedNetworkImage( - width: double.infinity, - height: double.infinity, - fit: BoxFit.cover, - imageUrl: imageUrl!, - httpHeaders: ApiService.getRequestHeaders(), - errorWidget: (context, url, error) => const Icon(Icons.image_not_supported_outlined), - ), + child: Thumbnail(imageProvider: RemoteImageProvider(url: imageUrl!)), ) : Center(child: Icon(noImageIcon ?? Icons.not_listed_location, color: textAndIconColor)), ); diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index d237c02023..05ecb020ba 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -201,30 +201,6 @@ packages: url: "https://pub.dev" source: hosted version: "8.9.5" - cached_network_image: - dependency: "direct main" - description: - name: cached_network_image - sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" - url: "https://pub.dev" - source: hosted - version: "3.4.1" - cached_network_image_platform_interface: - dependency: transitive - description: - name: cached_network_image_platform_interface - sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" - url: "https://pub.dev" - source: hosted - version: "4.1.1" - cached_network_image_web: - dependency: transitive - description: - name: cached_network_image_web - sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" - url: "https://pub.dev" - source: hosted - version: "1.3.1" cancellation_token: dependency: transitive description: @@ -1249,10 +1225,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -1942,10 +1918,10 @@ packages: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.7" thumbhash: dependency: "direct main" description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 198d3ad8f7..9f208654c5 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -12,7 +12,6 @@ dependencies: async: ^2.13.0 auto_route: ^9.2.0 background_downloader: ^9.3.0 - cached_network_image: ^3.4.1 cancellation_token_http: ^2.1.0 cast: ^2.1.0 collection: ^1.19.1