mirror of
https://github.com/immich-app/immich.git
synced 2025-07-09 03:04:16 -04:00
Adds blurhash
format
This commit is contained in:
parent
84cd91bbbe
commit
b05d4fa7d3
@ -38,7 +38,8 @@ class Asset {
|
|||||||
// stack handling to properly handle it
|
// stack handling to properly handle it
|
||||||
stackParentId =
|
stackParentId =
|
||||||
remote.stackParentId == remote.id ? null : remote.stackParentId,
|
remote.stackParentId == remote.id ? null : remote.stackParentId,
|
||||||
stackCount = remote.stackCount;
|
stackCount = remote.stackCount,
|
||||||
|
thumbhash = remote.thumbhash;
|
||||||
|
|
||||||
Asset.local(AssetEntity local, List<int> hash)
|
Asset.local(AssetEntity local, List<int> hash)
|
||||||
: localId = local.id,
|
: localId = local.id,
|
||||||
@ -91,6 +92,7 @@ class Asset {
|
|||||||
this.stackCount = 0,
|
this.stackCount = 0,
|
||||||
this.isReadOnly = false,
|
this.isReadOnly = false,
|
||||||
this.isOffline = false,
|
this.isOffline = false,
|
||||||
|
this.thumbhash,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ignore
|
@ignore
|
||||||
@ -119,6 +121,8 @@ class Asset {
|
|||||||
/// because Isar cannot sort lists of byte arrays
|
/// because Isar cannot sort lists of byte arrays
|
||||||
String checksum;
|
String checksum;
|
||||||
|
|
||||||
|
String? thumbhash;
|
||||||
|
|
||||||
@Index(unique: false, replace: false, type: IndexType.hash)
|
@Index(unique: false, replace: false, type: IndexType.hash)
|
||||||
String? remoteId;
|
String? remoteId;
|
||||||
|
|
||||||
@ -274,6 +278,7 @@ class Asset {
|
|||||||
a.exifInfo?.latitude != exifInfo?.latitude ||
|
a.exifInfo?.latitude != exifInfo?.latitude ||
|
||||||
a.exifInfo?.longitude != exifInfo?.longitude ||
|
a.exifInfo?.longitude != exifInfo?.longitude ||
|
||||||
// no local stack count or different count from remote
|
// no local stack count or different count from remote
|
||||||
|
a.thumbhash != thumbhash ||
|
||||||
((stackCount == null && a.stackCount != null) ||
|
((stackCount == null && a.stackCount != null) ||
|
||||||
(stackCount != null &&
|
(stackCount != null &&
|
||||||
a.stackCount != null &&
|
a.stackCount != null &&
|
||||||
@ -338,6 +343,7 @@ class Asset {
|
|||||||
isReadOnly: a.isReadOnly,
|
isReadOnly: a.isReadOnly,
|
||||||
isOffline: a.isOffline,
|
isOffline: a.isOffline,
|
||||||
exifInfo: a.exifInfo?.copyWith(id: id) ?? exifInfo,
|
exifInfo: a.exifInfo?.copyWith(id: id) ?? exifInfo,
|
||||||
|
thumbhash: a.thumbhash,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// add only missing values (and set isLocal to true)
|
// add only missing values (and set isLocal to true)
|
||||||
@ -374,6 +380,7 @@ class Asset {
|
|||||||
ExifInfo? exifInfo,
|
ExifInfo? exifInfo,
|
||||||
String? stackParentId,
|
String? stackParentId,
|
||||||
int? stackCount,
|
int? stackCount,
|
||||||
|
String? thumbhash,
|
||||||
}) =>
|
}) =>
|
||||||
Asset(
|
Asset(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
@ -398,6 +405,7 @@ class Asset {
|
|||||||
exifInfo: exifInfo ?? this.exifInfo,
|
exifInfo: exifInfo ?? this.exifInfo,
|
||||||
stackParentId: stackParentId ?? this.stackParentId,
|
stackParentId: stackParentId ?? this.stackParentId,
|
||||||
stackCount: stackCount ?? this.stackCount,
|
stackCount: stackCount ?? this.stackCount,
|
||||||
|
thumbhash: thumbhash ?? this.thumbhash,
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<void> put(Isar db) async {
|
Future<void> put(Isar db) async {
|
||||||
|
220
mobile/lib/shared/models/asset.g.dart
generated
220
mobile/lib/shared/models/asset.g.dart
generated
@ -102,19 +102,24 @@ const AssetSchema = CollectionSchema(
|
|||||||
name: r'stackParentId',
|
name: r'stackParentId',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'type': PropertySchema(
|
r'thumbhash': PropertySchema(
|
||||||
id: 17,
|
id: 17,
|
||||||
|
name: r'thumbhash',
|
||||||
|
type: IsarType.string,
|
||||||
|
),
|
||||||
|
r'type': PropertySchema(
|
||||||
|
id: 18,
|
||||||
name: r'type',
|
name: r'type',
|
||||||
type: IsarType.byte,
|
type: IsarType.byte,
|
||||||
enumMap: _AssettypeEnumValueMap,
|
enumMap: _AssettypeEnumValueMap,
|
||||||
),
|
),
|
||||||
r'updatedAt': PropertySchema(
|
r'updatedAt': PropertySchema(
|
||||||
id: 18,
|
id: 19,
|
||||||
name: r'updatedAt',
|
name: r'updatedAt',
|
||||||
type: IsarType.dateTime,
|
type: IsarType.dateTime,
|
||||||
),
|
),
|
||||||
r'width': PropertySchema(
|
r'width': PropertySchema(
|
||||||
id: 19,
|
id: 20,
|
||||||
name: r'width',
|
name: r'width',
|
||||||
type: IsarType.int,
|
type: IsarType.int,
|
||||||
)
|
)
|
||||||
@ -210,6 +215,12 @@ int _assetEstimateSize(
|
|||||||
bytesCount += 3 + value.length * 3;
|
bytesCount += 3 + value.length * 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
final value = object.thumbhash;
|
||||||
|
if (value != null) {
|
||||||
|
bytesCount += 3 + value.length * 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
return bytesCount;
|
return bytesCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,9 +247,10 @@ void _assetSerialize(
|
|||||||
writer.writeString(offsets[14], object.remoteId);
|
writer.writeString(offsets[14], object.remoteId);
|
||||||
writer.writeLong(offsets[15], object.stackCount);
|
writer.writeLong(offsets[15], object.stackCount);
|
||||||
writer.writeString(offsets[16], object.stackParentId);
|
writer.writeString(offsets[16], object.stackParentId);
|
||||||
writer.writeByte(offsets[17], object.type.index);
|
writer.writeString(offsets[17], object.thumbhash);
|
||||||
writer.writeDateTime(offsets[18], object.updatedAt);
|
writer.writeByte(offsets[18], object.type.index);
|
||||||
writer.writeInt(offsets[19], object.width);
|
writer.writeDateTime(offsets[19], object.updatedAt);
|
||||||
|
writer.writeInt(offsets[20], object.width);
|
||||||
}
|
}
|
||||||
|
|
||||||
Asset _assetDeserialize(
|
Asset _assetDeserialize(
|
||||||
@ -266,10 +278,11 @@ Asset _assetDeserialize(
|
|||||||
remoteId: reader.readStringOrNull(offsets[14]),
|
remoteId: reader.readStringOrNull(offsets[14]),
|
||||||
stackCount: reader.readLongOrNull(offsets[15]),
|
stackCount: reader.readLongOrNull(offsets[15]),
|
||||||
stackParentId: reader.readStringOrNull(offsets[16]),
|
stackParentId: reader.readStringOrNull(offsets[16]),
|
||||||
type: _AssettypeValueEnumMap[reader.readByteOrNull(offsets[17])] ??
|
thumbhash: reader.readStringOrNull(offsets[17]),
|
||||||
|
type: _AssettypeValueEnumMap[reader.readByteOrNull(offsets[18])] ??
|
||||||
AssetType.other,
|
AssetType.other,
|
||||||
updatedAt: reader.readDateTime(offsets[18]),
|
updatedAt: reader.readDateTime(offsets[19]),
|
||||||
width: reader.readIntOrNull(offsets[19]),
|
width: reader.readIntOrNull(offsets[20]),
|
||||||
);
|
);
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
@ -316,11 +329,13 @@ P _assetDeserializeProp<P>(
|
|||||||
case 16:
|
case 16:
|
||||||
return (reader.readStringOrNull(offset)) as P;
|
return (reader.readStringOrNull(offset)) as P;
|
||||||
case 17:
|
case 17:
|
||||||
|
return (reader.readStringOrNull(offset)) as P;
|
||||||
|
case 18:
|
||||||
return (_AssettypeValueEnumMap[reader.readByteOrNull(offset)] ??
|
return (_AssettypeValueEnumMap[reader.readByteOrNull(offset)] ??
|
||||||
AssetType.other) as P;
|
AssetType.other) as P;
|
||||||
case 18:
|
|
||||||
return (reader.readDateTime(offset)) as P;
|
|
||||||
case 19:
|
case 19:
|
||||||
|
return (reader.readDateTime(offset)) as P;
|
||||||
|
case 20:
|
||||||
return (reader.readIntOrNull(offset)) as P;
|
return (reader.readIntOrNull(offset)) as P;
|
||||||
default:
|
default:
|
||||||
throw IsarError('Unknown property with id $propertyId');
|
throw IsarError('Unknown property with id $propertyId');
|
||||||
@ -2078,6 +2093,152 @@ extension AssetQueryFilter on QueryBuilder<Asset, Asset, QFilterCondition> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> thumbhashIsNull() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(const FilterCondition.isNull(
|
||||||
|
property: r'thumbhash',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> thumbhashIsNotNull() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||||
|
property: r'thumbhash',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> thumbhashEqualTo(
|
||||||
|
String? value, {
|
||||||
|
bool caseSensitive = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
|
property: r'thumbhash',
|
||||||
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> thumbhashGreaterThan(
|
||||||
|
String? value, {
|
||||||
|
bool include = false,
|
||||||
|
bool caseSensitive = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||||
|
include: include,
|
||||||
|
property: r'thumbhash',
|
||||||
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> thumbhashLessThan(
|
||||||
|
String? value, {
|
||||||
|
bool include = false,
|
||||||
|
bool caseSensitive = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.lessThan(
|
||||||
|
include: include,
|
||||||
|
property: r'thumbhash',
|
||||||
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> thumbhashBetween(
|
||||||
|
String? lower,
|
||||||
|
String? upper, {
|
||||||
|
bool includeLower = true,
|
||||||
|
bool includeUpper = true,
|
||||||
|
bool caseSensitive = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.between(
|
||||||
|
property: r'thumbhash',
|
||||||
|
lower: lower,
|
||||||
|
includeLower: includeLower,
|
||||||
|
upper: upper,
|
||||||
|
includeUpper: includeUpper,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> thumbhashStartsWith(
|
||||||
|
String value, {
|
||||||
|
bool caseSensitive = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.startsWith(
|
||||||
|
property: r'thumbhash',
|
||||||
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> thumbhashEndsWith(
|
||||||
|
String value, {
|
||||||
|
bool caseSensitive = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.endsWith(
|
||||||
|
property: r'thumbhash',
|
||||||
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> thumbhashContains(
|
||||||
|
String value,
|
||||||
|
{bool caseSensitive = true}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.contains(
|
||||||
|
property: r'thumbhash',
|
||||||
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> thumbhashMatches(
|
||||||
|
String pattern,
|
||||||
|
{bool caseSensitive = true}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.matches(
|
||||||
|
property: r'thumbhash',
|
||||||
|
wildcard: pattern,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> thumbhashIsEmpty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
|
property: r'thumbhash',
|
||||||
|
value: '',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> thumbhashIsNotEmpty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||||
|
property: r'thumbhash',
|
||||||
|
value: '',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterFilterCondition> typeEqualTo(
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> typeEqualTo(
|
||||||
AssetType value) {
|
AssetType value) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
@ -2462,6 +2623,18 @@ extension AssetQuerySortBy on QueryBuilder<Asset, Asset, QSortBy> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterSortBy> sortByThumbhash() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'thumbhash', Sort.asc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterSortBy> sortByThumbhashDesc() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'thumbhash', Sort.desc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterSortBy> sortByType() {
|
QueryBuilder<Asset, Asset, QAfterSortBy> sortByType() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'type', Sort.asc);
|
return query.addSortBy(r'type', Sort.asc);
|
||||||
@ -2716,6 +2889,18 @@ extension AssetQuerySortThenBy on QueryBuilder<Asset, Asset, QSortThenBy> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterSortBy> thenByThumbhash() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'thumbhash', Sort.asc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterSortBy> thenByThumbhashDesc() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'thumbhash', Sort.desc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterSortBy> thenByType() {
|
QueryBuilder<Asset, Asset, QAfterSortBy> thenByType() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'type', Sort.asc);
|
return query.addSortBy(r'type', Sort.asc);
|
||||||
@ -2864,6 +3049,13 @@ extension AssetQueryWhereDistinct on QueryBuilder<Asset, Asset, QDistinct> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QDistinct> distinctByThumbhash(
|
||||||
|
{bool caseSensitive = true}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addDistinctBy(r'thumbhash', caseSensitive: caseSensitive);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QDistinct> distinctByType() {
|
QueryBuilder<Asset, Asset, QDistinct> distinctByType() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addDistinctBy(r'type');
|
return query.addDistinctBy(r'type');
|
||||||
@ -2992,6 +3184,12 @@ extension AssetQueryProperty on QueryBuilder<Asset, Asset, QQueryProperty> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, String?, QQueryOperations> thumbhashProperty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addPropertyName(r'thumbhash');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, AssetType, QQueryOperations> typeProperty() {
|
QueryBuilder<Asset, AssetType, QQueryOperations> typeProperty() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addPropertyName(r'type');
|
return query.addPropertyName(r'type');
|
||||||
|
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/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
@ -41,7 +40,6 @@ class ImmichImage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (asset == null) {
|
if (asset == null) {
|
||||||
print('using remote for $assetId');
|
|
||||||
return ImmichRemoteImageProvider(
|
return ImmichRemoteImageProvider(
|
||||||
assetId: assetId!,
|
assetId: assetId!,
|
||||||
isThumbnail: false,
|
isThumbnail: false,
|
||||||
@ -49,12 +47,10 @@ class ImmichImage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (useLocal(asset)) {
|
if (useLocal(asset)) {
|
||||||
print('using local for ${asset.localId}');
|
|
||||||
return ImmichLocalImageProvider(
|
return ImmichLocalImageProvider(
|
||||||
asset: asset,
|
asset: asset,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
print('using remote for ${asset.localId}');
|
|
||||||
return ImmichRemoteImageProvider(
|
return ImmichRemoteImageProvider(
|
||||||
assetId: asset.remoteId!,
|
assetId: asset.remoteId!,
|
||||||
isThumbnail: false,
|
isThumbnail: false,
|
||||||
|
@ -1,25 +1,24 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
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_local_thumbnail_provider.dart';
|
||||||
import 'package:immich_mobile/modules/asset_viewer/image_providers/immich_remote_image_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/models/asset.dart';
|
||||||
|
import 'package:immich_mobile/shared/ui/thumbhash_placeholder.dart';
|
||||||
import 'package:octo_image/octo_image.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({
|
const ImmichThumbnail({
|
||||||
this.asset,
|
this.asset,
|
||||||
this.width = 250,
|
this.width = 250,
|
||||||
this.height = 250,
|
this.height = 250,
|
||||||
this.fit = BoxFit.cover,
|
this.fit = BoxFit.cover,
|
||||||
this.placeholder,
|
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Asset? asset;
|
final Asset? asset;
|
||||||
final Widget? placeholder;
|
|
||||||
final double width;
|
final double width;
|
||||||
final double height;
|
final double height;
|
||||||
final BoxFit fit;
|
final BoxFit fit;
|
||||||
@ -63,51 +62,47 @@ class ImmichThumbnail extends StatelessWidget {
|
|||||||
|
|
||||||
static bool useLocal(Asset asset) => !asset.isRemote || asset.isLocal;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (asset == null) {
|
if (widget.asset == null) {
|
||||||
return Container(
|
return Container(
|
||||||
color: Colors.grey,
|
color: Colors.grey,
|
||||||
width: width,
|
width: widget.width,
|
||||||
height: height,
|
height: widget.height,
|
||||||
child: const Center(
|
child: const Center(
|
||||||
child: Icon(Icons.no_photography),
|
child: Icon(Icons.no_photography),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return OctoImage(
|
return OctoImage.fromSet(
|
||||||
fadeInDuration: const Duration(milliseconds: 0),
|
placeholderFadeInDuration: Duration.zero,
|
||||||
|
fadeInDuration: Duration.zero,
|
||||||
fadeOutDuration: const Duration(milliseconds: 100),
|
fadeOutDuration: const Duration(milliseconds: 100),
|
||||||
placeholderBuilder: (context) {
|
octoSet: blurHashOrPlaceholder(_decodedBlurHash),
|
||||||
return placeholder ??
|
|
||||||
ThumbnailPlaceholder(
|
|
||||||
height: height,
|
|
||||||
width: width,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
image: ImmichThumbnail.imageProvider(
|
image: ImmichThumbnail.imageProvider(
|
||||||
asset: asset,
|
asset: widget.asset,
|
||||||
),
|
),
|
||||||
width: width,
|
width: widget.width,
|
||||||
height: height,
|
height: widget.height,
|
||||||
fit: fit,
|
fit: widget.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,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.1"
|
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:
|
time:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -57,6 +57,7 @@ dependencies:
|
|||||||
flutter_local_notifications: ^16.3.2
|
flutter_local_notifications: ^16.3.2
|
||||||
timezone: ^0.9.2
|
timezone: ^0.9.2
|
||||||
octo_image: ^2.0.0
|
octo_image: ^2.0.0
|
||||||
|
thumbhash: 0.1.0+1
|
||||||
|
|
||||||
openapi:
|
openapi:
|
||||||
path: openapi
|
path: openapi
|
||||||
|
Loading…
x
Reference in New Issue
Block a user