mirror of
https://github.com/immich-app/immich.git
synced 2025-01-17 01:06:46 +01:00
Adds blurhash
format
This commit is contained in:
parent
84cd91bbbe
commit
b05d4fa7d3
8 changed files with 134 additions and 43 deletions
|
@ -38,7 +38,8 @@ class Asset {
|
|||
// stack handling to properly handle it
|
||||
stackParentId =
|
||||
remote.stackParentId == remote.id ? null : remote.stackParentId,
|
||||
stackCount = remote.stackCount;
|
||||
stackCount = remote.stackCount,
|
||||
thumbhash = remote.thumbhash;
|
||||
|
||||
Asset.local(AssetEntity local, List<int> hash)
|
||||
: localId = local.id,
|
||||
|
@ -91,6 +92,7 @@ class Asset {
|
|||
this.stackCount = 0,
|
||||
this.isReadOnly = false,
|
||||
this.isOffline = false,
|
||||
this.thumbhash,
|
||||
});
|
||||
|
||||
@ignore
|
||||
|
@ -119,6 +121,8 @@ class Asset {
|
|||
/// because Isar cannot sort lists of byte arrays
|
||||
String checksum;
|
||||
|
||||
String? thumbhash;
|
||||
|
||||
@Index(unique: false, replace: false, type: IndexType.hash)
|
||||
String? remoteId;
|
||||
|
||||
|
@ -274,6 +278,7 @@ class Asset {
|
|||
a.exifInfo?.latitude != exifInfo?.latitude ||
|
||||
a.exifInfo?.longitude != exifInfo?.longitude ||
|
||||
// no local stack count or different count from remote
|
||||
a.thumbhash != thumbhash ||
|
||||
((stackCount == null && a.stackCount != null) ||
|
||||
(stackCount != null &&
|
||||
a.stackCount != null &&
|
||||
|
@ -338,6 +343,7 @@ class Asset {
|
|||
isReadOnly: a.isReadOnly,
|
||||
isOffline: a.isOffline,
|
||||
exifInfo: a.exifInfo?.copyWith(id: id) ?? exifInfo,
|
||||
thumbhash: a.thumbhash,
|
||||
);
|
||||
} else {
|
||||
// add only missing values (and set isLocal to true)
|
||||
|
@ -374,6 +380,7 @@ class Asset {
|
|||
ExifInfo? exifInfo,
|
||||
String? stackParentId,
|
||||
int? stackCount,
|
||||
String? thumbhash,
|
||||
}) =>
|
||||
Asset(
|
||||
id: id ?? this.id,
|
||||
|
@ -398,6 +405,7 @@ class Asset {
|
|||
exifInfo: exifInfo ?? this.exifInfo,
|
||||
stackParentId: stackParentId ?? this.stackParentId,
|
||||
stackCount: stackCount ?? this.stackCount,
|
||||
thumbhash: thumbhash ?? this.thumbhash,
|
||||
);
|
||||
|
||||
Future<void> put(Isar db) async {
|
||||
|
|
BIN
mobile/lib/shared/models/asset.g.dart
generated
BIN
mobile/lib/shared/models/asset.g.dart
generated
Binary file not shown.
35
mobile/lib/shared/ui/fade_in_placeholder_image.dart
Normal file
35
mobile/lib/shared/ui/fade_in_placeholder_image.dart
Normal file
|
@ -0,0 +1,35 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/shared/ui/transparent_image.dart';
|
||||
|
||||
class FadeInPlaceholderImage extends StatelessWidget {
|
||||
final Widget placeholder;
|
||||
final ImageProvider image;
|
||||
final Duration duration;
|
||||
final BoxFit fit;
|
||||
|
||||
const FadeInPlaceholderImage({
|
||||
super.key,
|
||||
required this.placeholder,
|
||||
required this.image,
|
||||
this.duration = const Duration(milliseconds: 100),
|
||||
this.fit = BoxFit.cover,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox.expand(
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
placeholder,
|
||||
FadeInImage(
|
||||
fadeInDuration: const Duration(milliseconds: 100),
|
||||
image: image,
|
||||
fit: fit,
|
||||
placeholder: MemoryImage(kTransparentImage),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
|
@ -41,7 +40,6 @@ class ImmichImage extends StatelessWidget {
|
|||
}
|
||||
|
||||
if (asset == null) {
|
||||
print('using remote for $assetId');
|
||||
return ImmichRemoteImageProvider(
|
||||
assetId: assetId!,
|
||||
isThumbnail: false,
|
||||
|
@ -49,12 +47,10 @@ class ImmichImage extends StatelessWidget {
|
|||
}
|
||||
|
||||
if (useLocal(asset)) {
|
||||
print('using local for ${asset.localId}');
|
||||
return ImmichLocalImageProvider(
|
||||
asset: asset,
|
||||
);
|
||||
} else {
|
||||
print('using remote for ${asset.localId}');
|
||||
return ImmichRemoteImageProvider(
|
||||
assetId: asset.remoteId!,
|
||||
isThumbnail: false,
|
||||
|
|
|
@ -1,25 +1,24 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/image_providers/immich_local_thumbnail_provider.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/image_providers/immich_remote_image_provider.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/thumbhash_placeholder.dart';
|
||||
import 'package:octo_image/octo_image.dart';
|
||||
import 'package:thumbhash/thumbhash.dart' as thumbhash;
|
||||
|
||||
class ImmichThumbnail extends StatelessWidget {
|
||||
class ImmichThumbnail extends StatefulWidget {
|
||||
const ImmichThumbnail({
|
||||
this.asset,
|
||||
this.width = 250,
|
||||
this.height = 250,
|
||||
this.fit = BoxFit.cover,
|
||||
this.placeholder,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Asset? asset;
|
||||
final Widget? placeholder;
|
||||
final double width;
|
||||
final double height;
|
||||
final BoxFit fit;
|
||||
|
@ -63,51 +62,47 @@ class ImmichThumbnail extends StatelessWidget {
|
|||
|
||||
static bool useLocal(Asset asset) => !asset.isRemote || asset.isLocal;
|
||||
|
||||
@override
|
||||
State<ImmichThumbnail> createState() => _ImmichThumbnailState();
|
||||
}
|
||||
|
||||
class _ImmichThumbnailState extends State<ImmichThumbnail> {
|
||||
Uint8List? _decodedBlurHash;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
if (widget.asset?.thumbhash != null) {
|
||||
final rgbaImage =
|
||||
thumbhash.thumbHashToRGBA(base64Decode(widget.asset!.thumbhash!));
|
||||
_decodedBlurHash = thumbhash.rgbaToBmp(rgbaImage);
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (asset == null) {
|
||||
if (widget.asset == null) {
|
||||
return Container(
|
||||
color: Colors.grey,
|
||||
width: width,
|
||||
height: height,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
child: const Center(
|
||||
child: Icon(Icons.no_photography),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return OctoImage(
|
||||
fadeInDuration: const Duration(milliseconds: 0),
|
||||
return OctoImage.fromSet(
|
||||
placeholderFadeInDuration: Duration.zero,
|
||||
fadeInDuration: Duration.zero,
|
||||
fadeOutDuration: const Duration(milliseconds: 100),
|
||||
placeholderBuilder: (context) {
|
||||
return placeholder ??
|
||||
ThumbnailPlaceholder(
|
||||
height: height,
|
||||
width: width,
|
||||
);
|
||||
},
|
||||
octoSet: blurHashOrPlaceholder(_decodedBlurHash),
|
||||
image: ImmichThumbnail.imageProvider(
|
||||
asset: asset,
|
||||
asset: widget.asset,
|
||||
),
|
||||
width: width,
|
||||
height: height,
|
||||
fit: fit,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
if (error is PlatformException &&
|
||||
error.code == "The asset not found!") {
|
||||
debugPrint(
|
||||
"Asset ${asset?.localId} does not exist anymore on device!",
|
||||
);
|
||||
} else {
|
||||
debugPrint(
|
||||
"Error getting thumb for assetId=${asset?.localId}: $error",
|
||||
);
|
||||
}
|
||||
return Icon(
|
||||
Icons.image_not_supported_outlined,
|
||||
color: context.primaryColor,
|
||||
);
|
||||
},
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
fit: widget.fit,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
48
mobile/lib/shared/ui/thumbhash_placeholder.dart
Normal file
48
mobile/lib/shared/ui/thumbhash_placeholder.dart
Normal file
|
@ -0,0 +1,48 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_placeholder.dart';
|
||||
import 'package:immich_mobile/shared/ui/fade_in_placeholder_image.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, {
|
||||
BoxFit? fit,
|
||||
Text? errorMessage,
|
||||
}) {
|
||||
return OctoSet(
|
||||
placeholderBuilder: blurHashPlaceholderBuilder(blurhash, fit: fit),
|
||||
errorBuilder: blurHashErrorBuilder(blurhash, fit: fit),
|
||||
);
|
||||
}
|
||||
|
||||
OctoPlaceholderBuilder blurHashPlaceholderBuilder(
|
||||
Uint8List? blurhash, {
|
||||
BoxFit? fit,
|
||||
}) {
|
||||
return (context) => blurhash == null
|
||||
? const ThumbnailPlaceholder()
|
||||
: FadeInPlaceholderImage(
|
||||
placeholder: const ThumbnailPlaceholder(),
|
||||
image: MemoryImage(blurhash),
|
||||
fit: fit ?? BoxFit.cover,
|
||||
);
|
||||
}
|
||||
|
||||
OctoErrorBuilder blurHashErrorBuilder(
|
||||
Uint8List? blurhash, {
|
||||
BoxFit? fit,
|
||||
Text? message,
|
||||
IconData? icon,
|
||||
Color? iconColor,
|
||||
double? iconSize,
|
||||
}) {
|
||||
return OctoError.placeholderWithErrorIcon(
|
||||
blurHashPlaceholderBuilder(blurhash, fit: fit),
|
||||
message: message,
|
||||
icon: icon,
|
||||
iconColor: iconColor,
|
||||
iconSize: iconSize,
|
||||
);
|
||||
}
|
|
@ -1467,6 +1467,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.1"
|
||||
thumbhash:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: thumbhash
|
||||
sha256: "5f6d31c5279ca0b5caa81ec10aae8dcaab098d82cb699ea66ada4ed09c794a37"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.0+1"
|
||||
time:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -57,6 +57,7 @@ dependencies:
|
|||
flutter_local_notifications: ^16.3.2
|
||||
timezone: ^0.9.2
|
||||
octo_image: ^2.0.0
|
||||
thumbhash: 0.1.0+1
|
||||
|
||||
openapi:
|
||||
path: openapi
|
||||
|
|
Loading…
Reference in a new issue