diff --git a/mobile/lib/presentation/widgets/images/thumb_hash_provider.dart b/mobile/lib/presentation/widgets/images/thumb_hash_provider.dart deleted file mode 100644 index cd286a4cdf..0000000000 --- a/mobile/lib/presentation/widgets/images/thumb_hash_provider.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'dart:convert' hide Codec; -import 'dart:ui'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/rendering.dart'; -import 'package:thumbhash/thumbhash.dart'; - -class ThumbHashProvider extends ImageProvider { - final String thumbHash; - - const ThumbHashProvider({ - required this.thumbHash, - }); - - @override - Future obtainKey(ImageConfiguration configuration) { - return SynchronousFuture(this); - } - - @override - ImageStreamCompleter loadImage( - ThumbHashProvider key, - ImageDecoderCallback decode, - ) { - return MultiFrameImageStreamCompleter( - codec: _loadCodec(key, decode), - scale: 1.0, - ); - } - - Future _loadCodec( - ThumbHashProvider key, - ImageDecoderCallback decode, - ) async { - final image = thumbHashToRGBA(base64Decode(key.thumbHash)); - return decode(await ImmutableBuffer.fromUint8List(rgbaToBmp(image))); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - if (other is ThumbHashProvider) { - return thumbHash == other.thumbHash; - } - return false; - } - - @override - int get hashCode => thumbHash.hashCode; -} diff --git a/mobile/lib/presentation/widgets/images/thumbnail.widget.dart b/mobile/lib/presentation/widgets/images/thumbnail.widget.dart index f54c32dac1..b6d98185a3 100644 --- a/mobile/lib/presentation/widgets/images/thumbnail.widget.dart +++ b/mobile/lib/presentation/widgets/images/thumbnail.widget.dart @@ -1,9 +1,8 @@ import 'package:flutter/material.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; -import 'package:immich_mobile/presentation/widgets/images/thumb_hash_provider.dart'; import 'package:immich_mobile/widgets/asset_grid/thumbnail_placeholder.dart'; -import 'package:immich_mobile/widgets/common/fade_in_placeholder_image.dart'; +import 'package:immich_mobile/widgets/common/thumbhash.dart'; import 'package:logging/logging.dart'; import 'package:octo_image/octo_image.dart'; @@ -58,11 +57,7 @@ OctoPlaceholderBuilder _blurHashPlaceholderBuilder( }) { return (context) => thumbHash == null ? const ThumbnailPlaceholder() - : FadeInPlaceholderImage( - placeholder: const ThumbnailPlaceholder(), - image: ThumbHashProvider(thumbHash: thumbHash), - fit: fit ?? BoxFit.cover, - ); + : Thumbhash(blurhash: thumbHash, fit: fit ?? BoxFit.cover); } OctoErrorBuilder _blurHashErrorBuilder( diff --git a/mobile/lib/presentation/widgets/memory/memory_card.widget.dart b/mobile/lib/presentation/widgets/memory/memory_card.widget.dart index 8268196089..cb59edf165 100644 --- a/mobile/lib/presentation/widgets/memory/memory_card.widget.dart +++ b/mobile/lib/presentation/widgets/memory/memory_card.widget.dart @@ -6,7 +6,7 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/presentation/widgets/images/full_image.widget.dart'; import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; -import 'package:immich_mobile/utils/hooks/blurhash_hook.dart'; +import 'package:immich_mobile/widgets/common/thumbhash.dart'; class DriftMemoryCard extends StatelessWidget { final RemoteAsset asset; @@ -117,43 +117,34 @@ class _BlurredBackdrop extends HookWidget { @override Widget build(BuildContext context) { - final blurhash = useDriftBlurHashRef(asset).value; + final blurhash = asset.thumbHash; if (blurhash != null) { // Use a nice cheap blur hash image decoration - return Container( + return Stack( + children: [ + const ColoredBox(color: Color.fromRGBO(0, 0, 0, 0.2)), + Thumbhash(blurhash: blurhash, fit: BoxFit.cover), + ], + ); + } + + // Fall back to using a more expensive image filtered + // Since the ImmichImage is already precached, we can + // safely use that as the image provider + return ImageFiltered( + imageFilter: ImageFilter.blur(sigmaX: 30, sigmaY: 30), + child: DecoratedBox( decoration: BoxDecoration( image: DecorationImage( - image: MemoryImage( - blurhash, + image: getFullImageProvider( + asset, + size: Size(context.width, context.height), ), fit: BoxFit.cover, ), ), - child: Container( - color: Colors.black.withValues(alpha: 0.2), - ), - ); - } else { - // Fall back to using a more expensive image filtered - // Since the ImmichImage is already precached, we can - // safely use that as the image provider - return ImageFiltered( - imageFilter: ImageFilter.blur(sigmaX: 30, sigmaY: 30), - child: Container( - decoration: BoxDecoration( - image: DecorationImage( - image: getFullImageProvider( - asset, - size: Size(context.width, context.height), - ), - fit: BoxFit.cover, - ), - ), - child: Container( - color: Colors.black.withValues(alpha: 0.2), - ), - ), - ); - } + child: const ColoredBox(color: Color.fromRGBO(0, 0, 0, 0.2)), + ), + ); } } diff --git a/mobile/lib/utils/hooks/blurhash_hook.dart b/mobile/lib/utils/hooks/blurhash_hook.dart deleted file mode 100644 index 62208c4cf5..0000000000 --- a/mobile/lib/utils/hooks/blurhash_hook.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'dart:convert'; -import 'dart:typed_data'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:thumbhash/thumbhash.dart' as thumbhash; - -ObjectRef useBlurHashRef(Asset? asset) { - if (asset?.thumbhash == null) { - return useRef(null); - } - - final rbga = thumbhash.thumbHashToRGBA( - base64Decode(asset!.thumbhash!), - ); - - return useRef(thumbhash.rgbaToBmp(rbga)); -} - -ObjectRef useDriftBlurHashRef(RemoteAsset? asset) { - if (asset?.thumbHash == null) { - return useRef(null); - } - - final rbga = thumbhash.thumbHashToRGBA( - base64Decode(asset!.thumbHash!), - ); - - return useRef(thumbhash.rgbaToBmp(rbga)); -} diff --git a/mobile/lib/widgets/common/immich_thumbnail.dart b/mobile/lib/widgets/common/immich_thumbnail.dart index 0918348f4c..9ed0d92d39 100644 --- a/mobile/lib/widgets/common/immich_thumbnail.dart +++ b/mobile/lib/widgets/common/immich_thumbnail.dart @@ -1,11 +1,8 @@ -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/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'; @@ -64,7 +61,6 @@ class ImmichThumbnail extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - Uint8List? blurhash = useBlurHashRef(asset).value; final userId = ref.watch(currentUserProvider)?.id; if (asset == null) { @@ -82,7 +78,7 @@ class ImmichThumbnail extends HookConsumerWidget { asset!.exifInfo, asset!.fileCreatedAt, asset!.type, - [], + const [], ); final thumbnailProviderInstance = ImmichThumbnail.imageProvider( @@ -94,7 +90,7 @@ class ImmichThumbnail extends HookConsumerWidget { thumbnailProviderInstance.evict(); final originalErrorWidgetBuilder = - blurHashErrorBuilder(blurhash, fit: fit); + blurHashErrorBuilder(asset?.thumbhash, fit: fit); return originalErrorWidgetBuilder(ctx, error, stackTrace); } @@ -105,7 +101,8 @@ class ImmichThumbnail extends HookConsumerWidget { fadeInDuration: Duration.zero, fadeOutDuration: const Duration(milliseconds: 100), octoSet: OctoSet( - placeholderBuilder: blurHashPlaceholderBuilder(blurhash, fit: fit), + placeholderBuilder: + blurHashPlaceholderBuilder(asset?.thumbhash, fit: fit), errorBuilder: customErrorBuilder, ), image: thumbnailProviderInstance, diff --git a/mobile/lib/widgets/common/thumbhash.dart b/mobile/lib/widgets/common/thumbhash.dart new file mode 100644 index 0000000000..81ba62db07 --- /dev/null +++ b/mobile/lib/widgets/common/thumbhash.dart @@ -0,0 +1,23 @@ +import 'dart:convert'; + +import 'package:flutter/widgets.dart'; +import 'package:thumbhash/thumbhash.dart' as thumbhash; + +class Thumbhash extends StatelessWidget { + final String blurhash; + final BoxFit fit; + + const Thumbhash({ + required this.blurhash, + this.fit = BoxFit.cover, + super.key, + }); + + @override + Widget build(BuildContext context) { + return Image.memory( + thumbhash.rgbaToBmp(thumbhash.thumbHashToRGBA(base64.decode(blurhash))), + fit: fit, + ); + } +} diff --git a/mobile/lib/widgets/common/thumbhash_placeholder.dart b/mobile/lib/widgets/common/thumbhash_placeholder.dart index aa320f4230..384f68e252 100644 --- a/mobile/lib/widgets/common/thumbhash_placeholder.dart +++ b/mobile/lib/widgets/common/thumbhash_placeholder.dart @@ -1,13 +1,12 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:immich_mobile/widgets/asset_grid/thumbnail_placeholder.dart'; -import 'package:immich_mobile/widgets/common/fade_in_placeholder_image.dart'; +import 'package:immich_mobile/widgets/common/thumbhash.dart'; import 'package:octo_image/octo_image.dart'; /// Simple set to show [OctoPlaceholder.circularProgressIndicator] as /// placeholder and [OctoError.icon] as error. OctoSet blurHashOrPlaceholder( - Uint8List? blurhash, { + String? blurhash, { BoxFit? fit, Text? errorMessage, }) { @@ -19,20 +18,19 @@ OctoSet blurHashOrPlaceholder( } OctoPlaceholderBuilder blurHashPlaceholderBuilder( - Uint8List? blurhash, { + String? blurhash, { BoxFit? fit, }) { return (context) => blurhash == null ? const ThumbnailPlaceholder() - : FadeInPlaceholderImage( - placeholder: const ThumbnailPlaceholder(), - image: MemoryImage(blurhash), + : Thumbhash( + blurhash: blurhash, fit: fit ?? BoxFit.cover, ); } OctoErrorBuilder blurHashErrorBuilder( - Uint8List? blurhash, { + String? blurhash, { BoxFit? fit, Text? message, IconData? icon, diff --git a/mobile/lib/widgets/memories/memory_card.dart b/mobile/lib/widgets/memories/memory_card.dart index 31f4d5ed94..57ec724e46 100644 --- a/mobile/lib/widgets/memories/memory_card.dart +++ b/mobile/lib/widgets/memories/memory_card.dart @@ -5,8 +5,8 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/pages/common/native_video_viewer.page.dart'; -import 'package:immich_mobile/utils/hooks/blurhash_hook.dart'; import 'package:immich_mobile/widgets/common/immich_image.dart'; +import 'package:immich_mobile/widgets/common/thumbhash.dart'; class MemoryCard extends StatelessWidget { final Asset asset; @@ -113,44 +113,35 @@ class _BlurredBackdrop extends HookWidget { @override Widget build(BuildContext context) { - final blurhash = useBlurHashRef(asset).value; + final blurhash = asset.thumbhash; if (blurhash != null) { // Use a nice cheap blur hash image decoration - return Container( + return Stack( + children: [ + const ColoredBox(color: Color.fromRGBO(0, 0, 0, 0.2)), + Thumbhash(blurhash: blurhash, fit: BoxFit.cover), + ], + ); + } + + // Fall back to using a more expensive image filtered + // Since the ImmichImage is already precached, we can + // safely use that as the image provider + return ImageFiltered( + imageFilter: ImageFilter.blur(sigmaX: 30, sigmaY: 30), + child: DecoratedBox( decoration: BoxDecoration( image: DecorationImage( - image: MemoryImage( - blurhash, + image: ImmichImage.imageProvider( + asset: asset, + height: context.height, + width: context.width, ), fit: BoxFit.cover, ), ), - child: Container( - color: Colors.black.withValues(alpha: 0.2), - ), - ); - } else { - // Fall back to using a more expensive image filtered - // Since the ImmichImage is already precached, we can - // safely use that as the image provider - return ImageFiltered( - imageFilter: ImageFilter.blur(sigmaX: 30, sigmaY: 30), - child: Container( - decoration: BoxDecoration( - image: DecorationImage( - image: ImmichImage.imageProvider( - asset: asset, - height: context.height, - width: context.width, - ), - fit: BoxFit.cover, - ), - ), - child: Container( - color: Colors.black.withValues(alpha: 0.2), - ), - ), - ); - } + child: const ColoredBox(color: Color.fromRGBO(0, 0, 0, 0.2)), + ), + ); } }