From 45d4984fde5d0f75afd1a4e08026569185c396ad Mon Sep 17 00:00:00 2001 From: Marty Fuhry Date: Tue, 27 Feb 2024 09:34:44 -0500 Subject: [PATCH] blurhash fade to thumb --- .../home/ui/asset_grid/blurhash_thumb.dart | 60 +++++++++++++++++++ .../ui/asset_grid/immich_asset_grid_view.dart | 53 +++++++++------- .../lib/shared/ui/thumbhash_placeholder.dart | 3 +- mobile/pubspec.lock | 56 +++++------------ 4 files changed, 109 insertions(+), 63 deletions(-) create mode 100644 mobile/lib/modules/home/ui/asset_grid/blurhash_thumb.dart diff --git a/mobile/lib/modules/home/ui/asset_grid/blurhash_thumb.dart b/mobile/lib/modules/home/ui/asset_grid/blurhash_thumb.dart new file mode 100644 index 0000000000..1a8c842cfc --- /dev/null +++ b/mobile/lib/modules/home/ui/asset_grid/blurhash_thumb.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:immich_mobile/shared/models/asset.dart'; +import 'package:immich_mobile/shared/ui/hooks/blurhash_hook.dart'; + +class BlurhashThumb extends HookWidget { + final double height; + final double width; + final Asset asset; + final EdgeInsets margin; + + const BlurhashThumb({ + super.key, + required this.height, + required this.width, + required this.asset, + required this.margin, + }); + + @override + Widget build(BuildContext context) { + final blurhash = useBlurHashRef(asset).value; + if (blurhash == null) { + return SizedBox( + height: height, + width: width, + ); + } + return Padding( + padding: margin, + child: Image.memory( + blurhash, + gaplessPlayback: true, + frameBuilder: ( + BuildContext context, + Widget child, + int? frame, + bool wasSynchronouslyLoaded, + ) { + if (wasSynchronouslyLoaded) { + return child; + } + + return AnimatedSwitcher( + duration: const Duration(milliseconds: 100), + child: frame != null + ? child + : SizedBox( + height: height, + width: width, + ), + ); + }, + fit: BoxFit.cover, + height: height, + width: width, + ), + ); + } +} diff --git a/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart b/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart index 8a63108167..5c1487978e 100644 --- a/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart +++ b/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart @@ -1,4 +1,5 @@ import 'dart:collection'; +import 'dart:convert'; import 'dart:developer'; import 'dart:math'; @@ -6,12 +7,15 @@ import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/collection_extensions.dart'; import 'package:immich_mobile/modules/asset_viewer/providers/scroll_notifier.provider.dart'; +import 'package:immich_mobile/modules/home/ui/asset_grid/blurhash_thumb.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_image.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_placeholder.dart'; import 'package:immich_mobile/shared/models/asset.dart'; +import 'package:immich_mobile/shared/ui/hooks/blurhash_hook.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'asset_grid_data_structure.dart'; @@ -325,32 +329,33 @@ class ImmichAssetGridViewState extends State { } /// A single row of all placeholder widgets -class _PlaceholderRow extends StatelessWidget { - final int number; +class _PlaceholderRow extends HookWidget { final double width; final double height; final double margin; + final List assets; const _PlaceholderRow({ super.key, - required this.number, required this.width, required this.height, required this.margin, + required this.assets, }); @override Widget build(BuildContext context) { return Row( children: [ - for (int i = 0; i < number; i++) - ThumbnailPlaceholder( + for (int i = 0; i < assets.length; i++) + BlurhashThumb( key: ValueKey(i), + asset: assets[i], width: width, height: height, margin: EdgeInsets.only( bottom: margin, - right: i + 1 == number ? 0.0 : margin, + right: i + 1 == assets.length ? 0.0 : margin, ), ), ], @@ -401,9 +406,9 @@ class _Section extends StatelessWidget { final width = constraints.maxWidth / assetsPerRow - margin * (assetsPerRow - 1) / assetsPerRow; final rows = (section.count + assetsPerRow - 1) ~/ assetsPerRow; - final List assetsToRender = scrolling - ? [] - : renderList.loadAssets(section.offset, section.count); + final List assetsToRender = //scrolling + //? [] + renderList.loadAssets(section.offset, section.count); return Column( key: ValueKey(section.offset), crossAxisAlignment: CrossAxisAlignment.start, @@ -422,18 +427,22 @@ class _Section extends StatelessWidget { selectAssets: selectAssets, deselectAssets: deselectAssets, ), - for (int i = 0; i < rows; i++) - scrolling - ? _PlaceholderRow( - key: ValueKey(i), - number: i + 1 == rows - ? section.count - i * assetsPerRow - : assetsPerRow, - width: width, - height: width, - margin: margin, - ) - : _AssetRow( + Stack( + children: [ + for (int i = 0; i < rows; i++) + _PlaceholderRow( + key: ValueKey('placeholder-$i'), + assets: assetsToRender.nestedSlice( + i * assetsPerRow, + min((i + 1) * assetsPerRow, section.count), + ), + width: width, + height: width, + margin: margin, + ), + if (!scrolling) + for (int i = 0; i < rows; i++) + _AssetRow( key: ValueKey(i), assets: assetsToRender.nestedSlice( i * assetsPerRow, @@ -454,6 +463,8 @@ class _Section extends StatelessWidget { onSelect: (asset) => selectAssets([asset]), onDeselect: (asset) => deselectAssets([asset]), ), + ], + ), ], ); }, diff --git a/mobile/lib/shared/ui/thumbhash_placeholder.dart b/mobile/lib/shared/ui/thumbhash_placeholder.dart index 0ec64d3760..19c52c17d9 100644 --- a/mobile/lib/shared/ui/thumbhash_placeholder.dart +++ b/mobile/lib/shared/ui/thumbhash_placeholder.dart @@ -23,8 +23,7 @@ OctoPlaceholderBuilder blurHashPlaceholderBuilder( }) { return (context) => blurhash == null ? const ThumbnailPlaceholder() - : FadeInPlaceholderImage( - placeholder: const ThumbnailPlaceholder(), + : Image( image: MemoryImage(blurhash), fit: fit ?? BoxFit.cover, ); diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 9e379d4653..f27351898d 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -413,10 +413,10 @@ packages: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "6.1.4" file_selector_linux: dependency: transitive description: @@ -860,30 +860,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.8.1" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" - url: "https://pub.dev" - source: hosted - version: "10.0.0" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 - url: "https://pub.dev" - source: hosted - version: "2.0.1" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 - url: "https://pub.dev" - source: hosted - version: "2.0.1" lints: dependency: transitive description: @@ -931,18 +907,18 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.5.0" meta: dependency: "direct overridden" description: @@ -1026,10 +1002,10 @@ packages: dependency: "direct main" description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.8.3" path_provider: dependency: "direct main" description: @@ -1162,10 +1138,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.2" plugin_platform_interface: dependency: transitive description: @@ -1194,10 +1170,10 @@ packages: dependency: transitive description: name: process - sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "4.2.4" provider: dependency: transitive description: @@ -1663,10 +1639,10 @@ packages: dependency: transitive description: name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "11.10.0" wakelock_plus: dependency: "direct main" description: @@ -1711,10 +1687,10 @@ packages: dependency: transitive description: name: webdriver - sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" + sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.2" win32: dependency: transitive description: