diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index caab7f4b8..688fc7a48 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -771,7 +771,7 @@ export interface AssetResponseDto { * @type {number} * @memberof AssetResponseDto */ - 'stackCount': number; + 'stackCount': number | null; /** * * @type {string} diff --git a/mobile/lib/modules/asset_viewer/views/gallery_viewer.dart b/mobile/lib/modules/asset_viewer/views/gallery_viewer.dart index b546aa76d..acb7c6a64 100644 --- a/mobile/lib/modules/asset_viewer/views/gallery_viewer.dart +++ b/mobile/lib/modules/asset_viewer/views/gallery_viewer.dart @@ -83,7 +83,7 @@ class GalleryViewerPage extends HookConsumerWidget { navStack.length > 2 && navStack.elementAt(navStack.length - 2).name == TrashRoute.name; final stackIndex = useState(-1); - final stack = showStack && currentAsset.stackCount > 0 + final stack = showStack && currentAsset.stackChildrenCount > 0 ? ref.watch(assetStackStateProvider(currentAsset)) : []; final stackElements = showStack ? [currentAsset, ...stack] : []; diff --git a/mobile/lib/modules/home/ui/asset_grid/thumbnail_image.dart b/mobile/lib/modules/home/ui/asset_grid/thumbnail_image.dart index c98318887..2dc558a12 100644 --- a/mobile/lib/modules/home/ui/asset_grid/thumbnail_image.dart +++ b/mobile/lib/modules/home/ui/asset_grid/thumbnail_image.dart @@ -104,16 +104,16 @@ class ThumbnailImage extends StatelessWidget { right: 5, child: Row( children: [ - if (asset.stackCount > 1) + if (asset.stackChildrenCount > 1) Text( - "${asset.stackCount}", + "${asset.stackChildrenCount}", style: const TextStyle( color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold, ), ), - if (asset.stackCount > 1) + if (asset.stackChildrenCount > 1) const SizedBox( width: 3, ), @@ -233,7 +233,7 @@ class ThumbnailImage extends StatelessWidget { ), ), if (!asset.isImage) buildVideoIcon(), - if (asset.isImage && asset.stackCount > 0) buildStackIcon(), + if (asset.isImage && asset.stackChildrenCount > 0) buildStackIcon(), ], ), ); diff --git a/mobile/lib/shared/models/asset.dart b/mobile/lib/shared/models/asset.dart index 66f2cc9f3..e1a3f24cd 100644 --- a/mobile/lib/shared/models/asset.dart +++ b/mobile/lib/shared/models/asset.dart @@ -153,7 +153,10 @@ class Asset { String? stackParentId; - int stackCount; + @ignore + int get stackChildrenCount => stackCount ?? 0; + + int? stackCount; /// `true` if this [Asset] is present on the device @ignore @@ -253,7 +256,11 @@ class Asset { isFavorite != a.isFavorite || isArchived != a.isArchived || isTrashed != a.isTrashed || - stackCount != a.stackCount; + // no local stack count or different count from remote + ((stackCount == null && a.stackCount != null) || + (stackCount != null && + a.stackCount != null && + stackCount != a.stackCount)); } /// Returns a new [Asset] with values from this and merged & updated with [a] @@ -269,6 +276,7 @@ class Asset { width: a.width ?? width, height: a.height ?? height, exifInfo: a.exifInfo?.copyWith(id: id) ?? exifInfo, + stackCount: a.stackCount ?? stackCount, ); } else if (isRemote) { return _copyWith( @@ -299,7 +307,7 @@ class Asset { height: a.height, livePhotoVideoId: a.livePhotoVideoId, stackParentId: a.stackParentId, - stackCount: a.stackCount, + stackCount: a.stackCount ?? stackCount, // isFavorite + isArchived are not set by device-only assets isFavorite: a.isFavorite, isArchived: a.isArchived, diff --git a/mobile/lib/shared/models/asset.g.dart b/mobile/lib/shared/models/asset.g.dart index 4f485dfb0..f589ba0c6 100644 --- a/mobile/lib/shared/models/asset.g.dart +++ b/mobile/lib/shared/models/asset.g.dart @@ -250,7 +250,7 @@ Asset _assetDeserialize( localId: reader.readStringOrNull(offsets[10]), ownerId: reader.readLong(offsets[11]), remoteId: reader.readStringOrNull(offsets[12]), - stackCount: reader.readLong(offsets[13]), + stackCount: reader.readLongOrNull(offsets[13]), stackParentId: reader.readStringOrNull(offsets[14]), type: _AssettypeValueEnumMap[reader.readByteOrNull(offsets[15])] ?? AssetType.other, @@ -294,7 +294,7 @@ P _assetDeserializeProp

( case 12: return (reader.readStringOrNull(offset)) as P; case 13: - return (reader.readLong(offset)) as P; + return (reader.readLongOrNull(offset)) as P; case 14: return (reader.readStringOrNull(offset)) as P; case 15: @@ -1825,8 +1825,24 @@ extension AssetQueryFilter on QueryBuilder { }); } + QueryBuilder stackCountIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'stackCount', + )); + }); + } + + QueryBuilder stackCountIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'stackCount', + )); + }); + } + QueryBuilder stackCountEqualTo( - int value) { + int? value) { return QueryBuilder.apply(this, (query) { return query.addFilterCondition(FilterCondition.equalTo( property: r'stackCount', @@ -1836,7 +1852,7 @@ extension AssetQueryFilter on QueryBuilder { } QueryBuilder stackCountGreaterThan( - int value, { + int? value, { bool include = false, }) { return QueryBuilder.apply(this, (query) { @@ -1849,7 +1865,7 @@ extension AssetQueryFilter on QueryBuilder { } QueryBuilder stackCountLessThan( - int value, { + int? value, { bool include = false, }) { return QueryBuilder.apply(this, (query) { @@ -1862,8 +1878,8 @@ extension AssetQueryFilter on QueryBuilder { } QueryBuilder stackCountBetween( - int lower, - int upper, { + int? lower, + int? upper, { bool includeLower = true, bool includeUpper = true, }) { @@ -2854,7 +2870,7 @@ extension AssetQueryProperty on QueryBuilder { }); } - QueryBuilder stackCountProperty() { + QueryBuilder stackCountProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'stackCount'); }); diff --git a/mobile/openapi/lib/model/asset_response_dto.dart b/mobile/openapi/lib/model/asset_response_dto.dart index e580ca5a2..746118681 100644 --- a/mobile/openapi/lib/model/asset_response_dto.dart +++ b/mobile/openapi/lib/model/asset_response_dto.dart @@ -118,7 +118,7 @@ class AssetResponseDto { List stack; - int stackCount; + int? stackCount; String? stackParentId; @@ -194,7 +194,7 @@ class AssetResponseDto { (resized.hashCode) + (smartInfo == null ? 0 : smartInfo!.hashCode) + (stack.hashCode) + - (stackCount.hashCode) + + (stackCount == null ? 0 : stackCount!.hashCode) + (stackParentId == null ? 0 : stackParentId!.hashCode) + (tags.hashCode) + (thumbhash == null ? 0 : thumbhash!.hashCode) + @@ -248,7 +248,11 @@ class AssetResponseDto { // json[r'smartInfo'] = null; } json[r'stack'] = this.stack; + if (this.stackCount != null) { json[r'stackCount'] = this.stackCount; + } else { + // json[r'stackCount'] = null; + } if (this.stackParentId != null) { json[r'stackParentId'] = this.stackParentId; } else { @@ -299,7 +303,7 @@ class AssetResponseDto { resized: mapValueOfType(json, r'resized')!, smartInfo: SmartInfoResponseDto.fromJson(json[r'smartInfo']), stack: AssetResponseDto.listFromJson(json[r'stack']), - stackCount: mapValueOfType(json, r'stackCount')!, + stackCount: mapValueOfType(json, r'stackCount'), stackParentId: mapValueOfType(json, r'stackParentId'), tags: TagResponseDto.listFromJson(json[r'tags']), thumbhash: mapValueOfType(json, r'thumbhash'), diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index f9164a265..58e0e3055 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -6009,6 +6009,7 @@ "type": "array" }, "stackCount": { + "nullable": true, "type": "integer" }, "stackParentId": { diff --git a/server/src/domain/asset/response-dto/asset-response.dto.ts b/server/src/domain/asset/response-dto/asset-response.dto.ts index 1c8d5ba34..0f5e01320 100644 --- a/server/src/domain/asset/response-dto/asset-response.dto.ts +++ b/server/src/domain/asset/response-dto/asset-response.dto.ts @@ -45,7 +45,7 @@ export class AssetResponseDto extends SanitizedAssetResponseDto { stackParentId?: string | null; stack?: AssetResponseDto[]; @ApiProperty({ type: 'integer' }) - stackCount!: number; + stackCount!: number | null; } export type AssetMapOptions = { @@ -102,7 +102,7 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As checksum: entity.checksum.toString('base64'), stackParentId: entity.stackParentId, stack: withStack ? entity.stack?.map((a) => mapAsset(a, { stripMetadata })) ?? undefined : undefined, - stackCount: entity.stack?.length ?? 0, + stackCount: entity.stack?.length ?? null, isExternal: entity.isExternal, isOffline: entity.isOffline, isReadOnly: entity.isReadOnly, diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index caab7f4b8..688fc7a48 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -771,7 +771,7 @@ export interface AssetResponseDto { * @type {number} * @memberof AssetResponseDto */ - 'stackCount': number; + 'stackCount': number | null; /** * * @type {string}