Skip to content

Commit

Permalink
Refactor to use ImmichThumbnail and local thumbnail image provider
Browse files Browse the repository at this point in the history
format
  • Loading branch information
martyfuhry committed Feb 21, 2024
1 parent 7382591 commit 718c258
Show file tree
Hide file tree
Showing 13 changed files with 278 additions and 104 deletions.
3 changes: 2 additions & 1 deletion mobile/lib/modules/album/ui/album_thumbnail_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/ui/immich_image.dart';
import 'package:immich_mobile/shared/ui/immich_thumbnail.dart';

class AlbumThumbnailCard extends StatelessWidget {
final Function()? onTap;
Expand Down Expand Up @@ -45,7 +46,7 @@ class AlbumThumbnailCard extends StatelessWidget {
);
}

buildAlbumThumbnail() => ImmichImage.thumbnail(
buildAlbumThumbnail() => ImmichThumbnail(asset:
album.thumbnail.value,
width: cardSize,
height: cardSize,
Expand Down
6 changes: 3 additions & 3 deletions mobile/lib/modules/album/ui/shared_album_thumbnail_image.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/ui/immich_image.dart';
import 'package:immich_mobile/shared/ui/immich_thumbnail.dart';

class SharedAlbumThumbnailImage extends HookConsumerWidget {
final Asset asset;
Expand All @@ -16,8 +16,8 @@ class SharedAlbumThumbnailImage extends HookConsumerWidget {
},
child: Stack(
children: [
ImmichImage.thumbnail(
asset,
ImmichThumbnail(
asset: asset,
width: 500,
height: 500,
),
Expand Down
5 changes: 3 additions & 2 deletions mobile/lib/modules/album/views/sharing_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/providers/user.provider.dart';
import 'package:immich_mobile/shared/ui/immich_app_bar.dart';
import 'package:immich_mobile/shared/ui/immich_image.dart';
import 'package:immich_mobile/shared/ui/immich_thumbnail.dart';

@RoutePage()
class SharingPage extends HookConsumerWidget {
Expand Down Expand Up @@ -72,8 +73,8 @@ class SharingPage extends HookConsumerWidget {
contentPadding: const EdgeInsets.symmetric(horizontal: 12),
leading: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: ImmichImage.thumbnail(
album.thumbnail.value,
child: ImmichThumbnail(
asset: album.thumbnail.value,
width: 60,
height: 60,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import 'package:photo_manager/photo_manager.dart';

/// The local image provider for an asset
/// Only viable
class ImmichLocalImageProvider extends ImageProvider<Asset> {
class ImmichLocalImageProvider extends ImageProvider<ImmichLocalImageProvider> {
final Asset asset;

ImmichLocalImageProvider({
Expand All @@ -21,15 +21,18 @@ class ImmichLocalImageProvider extends ImageProvider<Asset> {
/// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key
/// that describes the precise image to load.
@override
Future<Asset> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture(asset);
Future<ImmichLocalImageProvider> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture(this);
}

@override
ImageStreamCompleter loadImage(Asset key, ImageDecoderCallback decode) {
ImageStreamCompleter loadImage(
ImmichLocalImageProvider key,
ImageDecoderCallback decode,
) {
final chunkEvents = StreamController<ImageChunkEvent>();
return MultiImageStreamCompleter(
codec: _codec(key, decode, chunkEvents),
codec: _codec(key.asset, decode, chunkEvents),
scale: 1.0,
chunkEvents: chunkEvents.stream,
informationCollector: () sync* {
Expand Down Expand Up @@ -82,11 +85,6 @@ class ImmichLocalImageProvider extends ImageProvider<Asset> {
yield codec;
} catch (error) {
throw StateError("Loading asset ${asset.fileName} failed");
} finally {
if (Platform.isIOS) {
// Clean up this file
await file.delete();
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import 'dart:async';
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/shared/models/asset.dart';
import 'package:photo_manager/photo_manager.dart';

/// The local image provider for an asset
/// Only viable
class ImmichLocalThumbnailProvider extends ImageProvider<Asset> {
final Asset asset;
final int height;
final int width;

ImmichLocalThumbnailProvider({
required this.asset,
this.height = 256,
this.width = 256,
}) : 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<Asset> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture(asset);
}

@override
ImageStreamCompleter loadImage(Asset key, ImageDecoderCallback decode) {
final chunkEvents = StreamController<ImageChunkEvent>();
return MultiImageStreamCompleter(
codec: _codec(key, 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<ui.Codec> _codec(
Asset key,
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 normalThumbBytes = await asset.local
?.thumbnailDataWithSize(ThumbnailSize(width, height));
if (normalThumbBytes == null) {
throw StateError(
"Loading thumb for local photo ${asset.fileName} failed",
);
}
final buffer = await ui.ImmutableBuffer.fromUint8List(normalThumbBytes);
final codec = await decode(buffer);
yield codec;

chunkEvents.close();
}

@override
bool operator ==(Object other) {
if (other is! ImmichLocalThumbnailProvider) return false;
if (identical(this, other)) return true;
return asset == other.asset;
}

@override
int get hashCode => asset.hashCode;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import 'package:immich_mobile/utils/image_url_builder.dart';
final _httpClient = HttpClient()..autoUncompress = false;

/// The remote image provider
class ImmichRemoteImageProvider extends ImageProvider<String> {
class ImmichRemoteImageProvider
extends ImageProvider<ImmichRemoteImageProvider> {
/// The [Asset.remoteId] of the asset to fetch
final String assetId;

Expand All @@ -32,16 +33,20 @@ class ImmichRemoteImageProvider extends ImageProvider<String> {
/// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key
/// that describes the precise image to load.
@override
Future<String> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture('$assetId,$isThumbnail');
Future<ImmichRemoteImageProvider> obtainKey(
ImageConfiguration configuration,
) {
return SynchronousFuture(this);
}

@override
ImageStreamCompleter loadImage(String key, ImageDecoderCallback decode) {
final id = key.split(',').first;
ImageStreamCompleter loadImage(
ImmichRemoteImageProvider key,
ImageDecoderCallback decode,
) {
final chunkEvents = StreamController<ImageChunkEvent>();
return MultiImageStreamCompleter(
codec: _codec(id, decode, chunkEvents),
codec: _codec(key, decode, chunkEvents),
scale: 1.0,
chunkEvents: chunkEvents.stream,
);
Expand All @@ -61,14 +66,14 @@ class ImmichRemoteImageProvider extends ImageProvider<String> {

// Streams in each stage of the image as we ask for it
Stream<ui.Codec> _codec(
String key,
ImmichRemoteImageProvider key,
ImageDecoderCallback decode,
StreamController<ImageChunkEvent> chunkEvents,
) async* {
// Load a preview to the chunk events
if (_loadPreview || isThumbnail) {
if (_loadPreview || key.isThumbnail) {
final preview = getThumbnailUrlForRemoteId(
assetId,
key.assetId,
type: api.ThumbnailFormat.WEBP,
);

Expand All @@ -80,14 +85,14 @@ class ImmichRemoteImageProvider extends ImageProvider<String> {
}

// Guard thumnbail rendering
if (isThumbnail) {
if (key.isThumbnail) {
await chunkEvents.close();
return;
}

// Load the higher resolution version of the image
final url = getThumbnailUrlForRemoteId(
assetId,
key.assetId,
type: api.ThumbnailFormat.JPEG,
);
final codec = await _loadFromUri(Uri.parse(url), decode, chunkEvents);
Expand All @@ -96,7 +101,7 @@ class ImmichRemoteImageProvider extends ImageProvider<String> {
// Load the final remote image
if (_useOriginal) {
// Load the original image
final url = getImageUrlFromId(assetId);
final url = getImageUrlFromId(key.assetId);
final codec = await _loadFromUri(Uri.parse(url), decode, chunkEvents);
yield codec;
}
Expand Down Expand Up @@ -137,7 +142,7 @@ class ImmichRemoteImageProvider extends ImageProvider<String> {
bool operator ==(Object other) {
if (other is! ImmichRemoteImageProvider) return false;
if (identical(this, other)) return true;
return assetId == other.assetId;
return assetId == other.assetId && isThumbnail == other.isThumbnail;
}

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/utils/image_url_builder.dart';

/// The remote image provider
class ImmichRemoteThumbnailProvider extends ImageProvider<String> {
class ImmichRemoteThumbnailProvider extends ImageProvider<ImmichRemoteThumbnailProvider> {
/// The [Asset.remoteId] of the asset to fetch
final String assetId;

Expand All @@ -27,12 +27,12 @@ class ImmichRemoteThumbnailProvider extends ImageProvider<String> {
/// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key
/// that describes the precise image to load.
@override
Future<String> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture(assetId);
Future<ImmichRemoteThumbnailProvider> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture(this);
}

@override
ImageStreamCompleter loadImage(String key, ImageDecoderCallback decode) {
ImageStreamCompleter loadImage(ImmichRemoteThumbnailProvider key, ImageDecoderCallback decode) {
final chunkEvents = StreamController<ImageChunkEvent>();
return MultiImageStreamCompleter(
codec: _codec(key, decode, chunkEvents),
Expand All @@ -43,13 +43,13 @@ class ImmichRemoteThumbnailProvider extends ImageProvider<String> {

// Streams in each stage of the image as we ask for it
Stream<ui.Codec> _codec(
String key,
ImmichRemoteThumbnailProvider key,
ImageDecoderCallback decode,
StreamController<ImageChunkEvent> chunkEvents,
) async* {
// Load a preview to the chunk events
final preview = getThumbnailUrlForRemoteId(
assetId,
key.assetId,
type: api.ThumbnailFormat.WEBP,
);

Expand Down
26 changes: 14 additions & 12 deletions mobile/lib/modules/asset_viewer/views/gallery_viewer.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import 'dart:io';
import 'dart:math';
import 'dart:ui' as ui;
import 'package:easy_localization/easy_localization.dart';
import 'package:auto_route/auto_route.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/album/providers/current_album.provider.dart';
import 'package:immich_mobile/modules/asset_viewer/image_providers/immich_remote_image_provider.dart';
import 'package:immich_mobile/modules/asset_viewer/providers/asset_stack.provider.dart';
import 'package:immich_mobile/modules/asset_viewer/providers/current_asset.provider.dart';
import 'package:immich_mobile/modules/asset_viewer/providers/show_controls.provider.dart';
Expand All @@ -33,6 +34,7 @@ import 'package:immich_mobile/modules/settings/services/app_settings.service.dar
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
import 'package:immich_mobile/shared/providers/user.provider.dart';
import 'package:immich_mobile/shared/ui/immich_image.dart';
import 'package:immich_mobile/shared/ui/immich_thumbnail.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart';
import 'package:immich_mobile/shared/ui/photo_view/photo_view_gallery.dart';
import 'package:immich_mobile/shared/ui/photo_view/src/photo_view_computed_scale.dart';
Expand Down Expand Up @@ -481,15 +483,9 @@ class GalleryViewerPage extends HookConsumerWidget {
),
child: ClipRRect(
borderRadius: BorderRadius.circular(4),
child: CachedNetworkImage(
child: Image(
fit: BoxFit.cover,
imageUrl:
'${Store.get(StoreKey.serverEndpoint)}/asset/thumbnail/$assetId',
httpHeaders: {
"x-immich-user-token": Store.get(StoreKey.accessToken),
},
errorWidget: (context, url, error) =>
const Icon(Icons.image_not_supported_outlined),
image: ImmichRemoteImageProvider(assetId: assetId!),
),
),
),
Expand Down Expand Up @@ -728,9 +724,15 @@ class GalleryViewerPage extends HookConsumerWidget {
isZoomed.value = state != PhotoViewScaleState.initial;
ref.read(showControlsProvider.notifier).show = !isZoomed.value;
},
loadingBuilder: (context, event, index) => ImmichImage.thumbnail(
asset(),
fit: BoxFit.contain,
loadingBuilder: (context, event, index) => BackdropFilter(
filter: ui.ImageFilter.blur(
sigmaX: 0.2,
sigmaY: 0.2,
),
child: ImmichThumbnail(
asset: asset(),
fit: BoxFit.contain,
),
),
pageController: controller,
scrollPhysics: isZoomed.value
Expand Down
Loading

0 comments on commit 718c258

Please sign in to comment.