feat: people management (#31620)

Co-authored-by: Jason Rasmussen <jason@rasm.me>
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
Daniel Dietzler
2026-09-28 21:29:15 +02:00
committed by GitHub
co-authored by Jason Rasmussen shenlong-tanwen
parent 0f179b9914
commit c12d5e6d35
67 changed files with 4812 additions and 582 deletions
@@ -74,8 +74,8 @@ class TimelineFactory {
TimelineService place(String place) => TimelineService(_timelineRepository.place(place, groupBy));
TimelineService person(String userId, String personId) =>
TimelineService(_timelineRepository.person(userId, personId, groupBy));
TimelineService person(List<String> userIds, String personId) =>
TimelineService(_timelineRepository.person(userIds, personId, groupBy));
TimelineService fromAssets(List<BaseAsset> assets, TimelineOrigin type) =>
TimelineService(_timelineRepository.fromAssets(assets, type));
@@ -366,10 +366,10 @@ class TimelineRepository extends DatabaseAccessor<Drift> with $TimelineRepositor
origin: TimelineOrigin.place,
);
TimelineQuery person(String userId, String personId, GroupAssetsBy groupBy) => (
bucketSource: () => _watchPersonBucket(userId, personId, groupBy: groupBy),
TimelineQuery person(List<String> userIds, String personId, GroupAssetsBy groupBy) => (
bucketSource: () => _watchPersonBucket(userIds, personId, groupBy: groupBy),
assetSource: (offset, count) =>
_getPersonBucketAssets(userId, personId, groupBy: groupBy, offset: offset, count: count),
_getPersonBucketAssets(userIds, personId, groupBy: groupBy, offset: offset, count: count),
origin: TimelineOrigin.person,
);
@@ -430,24 +430,30 @@ class TimelineRepository extends DatabaseAccessor<Drift> with $TimelineRepositor
return query.map((row) => row.readTable(_db.remoteAssetEntity).toDto()).get();
}
Stream<List<Bucket>> _watchPersonBucket(String userId, String personId, {GroupAssetsBy groupBy = GroupAssetsBy.day}) {
final idQuery = _db.assetFaceEntity.selectOnly()
Expression<bool> _personAssetFilter(List<String> userIds, String personId) {
final faceAssetIds = _db.assetFaceEntity.selectOnly()
..addColumns([_db.assetFaceEntity.assetId])
..where(
_db.assetFaceEntity.personId.equals(personId) &
_db.assetFaceEntity.isVisible.equals(true) &
_db.assetFaceEntity.deletedAt.isNull(),
);
final albumAssetIds = _db.remoteAlbumAssetEntity.selectOnly()..addColumns([_db.remoteAlbumAssetEntity.assetId]);
return _db.remoteAssetEntity.id.isInQuery(faceAssetIds) &
(_db.remoteAssetEntity.ownerId.isIn(userIds) | _db.remoteAssetEntity.id.isInQuery(albumAssetIds)) &
_db.remoteAssetEntity.deletedAt.isNull() &
_db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline);
}
Stream<List<Bucket>> _watchPersonBucket(
List<String> userIds,
String personId, {
GroupAssetsBy groupBy = GroupAssetsBy.day,
}) {
if (groupBy == GroupAssetsBy.none) {
final query = _db.remoteAssetEntity.selectOnly()
..addColumns([_db.remoteAssetEntity.id.count()])
..where(
_db.remoteAssetEntity.id.isInQuery(idQuery) &
_db.remoteAssetEntity.deletedAt.isNull() &
_db.remoteAssetEntity.ownerId.equals(userId) &
_db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline),
);
..where(_personAssetFilter(userIds, personId));
return query.map((row) {
final count = row.read(_db.remoteAssetEntity.id.count())!;
@@ -460,12 +466,7 @@ class TimelineRepository extends DatabaseAccessor<Drift> with $TimelineRepositor
final query = _db.remoteAssetEntity.selectOnly()
..addColumns([assetCountExp, dateExp])
..where(
_db.remoteAssetEntity.id.isInQuery(idQuery) &
_db.remoteAssetEntity.ownerId.equals(userId) &
_db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline) &
_db.remoteAssetEntity.deletedAt.isNull(),
)
..where(_personAssetFilter(userIds, personId))
..groupBy([dateExp])
..orderBy([OrderingTerm.desc(dateExp)]);
@@ -477,28 +478,14 @@ class TimelineRepository extends DatabaseAccessor<Drift> with $TimelineRepositor
}
Future<List<BaseAsset>> _getPersonBucketAssets(
String userId,
List<String> userIds,
String personId, {
required int offset,
required int count,
GroupAssetsBy groupBy = GroupAssetsBy.day,
}) {
final idQuery = _db.assetFaceEntity.selectOnly()
..addColumns([_db.assetFaceEntity.assetId])
..where(
_db.assetFaceEntity.personId.equals(personId) &
_db.assetFaceEntity.isVisible.equals(true) &
_db.assetFaceEntity.deletedAt.isNull(),
);
final query = _db.remoteAssetEntity.select()
..where(
(row) =>
row.id.isInQuery(idQuery) &
row.deletedAt.isNull() &
row.ownerId.equals(userId) &
row.visibility.equalsValue(AssetVisibility.timeline),
)
..where((_) => _personAssetFilter(userIds, personId))
..orderBy(_assetDateOrder(groupBy))
..limit(count, offset: offset);
@@ -82,7 +82,8 @@ class _PersonPageState extends ConsumerState<PersonPage> {
throw Exception('User must be logged in to view person timeline');
}
final timelineService = ref.watch(timelineFactoryProvider).person(user.id, _person.id);
final users = ref.watch(timelineUsersProvider).valueOrNull ?? [user.id];
final timelineService = ref.watch(timelineFactoryProvider).person(users, _person.id);
ref.onDispose(timelineService.dispose);
return timelineService;
}),
+1
View File
@@ -42,6 +42,7 @@ final Map<String, Map<String, Object?>> openApiPatches = {
'ServerFeaturesDto': {'ocr': false, 'realtimeTranscoding': false},
'SearchAssetResponseDto': {'nextCursor': null},
'MemoriesResponse': {'duration': 5, 'sidebarWeb': false},
'PersonResponseDto': {'otherPeople': const [], 'sharedBy': const [], 'sharedWith': const []},
'WorkflowResponseDto': {'logging': false},
};
@@ -113,7 +113,7 @@ void main() {
await ctx.newFace(assetId: asset.id, personId: person.id);
await ctx.newFace(assetId: asset.id, personId: person.id);
final query = sut.person(user.id, person.id, .day);
final query = sut.person([user.id], person.id, .day);
final buckets = await query.bucketSource().first;
expect(buckets, hasLength(1));
@@ -140,7 +140,7 @@ void main() {
await ctx.newFace(assetId: shiftedLater.id, personId: person.id);
await ctx.newFace(assetId: shiftedEarlier.id, personId: person.id);
final query = sut.person(user.id, person.id, .day);
final query = sut.person([user.id], person.id, .day);
final buckets = await query.bucketSource().first;
expect(buckets, hasLength(2));