diff --git a/mobile/lib/domain/services/timeline.service.dart b/mobile/lib/domain/services/timeline.service.dart index eb332657ff..02ed552e0e 100644 --- a/mobile/lib/domain/services/timeline.service.dart +++ b/mobile/lib/domain/services/timeline.service.dart @@ -58,11 +58,11 @@ class TimelineFactory { ), ); - TimelineService remoteAssets(List timelineUsers) => TimelineService( + TimelineService remoteAssets(String ownerId) => TimelineService( assetSource: (offset, count) => _timelineRepository - .getRemoteBucketAssets(timelineUsers, offset: offset, count: count), + .getRemoteBucketAssets(ownerId, offset: offset, count: count), bucketSource: () => _timelineRepository.watchRemoteBucket( - timelineUsers, + ownerId, groupBy: GroupAssetsBy.month, ), ); diff --git a/mobile/lib/infrastructure/repositories/remote_album.repository.dart b/mobile/lib/infrastructure/repositories/remote_album.repository.dart index 0e49b45bc5..b1b73b4cdc 100644 --- a/mobile/lib/infrastructure/repositories/remote_album.repository.dart +++ b/mobile/lib/infrastructure/repositories/remote_album.repository.dart @@ -18,13 +18,21 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { _db.remoteAlbumAssetEntity.albumId.equalsExp(_db.remoteAlbumEntity.id), useColumns: false, ), + leftOuterJoin( + _db.remoteAssetEntity, + _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId), + useColumns: false, + ), leftOuterJoin( _db.userEntity, _db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId), + useColumns: false, ), ]); query + ..where(_db.remoteAssetEntity.deletedAt.isNull()) ..addColumns([assetCount]) + ..addColumns([_db.userEntity.name]) ..groupBy([_db.remoteAlbumEntity.id]); if (sortBy.isNotEmpty) { @@ -43,7 +51,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { .map( (row) => row.readTable(_db.remoteAlbumEntity).toDto( assetCount: row.read(assetCount) ?? 0, - ownerName: row.readTable(_db.userEntity).name, + ownerName: row.read(_db.userEntity.name)!, ), ) .get(); diff --git a/mobile/lib/infrastructure/repositories/timeline.repository.dart b/mobile/lib/infrastructure/repositories/timeline.repository.dart index a13109068c..f67a154398 100644 --- a/mobile/lib/infrastructure/repositories/timeline.repository.dart +++ b/mobile/lib/infrastructure/repositories/timeline.repository.dart @@ -124,6 +124,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { innerJoin( _db.localAlbumAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id), + useColumns: false, ), ]) ..where(_db.localAlbumAssetEntity.albumId.equals(albumId)) @@ -147,6 +148,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { innerJoin( _db.localAlbumAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id), + useColumns: false, ), ], ) @@ -179,9 +181,13 @@ class DriftTimelineRepository extends DriftDatabaseRepository { _db.remoteAlbumAssetEntity, _db.remoteAlbumAssetEntity.assetId .equalsExp(_db.remoteAssetEntity.id), + useColumns: false, ), ]) - ..where(_db.remoteAlbumAssetEntity.albumId.equals(albumId)) + ..where( + _db.remoteAssetEntity.deletedAt.isNull() & + _db.remoteAlbumAssetEntity.albumId.equals(albumId), + ) ..groupBy([dateExp]) ..orderBy([OrderingTerm.desc(dateExp)]); @@ -203,10 +209,14 @@ class DriftTimelineRepository extends DriftDatabaseRepository { _db.remoteAlbumAssetEntity, _db.remoteAlbumAssetEntity.assetId .equalsExp(_db.remoteAssetEntity.id), + useColumns: false, ), ], ) - ..where(_db.remoteAlbumAssetEntity.albumId.equals(albumId)) + ..where( + _db.remoteAssetEntity.deletedAt.isNull() & + _db.remoteAlbumAssetEntity.albumId.equals(albumId), + ) ..orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)]) ..limit(count, offset: offset); return query @@ -214,15 +224,17 @@ class DriftTimelineRepository extends DriftDatabaseRepository { .get(); } - Stream> watchFavoriteBucket( - String userId, { + Stream> watchRemoteBucket( + String ownerId, { GroupAssetsBy groupBy = GroupAssetsBy.day, }) { if (groupBy == GroupAssetsBy.none) { return _db.remoteAssetEntity .count( where: (row) => - row.isFavorite.equals(true) & row.ownerId.equals(userId), + row.deletedAt.isNull() & + row.visibility.equalsValue(AssetVisibility.timeline) & + row.ownerId.equals(ownerId), ) .map(_generateBuckets) .watchSingle(); @@ -234,7 +246,63 @@ class DriftTimelineRepository extends DriftDatabaseRepository { final query = _db.remoteAssetEntity.selectOnly() ..addColumns([assetCountExp, dateExp]) ..where( - _db.remoteAssetEntity.ownerId.equals(userId) & + _db.remoteAssetEntity.deletedAt.isNull() & + _db.remoteAssetEntity.visibility + .equalsValue(AssetVisibility.timeline) & + _db.remoteAssetEntity.ownerId.equals(ownerId), + ) + ..groupBy([dateExp]) + ..orderBy([OrderingTerm.desc(dateExp)]); + + return query.map((row) { + final timeline = row.read(dateExp)!.dateFmt(groupBy); + final assetCount = row.read(assetCountExp)!; + return TimeBucket(date: timeline, assetCount: assetCount); + }).watch(); + } + + Future> getRemoteBucketAssets( + String ownerId, { + required int offset, + required int count, + }) { + final query = _db.remoteAssetEntity.select() + ..where( + (row) => + row.deletedAt.isNull() & + row.visibility.equalsValue(AssetVisibility.timeline) & + row.ownerId.equals(ownerId), + ) + ..orderBy([(row) => OrderingTerm.desc(row.createdAt)]) + ..limit(count, offset: offset); + + return query.map((row) => row.toDto()).get(); + } + + Stream> watchFavoriteBucket( + String userId, { + GroupAssetsBy groupBy = GroupAssetsBy.day, + }) { + if (groupBy == GroupAssetsBy.none) { + return _db.remoteAssetEntity + .count( + where: (row) => + row.deletedAt.isNull() & + row.isFavorite.equals(true) & + row.ownerId.equals(userId), + ) + .map(_generateBuckets) + .watchSingle(); + } + + final assetCountExp = _db.remoteAssetEntity.id.count(); + final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy); + + final query = _db.remoteAssetEntity.selectOnly() + ..addColumns([assetCountExp, dateExp]) + ..where( + _db.remoteAssetEntity.deletedAt.isNull() & + _db.remoteAssetEntity.ownerId.equals(userId) & _db.remoteAssetEntity.isFavorite.equals(true), ) ..groupBy([dateExp]) @@ -254,7 +322,10 @@ class DriftTimelineRepository extends DriftDatabaseRepository { }) { final query = _db.remoteAssetEntity.select() ..where( - (row) => row.isFavorite.equals(true) & row.ownerId.equals(userId), + (row) => + row.deletedAt.isNull() & + row.isFavorite.equals(true) & + row.ownerId.equals(userId), ) ..orderBy([(row) => OrderingTerm.desc(row.createdAt)]) ..limit(count, offset: offset); @@ -318,6 +389,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { return _db.remoteAssetEntity .count( where: (row) => + row.deletedAt.isNull() & row.visibility.equalsValue(AssetVisibility.archive) & row.ownerId.equals(userId), ) @@ -331,7 +403,8 @@ class DriftTimelineRepository extends DriftDatabaseRepository { final query = _db.remoteAssetEntity.selectOnly() ..addColumns([assetCountExp, dateExp]) ..where( - _db.remoteAssetEntity.ownerId.equals(userId) & + _db.remoteAssetEntity.deletedAt.isNull() & + _db.remoteAssetEntity.ownerId.equals(userId) & _db.remoteAssetEntity.visibility .equalsValue(AssetVisibility.archive), ) @@ -353,6 +426,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { final query = _db.remoteAssetEntity.select() ..where( (row) => + row.deletedAt.isNull() & row.ownerId.equals(userId) & row.visibility.equalsValue(AssetVisibility.archive), ) @@ -370,6 +444,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { return _db.remoteAssetEntity .count( where: (row) => + row.deletedAt.isNull() & row.visibility.equalsValue(AssetVisibility.locked) & row.ownerId.equals(userId), ) @@ -383,7 +458,8 @@ class DriftTimelineRepository extends DriftDatabaseRepository { final query = _db.remoteAssetEntity.selectOnly() ..addColumns([assetCountExp, dateExp]) ..where( - _db.remoteAssetEntity.ownerId.equals(userId) & + _db.remoteAssetEntity.deletedAt.isNull() & + _db.remoteAssetEntity.ownerId.equals(userId) & _db.remoteAssetEntity.visibility .equalsValue(AssetVisibility.locked), ) @@ -405,6 +481,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { final query = _db.remoteAssetEntity.select() ..where( (row) => + row.deletedAt.isNull() & row.visibility.equalsValue(AssetVisibility.locked) & row.ownerId.equals(userId), ) @@ -422,6 +499,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { return _db.remoteAssetEntity .count( where: (row) => + row.deletedAt.isNull() & row.type.equalsValue(AssetType.video) & row.visibility.equalsValue(AssetVisibility.timeline) & row.ownerId.equals(userId), @@ -436,7 +514,8 @@ class DriftTimelineRepository extends DriftDatabaseRepository { final query = _db.remoteAssetEntity.selectOnly() ..addColumns([assetCountExp, dateExp]) ..where( - _db.remoteAssetEntity.ownerId.equals(userId) & + _db.remoteAssetEntity.deletedAt.isNull() & + _db.remoteAssetEntity.ownerId.equals(userId) & _db.remoteAssetEntity.type.equalsValue(AssetType.video) & _db.remoteAssetEntity.visibility .equalsValue(AssetVisibility.timeline), @@ -459,71 +538,16 @@ class DriftTimelineRepository extends DriftDatabaseRepository { final query = _db.remoteAssetEntity.select() ..where( (row) => - _db.remoteAssetEntity.type.equalsValue(AssetType.video) & - _db.remoteAssetEntity.visibility - .equalsValue(AssetVisibility.timeline) & - _db.remoteAssetEntity.ownerId.equals(userId), + row.deletedAt.isNull() & + row.type.equalsValue(AssetType.video) & + row.visibility.equalsValue(AssetVisibility.timeline) & + row.ownerId.equals(userId), ) ..orderBy([(row) => OrderingTerm.desc(row.createdAt)]) ..limit(count, offset: offset); return query.map((row) => row.toDto()).get(); } - - Stream> watchRemoteBucket( - List userIds, { - GroupAssetsBy groupBy = GroupAssetsBy.day, - }) { - if (groupBy == GroupAssetsBy.none) { - return _db.remoteAssetEntity - .count( - where: (row) => - row.deletedAt.isNull() & - row.visibility.equalsValue(AssetVisibility.timeline) & - row.ownerId.isIn(userIds), - ) - .map(_generateBuckets) - .watchSingle(); - } - - final assetCountExp = _db.remoteAssetEntity.id.count(); - final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy); - - final query = _db.remoteAssetEntity.selectOnly() - ..addColumns([assetCountExp, dateExp]) - ..where( - _db.remoteAssetEntity.deletedAt.isNull() & - _db.remoteAssetEntity.visibility - .equalsValue(AssetVisibility.timeline) & - _db.remoteAssetEntity.ownerId.isIn(userIds), - ) - ..groupBy([dateExp]) - ..orderBy([OrderingTerm.desc(dateExp)]); - - return query.map((row) { - final timeline = row.read(dateExp)!.dateFmt(groupBy); - final assetCount = row.read(assetCountExp)!; - return TimeBucket(date: timeline, assetCount: assetCount); - }).watch(); - } - - Future> getRemoteBucketAssets( - List userIds, { - required int offset, - required int count, - }) { - final query = _db.remoteAssetEntity.select() - ..where( - (row) => - row.deletedAt.isNull() & - row.visibility.equalsValue(AssetVisibility.timeline) & - row.ownerId.isIn(userIds), - ) - ..orderBy([(t) => OrderingTerm.desc(t.createdAt)]) - ..limit(count, offset: offset); - - return query.map((row) => row.toDto()).get(); - } } extension on Expression { diff --git a/mobile/lib/presentation/pages/dev/drift_recently_taken.page.dart b/mobile/lib/presentation/pages/dev/drift_recently_taken.page.dart new file mode 100644 index 0000000000..e2972fad56 --- /dev/null +++ b/mobile/lib/presentation/pages/dev/drift_recently_taken.page.dart @@ -0,0 +1,35 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/widgets.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; + +@RoutePage() +class DriftRecentlyTakenPage extends StatelessWidget { + const DriftRecentlyTakenPage({super.key}); + + @override + Widget build(BuildContext context) { + return ProviderScope( + overrides: [ + timelineServiceProvider.overrideWith( + (ref) { + final user = ref.watch(currentUserProvider); + if (user == null) { + throw Exception( + 'User must be logged in to access recently taken', + ); + } + + final timelineService = + ref.watch(timelineFactoryProvider).remoteAssets(user.id); + ref.onDispose(timelineService.dispose); + return timelineService; + }, + ), + ], + child: const Timeline(), + ); + } +} diff --git a/mobile/lib/presentation/pages/dev/feat_in_development.page.dart b/mobile/lib/presentation/pages/dev/feat_in_development.page.dart index a4343b720f..566c10c70a 100644 --- a/mobile/lib/presentation/pages/dev/feat_in_development.page.dart +++ b/mobile/lib/presentation/pages/dev/feat_in_development.page.dart @@ -132,6 +132,11 @@ final _features = [ icon: Icons.video_collection_outlined, onTap: (ctx, _) => ctx.pushRoute(const DriftVideoRoute()), ), + _Feature( + name: 'Recently Taken', + icon: Icons.schedule_outlined, + onTap: (ctx, _) => ctx.pushRoute(const DriftRecentlyTakenRoute()), + ), ]; @RoutePage() diff --git a/mobile/lib/presentation/pages/drift_asset_selection_timeline.page.dart b/mobile/lib/presentation/pages/drift_asset_selection_timeline.page.dart index b7cb033828..df6211c338 100644 --- a/mobile/lib/presentation/pages/drift_asset_selection_timeline.page.dart +++ b/mobile/lib/presentation/pages/drift_asset_selection_timeline.page.dart @@ -5,6 +5,7 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; @RoutePage() class DriftAssetSelectionTimelinePage extends ConsumerWidget { @@ -29,10 +30,15 @@ class DriftAssetSelectionTimelinePage extends ConsumerWidget { ), timelineServiceProvider.overrideWith( (ref) { - final timelineUsers = - ref.watch(timelineUsersProvider).valueOrNull ?? []; + final user = ref.watch(currentUserProvider); + if (user == null) { + throw Exception( + 'User must be logged in to access recently taken', + ); + } + final timelineService = - ref.watch(timelineFactoryProvider).remoteAssets(timelineUsers); + ref.watch(timelineFactoryProvider).remoteAssets(user.id); ref.onDispose(timelineService.dispose); return timelineService; }, diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index 4bf287c08a..0de001ea47 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -68,6 +68,7 @@ import 'package:immich_mobile/pages/search/recently_taken.page.dart'; import 'package:immich_mobile/pages/search/search.page.dart'; import 'package:immich_mobile/pages/share_intent/share_intent.page.dart'; import 'package:immich_mobile/presentation/pages/dev/drift_favorite.page.dart'; +import 'package:immich_mobile/presentation/pages/dev/drift_recently_taken.page.dart'; import 'package:immich_mobile/presentation/pages/dev/drift_video.page.dart'; import 'package:immich_mobile/presentation/pages/dev/drift_trash.page.dart'; import 'package:immich_mobile/presentation/pages/dev/drift_archive.page.dart'; @@ -428,7 +429,10 @@ class AppRouter extends RootStackRouter { page: DriftAssetSelectionTimelineRoute.page, guards: [_authGuard, _duplicateGuard], ), - + AutoRoute( + page: DriftRecentlyTakenRoute.page, + guards: [_authGuard, _duplicateGuard], + ), // required to handle all deeplinks in deep_link.service.dart // auto_route_library#1722 RedirectRoute(path: '*', redirectTo: '/'), diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart index f752c2dc8a..1ddba4e596 100644 --- a/mobile/lib/routing/router.gr.dart +++ b/mobile/lib/routing/router.gr.dart @@ -783,6 +783,22 @@ class DriftMemoryRouteArgs { } } +/// generated route for +/// [DriftRecentlyTakenPage] +class DriftRecentlyTakenRoute extends PageRouteInfo { + const DriftRecentlyTakenRoute({List? children}) + : super(DriftRecentlyTakenRoute.name, initialChildren: children); + + static const String name = 'DriftRecentlyTakenRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const DriftRecentlyTakenPage(); + }, + ); +} + /// generated route for /// [DriftTrashPage] class DriftTrashRoute extends PageRouteInfo {