diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index caab7f4b83..688fc7a488 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 b546aa76da..acb7c6a642 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 c983188873..2dc558a127 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 66f2cc9f37..e1a3f24cd1 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 4f485dfb02..f589ba0c67 100644 Binary files a/mobile/lib/shared/models/asset.g.dart and b/mobile/lib/shared/models/asset.g.dart differ diff --git a/mobile/openapi/lib/model/asset_response_dto.dart b/mobile/openapi/lib/model/asset_response_dto.dart index e580ca5a2f..7461186810 100644 Binary files a/mobile/openapi/lib/model/asset_response_dto.dart and b/mobile/openapi/lib/model/asset_response_dto.dart differ diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index f9164a265e..58e0e3055a 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 1c8d5ba34d..0f5e013202 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 caab7f4b83..688fc7a488 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}