mirror of
https://github.com/immich-app/immich
synced 2026-08-15 13:03:57 +00:00
Merge aa8c9531f3 into f9c05af45f
This commit is contained in:
commit
1e5d5ef65d
3 changed files with 210 additions and 28 deletions
|
|
@ -34,7 +34,8 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||
leftOuterJoin(
|
||||
_db.remoteAssetEntity,
|
||||
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId) &
|
||||
_db.remoteAssetEntity.deletedAt.isNull(),
|
||||
_db.remoteAssetEntity.deletedAt.isNull() &
|
||||
_db.remoteAssetEntity.visibility.isIn([AssetVisibility.archive.index, AssetVisibility.timeline.index]),
|
||||
useColumns: false,
|
||||
),
|
||||
leftOuterJoin(
|
||||
|
|
@ -94,7 +95,11 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||
leftOuterJoin(
|
||||
_db.remoteAssetEntity,
|
||||
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId) &
|
||||
_db.remoteAssetEntity.deletedAt.isNull(),
|
||||
_db.remoteAssetEntity.deletedAt.isNull() &
|
||||
_db.remoteAssetEntity.visibility.isIn([
|
||||
AssetVisibility.archive.index,
|
||||
AssetVisibility.timeline.index,
|
||||
]),
|
||||
useColumns: false,
|
||||
),
|
||||
leftOuterJoin(
|
||||
|
|
@ -218,16 +223,22 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||
}
|
||||
|
||||
Stream<(DateTime, DateTime)> watchDateRange(String albumId) {
|
||||
final dateExp = coalesce([_db.remoteAssetEntity.localDateTime, _db.remoteAssetEntity.createdAt]);
|
||||
final query = _db.remoteAlbumAssetEntity.selectOnly()
|
||||
..where(_db.remoteAlbumAssetEntity.albumId.equals(albumId))
|
||||
..addColumns([_db.remoteAssetEntity.createdAt.min(), _db.remoteAssetEntity.createdAt.max()])
|
||||
..addColumns([dateExp.min(), dateExp.max()])
|
||||
..join([
|
||||
innerJoin(_db.remoteAssetEntity, _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId)),
|
||||
innerJoin(
|
||||
_db.remoteAssetEntity,
|
||||
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId) &
|
||||
_db.remoteAssetEntity.deletedAt.isNull() &
|
||||
_db.remoteAssetEntity.visibility.isIn([AssetVisibility.archive.index, AssetVisibility.timeline.index]),
|
||||
),
|
||||
]);
|
||||
|
||||
return query.map((row) {
|
||||
final minDate = row.read(_db.remoteAssetEntity.createdAt.min());
|
||||
final maxDate = row.read(_db.remoteAssetEntity.createdAt.max());
|
||||
final minDate = row.read(dateExp.min());
|
||||
final maxDate = row.read(dateExp.max());
|
||||
return (minDate ?? DateTime.now(), maxDate ?? DateTime.now());
|
||||
}).watchSingle();
|
||||
}
|
||||
|
|
@ -419,16 +430,22 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||
'''
|
||||
SELECT
|
||||
raae.album_id,
|
||||
$sqlAgg(rae.local_date_time) AS asset_date
|
||||
$sqlAgg(COALESCE(rae.local_date_time, rae.created_at)) AS asset_date
|
||||
FROM json_each(?) ids
|
||||
INNER JOIN remote_album_asset_entity raae
|
||||
ON raae.album_id = ids.value
|
||||
INNER JOIN remote_asset_entity rae
|
||||
ON rae.id = raae.asset_id
|
||||
WHERE rae.deleted_at IS NULL
|
||||
AND rae.visibility IN (?, ?)
|
||||
GROUP BY raae.album_id
|
||||
ORDER BY asset_date ASC
|
||||
''',
|
||||
variables: [Variable<String>(jsonIds)],
|
||||
variables: [
|
||||
Variable<String>(jsonIds),
|
||||
Variable<int>(AssetVisibility.timeline.index),
|
||||
Variable<int>(AssetVisibility.archive.index),
|
||||
],
|
||||
readsFrom: {_db.remoteAlbumAssetEntity, _db.remoteAssetEntity},
|
||||
)
|
||||
.get();
|
||||
|
|
@ -530,7 +547,11 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||
leftOuterJoin(
|
||||
_db.remoteAssetEntity,
|
||||
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId) &
|
||||
_db.remoteAssetEntity.deletedAt.isNull(),
|
||||
_db.remoteAssetEntity.deletedAt.isNull() &
|
||||
_db.remoteAssetEntity.visibility.isIn([
|
||||
AssetVisibility.archive.index,
|
||||
AssetVisibility.timeline.index,
|
||||
]),
|
||||
useColumns: false,
|
||||
),
|
||||
leftOuterJoin(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
|
||||
|
||||
import '../repository_context.dart';
|
||||
|
|
@ -86,6 +87,24 @@ void main() {
|
|||
expect(albums.first.id, album.id);
|
||||
expect(albums.first.assetCount, 0);
|
||||
});
|
||||
|
||||
test('excludes non-default visibility assets (hidden/locked) from assetCount', () async {
|
||||
final user = await ctx.newUser();
|
||||
final album = await ctx.newRemoteAlbum(ownerId: user.id);
|
||||
final active = await ctx.newRemoteAsset(ownerId: user.id, visibility: AssetVisibility.timeline);
|
||||
final archived = await ctx.newRemoteAsset(ownerId: user.id, visibility: AssetVisibility.archive);
|
||||
final hidden = await ctx.newRemoteAsset(ownerId: user.id, visibility: AssetVisibility.hidden);
|
||||
final locked = await ctx.newRemoteAsset(ownerId: user.id, visibility: AssetVisibility.locked);
|
||||
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: active.id);
|
||||
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: archived.id);
|
||||
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: hidden.id);
|
||||
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: locked.id);
|
||||
|
||||
final albums = await sut.getAll();
|
||||
|
||||
expect(albums, hasLength(1));
|
||||
expect(albums.first.assetCount, 2);
|
||||
});
|
||||
});
|
||||
|
||||
group('get', () {
|
||||
|
|
@ -101,6 +120,20 @@ void main() {
|
|||
expect(result?.id, album.id);
|
||||
expect(result?.assetCount, 0);
|
||||
});
|
||||
|
||||
test('excludes non-default visibility assets (hidden/locked) from assetCount', () async {
|
||||
final user = await ctx.newUser();
|
||||
final album = await ctx.newRemoteAlbum(ownerId: user.id);
|
||||
final active = await ctx.newRemoteAsset(ownerId: user.id, visibility: AssetVisibility.timeline);
|
||||
final hidden = await ctx.newRemoteAsset(ownerId: user.id, visibility: AssetVisibility.hidden);
|
||||
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: active.id);
|
||||
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: hidden.id);
|
||||
|
||||
final result = await sut.get(album.id);
|
||||
|
||||
expect(result, isNotNull);
|
||||
expect(result?.assetCount, 1);
|
||||
});
|
||||
});
|
||||
|
||||
group('getAlbumsContainingAsset', () {
|
||||
|
|
@ -130,6 +163,87 @@ void main() {
|
|||
expect(albums, hasLength(1));
|
||||
expect(albums.first.assetCount, 0);
|
||||
});
|
||||
|
||||
test('excludes non-default visibility assets from assetCount', () async {
|
||||
final user = await ctx.newUser();
|
||||
final album = await ctx.newRemoteAlbum(ownerId: user.id);
|
||||
final active = await ctx.newRemoteAsset(ownerId: user.id);
|
||||
final hidden = await ctx.newRemoteAsset(ownerId: user.id, visibility: AssetVisibility.hidden);
|
||||
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: active.id);
|
||||
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: hidden.id);
|
||||
|
||||
final albums = await sut.getAlbumsContainingAsset(active.id);
|
||||
|
||||
expect(albums, hasLength(1));
|
||||
expect(albums.first.assetCount, 1);
|
||||
});
|
||||
});
|
||||
|
||||
group('watchDateRange', () {
|
||||
test('excludes trashed and hidden/locked assets from date range calculation', () async {
|
||||
final user = await ctx.newUser();
|
||||
final album = await ctx.newRemoteAlbum(ownerId: user.id);
|
||||
|
||||
final trashed = await ctx.newRemoteAsset(
|
||||
ownerId: user.id,
|
||||
createdAt: DateTime(2023, 1, 1),
|
||||
deletedAt: DateTime(2025, 1, 1),
|
||||
);
|
||||
final active1 = await ctx.newRemoteAsset(
|
||||
ownerId: user.id,
|
||||
createdAt: DateTime(2024, 5, 10),
|
||||
localDateTime: DateTime(2024, 5, 1),
|
||||
visibility: AssetVisibility.timeline,
|
||||
);
|
||||
final active2 = await ctx.newRemoteAsset(
|
||||
ownerId: user.id,
|
||||
createdAt: DateTime(2024, 10, 10),
|
||||
localDateTime: DateTime(2024, 10, 1),
|
||||
visibility: AssetVisibility.archive,
|
||||
);
|
||||
final hidden = await ctx.newRemoteAsset(
|
||||
ownerId: user.id,
|
||||
createdAt: DateTime(2024, 12, 1),
|
||||
visibility: AssetVisibility.hidden,
|
||||
);
|
||||
|
||||
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: trashed.id);
|
||||
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: active1.id);
|
||||
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: active2.id);
|
||||
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: hidden.id);
|
||||
|
||||
final range = await sut.watchDateRange(album.id).first;
|
||||
|
||||
expect(range.$1.toUtc(), active1.localDateTime!.toUtc());
|
||||
expect(range.$2.toUtc(), active2.localDateTime!.toUtc());
|
||||
});
|
||||
|
||||
test('prioritizes localDateTime over createdAt for date range calculation', () async {
|
||||
final user = await ctx.newUser();
|
||||
final album = await ctx.newRemoteAlbum(ownerId: user.id);
|
||||
|
||||
final asset1 = await ctx.newRemoteAsset(
|
||||
ownerId: user.id,
|
||||
createdAt: DateTime(2024, 5, 10),
|
||||
localDateTime: DateTime(2024, 5, 1),
|
||||
visibility: AssetVisibility.timeline,
|
||||
);
|
||||
final asset2 = await ctx.newRemoteAsset(
|
||||
ownerId: user.id,
|
||||
createdAt: DateTime(2024, 10, 10),
|
||||
localDateTime: DateTime(2024, 10, 1),
|
||||
visibility: AssetVisibility.archive,
|
||||
);
|
||||
|
||||
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: asset1.id);
|
||||
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: asset2.id);
|
||||
|
||||
final range = await sut.watchDateRange(album.id).first;
|
||||
|
||||
// Dates should match localDateTime, not createdAt
|
||||
expect(range.$1.toUtc(), DateTime(2024, 5, 1).toUtc());
|
||||
expect(range.$2.toUtc(), DateTime(2024, 10, 1).toUtc());
|
||||
});
|
||||
});
|
||||
|
||||
group('getSortedAlbumIds', () {
|
||||
|
|
@ -321,5 +435,51 @@ void main() {
|
|||
// album2 (Jan 1) should come before album1 (Jan 25)
|
||||
expect(resultEnd, [album2.id, album1.id]);
|
||||
});
|
||||
|
||||
test('ignores trashed assets when sorting albums by date', () async {
|
||||
// Album 1: one active asset at Jan 10, one trashed asset at Jan 1 (older)
|
||||
final album1 = await ctx.newRemoteAlbum(ownerId: userId);
|
||||
final trashedEarly = await ctx.newRemoteAsset(
|
||||
ownerId: userId,
|
||||
createdAt: DateTime(2024, 1, 1),
|
||||
deletedAt: DateTime(2025, 1, 1),
|
||||
);
|
||||
final active = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 10));
|
||||
await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: trashedEarly.id);
|
||||
await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: active.id);
|
||||
|
||||
// Album 2: one active asset at Jan 5 (earlier than album1's active asset)
|
||||
final album2 = await ctx.newRemoteAlbum(ownerId: userId);
|
||||
final active2 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 5));
|
||||
await ctx.newRemoteAlbumAsset(albumId: album2.id, assetId: active2.id);
|
||||
|
||||
final result = await sut.getSortedAlbumIds([album1.id, album2.id], aggregation: AssetDateAggregation.start);
|
||||
|
||||
// album2 (Jan 5) should come before album1 (Jan 10, ignoring trashed Jan 1)
|
||||
expect(result, [album2.id, album1.id]);
|
||||
});
|
||||
|
||||
test('ignores hidden and locked assets when sorting albums by date', () async {
|
||||
// Album 1: active asset at Jan 20, hidden asset at Jan 1 (should be ignored)
|
||||
final album1 = await ctx.newRemoteAlbum(ownerId: userId);
|
||||
final hiddenEarly = await ctx.newRemoteAsset(
|
||||
ownerId: userId,
|
||||
createdAt: DateTime(2024, 1, 1),
|
||||
visibility: AssetVisibility.hidden,
|
||||
);
|
||||
final activeAlbum1 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 20));
|
||||
await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: hiddenEarly.id);
|
||||
await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: activeAlbum1.id);
|
||||
|
||||
// Album 2: one active asset at Jan 10 (earlier than album1's visible start)
|
||||
final album2 = await ctx.newRemoteAlbum(ownerId: userId);
|
||||
final activeAlbum2 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 10));
|
||||
await ctx.newRemoteAlbumAsset(albumId: album2.id, assetId: activeAlbum2.id);
|
||||
|
||||
final result = await sut.getSortedAlbumIds([album1.id, album2.id], aggregation: AssetDateAggregation.start);
|
||||
|
||||
// album2 (Jan 10) before album1 (Jan 20, ignoring hidden Jan 1)
|
||||
expect(result, [album2.id, album1.id]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ class MediumRepositoryContext {
|
|||
String? stackId,
|
||||
String? thumbHash,
|
||||
String? libraryId,
|
||||
DateTime? localDateTime,
|
||||
}) async {
|
||||
id ??= TestUtils.uuid();
|
||||
createdAt ??= TestUtils.date();
|
||||
|
|
@ -128,25 +129,25 @@ class MediumRepositoryContext {
|
|||
.into(db.remoteAssetEntity)
|
||||
.insertReturning(
|
||||
RemoteAssetEntityCompanion(
|
||||
id: .new(id),
|
||||
name: .new('remote_$id.jpg'),
|
||||
checksum: .new(TestUtils.uuid(checksum)),
|
||||
type: .new(type ?? .image),
|
||||
createdAt: .new(createdAt),
|
||||
updatedAt: .new(TestUtils.date(updatedAt)),
|
||||
ownerId: .new(TestUtils.uuid(ownerId)),
|
||||
visibility: .new(visibility ?? .timeline),
|
||||
deletedAt: .new(deletedAt),
|
||||
durationMs: .new(durationMs ?? 0),
|
||||
width: .new(width ?? TestUtils.randInt(1000)),
|
||||
height: .new(height ?? TestUtils.randInt(1000)),
|
||||
isFavorite: .new(isFavorite ?? false),
|
||||
isEdited: .new(isEdited ?? false),
|
||||
livePhotoVideoId: .new(livePhotoVideoId),
|
||||
stackId: .new(stackId),
|
||||
localDateTime: .new(createdAt.toLocal()),
|
||||
thumbHash: .new(TestUtils.uuid(thumbHash)),
|
||||
libraryId: .new(TestUtils.uuid(libraryId)),
|
||||
id: Value(id),
|
||||
name: Value('remote_$id.jpg'),
|
||||
checksum: Value(TestUtils.uuid(checksum)),
|
||||
type: Value(type ?? AssetType.image),
|
||||
createdAt: Value(createdAt),
|
||||
updatedAt: Value(TestUtils.date(updatedAt)),
|
||||
ownerId: Value(TestUtils.uuid(ownerId)),
|
||||
visibility: Value(visibility ?? AssetVisibility.timeline),
|
||||
deletedAt: Value(deletedAt),
|
||||
durationMs: Value(durationMs ?? 0),
|
||||
width: Value(width ?? TestUtils.randInt(1000)),
|
||||
height: Value(height ?? TestUtils.randInt(1000)),
|
||||
isFavorite: Value(isFavorite ?? false),
|
||||
isEdited: Value(isEdited ?? false),
|
||||
livePhotoVideoId: Value(livePhotoVideoId),
|
||||
stackId: Value(stackId),
|
||||
localDateTime: Value(localDateTime ?? createdAt.toLocal()),
|
||||
thumbHash: Value(TestUtils.uuid(thumbHash)),
|
||||
libraryId: Value(TestUtils.uuid(libraryId)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue