fix(mobile): show memories with no showAt/hideAt in the timeline lane (#29158)

This commit is contained in:
Santo Shakil
2026-06-17 00:46:52 +06:00
committed by GitHub
parent 83b4dc1733
commit 12b7cd066b
3 changed files with 146 additions and 2 deletions

View File

@@ -25,8 +25,8 @@ class DriftMemoryRepository extends DriftDatabaseRepository {
])
..where(_db.memoryEntity.ownerId.equals(ownerId))
..where(_db.memoryEntity.deletedAt.isNull())
..where(_db.memoryEntity.showAt.isSmallerOrEqualValue(localUtc))
..where(_db.memoryEntity.hideAt.isBiggerOrEqualValue(localUtc))
..where(_db.memoryEntity.showAt.isNull() | _db.memoryEntity.showAt.isSmallerOrEqualValue(localUtc))
..where(_db.memoryEntity.hideAt.isNull() | _db.memoryEntity.hideAt.isBiggerOrEqualValue(localUtc))
..orderBy([OrderingTerm.desc(_db.memoryEntity.memoryAt), OrderingTerm.asc(_db.remoteAssetEntity.createdAt)]);
final rows = await query.get();

View File

@@ -0,0 +1,108 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:immich_mobile/infrastructure/repositories/memory.repository.dart';
import '../repository_context.dart';
void main() {
late MediumRepositoryContext ctx;
late DriftMemoryRepository sut;
setUp(() {
ctx = MediumRepositoryContext();
sut = DriftMemoryRepository(ctx.db);
});
tearDown(() async {
await ctx.dispose();
});
group('getAll', () {
// #24745: memories created via the API have no showAt/hideAt. before the fix
// the window filter (show_at <= now AND hide_at >= now) drops them because a
// NULL comparison is never true.
test('includes a memory with null showAt/hideAt', () async {
final user = await ctx.newUser();
final asset = await ctx.newRemoteAsset(ownerId: user.id);
final memory = await ctx.newMemory(ownerId: user.id);
await ctx.newMemoryAsset(memoryId: memory.id, assetId: asset.id);
final result = await sut.getAll(user.id);
expect(result, hasLength(1));
expect(result.first.id, memory.id);
expect(result.first.assets.single.id, asset.id);
});
test('includes a memory whose window covers today', () async {
final user = await ctx.newUser();
final asset = await ctx.newRemoteAsset(ownerId: user.id);
final now = DateTime.now().toUtc();
final memory = await ctx.newMemory(
ownerId: user.id,
showAt: now.subtract(const Duration(days: 10)),
hideAt: now.add(const Duration(days: 10)),
);
await ctx.newMemoryAsset(memoryId: memory.id, assetId: asset.id);
final result = await sut.getAll(user.id);
expect(result.map((m) => m.id), [memory.id]);
});
test('excludes a memory whose hideAt is in the past', () async {
final user = await ctx.newUser();
final asset = await ctx.newRemoteAsset(ownerId: user.id);
final now = DateTime.now().toUtc();
final memory = await ctx.newMemory(
ownerId: user.id,
showAt: now.subtract(const Duration(days: 20)),
hideAt: now.subtract(const Duration(days: 10)),
);
await ctx.newMemoryAsset(memoryId: memory.id, assetId: asset.id);
final result = await sut.getAll(user.id);
expect(result, isEmpty);
});
test('excludes a memory whose showAt is in the future', () async {
final user = await ctx.newUser();
final asset = await ctx.newRemoteAsset(ownerId: user.id);
final now = DateTime.now().toUtc();
final memory = await ctx.newMemory(
ownerId: user.id,
showAt: now.add(const Duration(days: 10)),
hideAt: now.add(const Duration(days: 20)),
);
await ctx.newMemoryAsset(memoryId: memory.id, assetId: asset.id);
final result = await sut.getAll(user.id);
expect(result, isEmpty);
});
test('includes a memory with showAt in the past and null hideAt', () async {
final user = await ctx.newUser();
final asset = await ctx.newRemoteAsset(ownerId: user.id);
final now = DateTime.now().toUtc();
final memory = await ctx.newMemory(ownerId: user.id, showAt: now.subtract(const Duration(days: 10)));
await ctx.newMemoryAsset(memoryId: memory.id, assetId: asset.id);
final result = await sut.getAll(user.id);
expect(result.map((m) => m.id), [memory.id]);
});
test('excludes a memory with null showAt and hideAt in the past', () async {
final user = await ctx.newUser();
final asset = await ctx.newRemoteAsset(ownerId: user.id);
final now = DateTime.now().toUtc();
final memory = await ctx.newMemory(ownerId: user.id, hideAt: now.subtract(const Duration(days: 10)));
await ctx.newMemoryAsset(memoryId: memory.id, assetId: asset.id);
final result = await sut.getAll(user.id);
expect(result, isEmpty);
});
});
}

View File

@@ -3,11 +3,14 @@ import 'package:drift/native.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/memory.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart';
@@ -309,4 +312,37 @@ class MediumRepositoryContext {
Future<void> newLocalAlbumAsset({required String albumId, required String assetId}) => db
.into(db.localAlbumAssetEntity)
.insert(LocalAlbumAssetEntityCompanion(albumId: .new(albumId), assetId: .new(assetId)));
Future<MemoryEntityData> newMemory({
String? id,
String? ownerId,
MemoryTypeEnum? type,
int? year,
DateTime? memoryAt,
DateTime? showAt,
DateTime? hideAt,
DateTime? deletedAt,
bool? isSaved,
}) async {
id ??= TestUtils.uuid();
return db
.into(db.memoryEntity)
.insertReturning(
MemoryEntityCompanion(
id: .new(id),
ownerId: .new(TestUtils.uuid(ownerId)),
type: .new(type ?? MemoryTypeEnum.onThisDay),
data: .new(MemoryData(year: year ?? 2020).toJson()),
isSaved: .new(isSaved ?? false),
memoryAt: .new(TestUtils.date(memoryAt)),
showAt: .new(showAt),
hideAt: .new(hideAt),
deletedAt: .new(deletedAt),
),
);
}
Future<void> newMemoryAsset({required String memoryId, required String assetId}) => db
.into(db.memoryAssetEntity)
.insert(MemoryAssetEntityCompanion(memoryId: .new(memoryId), assetId: .new(assetId)));
}