feat(mobile): drift video page (#19784)

* feat(mobile): drift video page

* filter motional parts

* remove status indicator join

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Daimolean 2025-07-08 02:51:49 +08:00 committed by GitHub
parent d149d6fa3f
commit 683af67344
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 122 additions and 0 deletions

View File

@ -83,6 +83,13 @@ class TimelineFactory {
groupBy: groupBy, groupBy: groupBy,
), ),
); );
TimelineService video(String userId) => TimelineService(
assetSource: (offset, count) => _timelineRepository
.getVideoBucketAssets(userId, offset: offset, count: count),
bucketSource: () =>
_timelineRepository.watchVideoBucket(userId, groupBy: groupBy),
);
} }
class TimelineService { class TimelineService {

View File

@ -413,6 +413,62 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
return query.map((row) => row.toDto()).get(); return query.map((row) => row.toDto()).get();
} }
Stream<List<Bucket>> watchVideoBucket(
String userId, {
GroupAssetsBy groupBy = GroupAssetsBy.day,
}) {
if (groupBy == GroupAssetsBy.none) {
return _db.remoteAssetEntity
.count(
where: (row) =>
row.type.equalsValue(AssetType.video) &
row.visibility.equalsValue(AssetVisibility.timeline) &
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.ownerId.equals(userId) &
_db.remoteAssetEntity.type.equalsValue(AssetType.video) &
_db.remoteAssetEntity.visibility
.equalsValue(AssetVisibility.timeline),
)
..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<List<BaseAsset>> getVideoBucketAssets(
String userId, {
required int offset,
required int count,
}) {
final query = _db.remoteAssetEntity.select()
..where(
(row) =>
_db.remoteAssetEntity.type.equalsValue(AssetType.video) &
_db.remoteAssetEntity.visibility
.equalsValue(AssetVisibility.timeline) &
_db.remoteAssetEntity.ownerId.equals(userId),
)
..orderBy([(row) => OrderingTerm.desc(row.createdAt)])
..limit(count, offset: offset);
return query.map((row) => row.toDto()).get();
}
} }
extension on Expression<DateTime> { extension on Expression<DateTime> {

View File

@ -0,0 +1,33 @@
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 DriftVideoPage extends StatelessWidget {
const DriftVideoPage({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 video');
}
final timelineService =
ref.watch(timelineFactoryProvider).video(user.id);
ref.onDispose(timelineService.dispose);
return timelineService;
},
),
],
child: const Timeline(),
);
}
}

View File

@ -119,6 +119,11 @@ final _features = [
icon: Icons.lock_outline_rounded, icon: Icons.lock_outline_rounded,
onTap: (ctx, _) => ctx.pushRoute(const DriftLockedFolderRoute()), onTap: (ctx, _) => ctx.pushRoute(const DriftLockedFolderRoute()),
), ),
_Feature(
name: 'Video',
icon: Icons.video_collection_outlined,
onTap: (ctx, _) => ctx.pushRoute(const DriftVideoRoute()),
),
]; ];
@RoutePage() @RoutePage()

View File

@ -67,6 +67,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/search/search.page.dart';
import 'package:immich_mobile/pages/share_intent/share_intent.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_favorite.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_trash.page.dart';
import 'package:immich_mobile/presentation/pages/dev/drift_archive.page.dart'; import 'package:immich_mobile/presentation/pages/dev/drift_archive.page.dart';
import 'package:immich_mobile/presentation/pages/dev/drift_locked_folder.page.dart'; import 'package:immich_mobile/presentation/pages/dev/drift_locked_folder.page.dart';
@ -412,6 +413,10 @@ class AppRouter extends RootStackRouter {
page: DriftLockedFolderRoute.page, page: DriftLockedFolderRoute.page,
guards: [_authGuard, _duplicateGuard], guards: [_authGuard, _duplicateGuard],
), ),
AutoRoute(
page: DriftVideoRoute.page,
guards: [_authGuard, _duplicateGuard],
),
// required to handle all deeplinks in deep_link.service.dart // required to handle all deeplinks in deep_link.service.dart
// auto_route_library#1722 // auto_route_library#1722
RedirectRoute(path: '*', redirectTo: '/'), RedirectRoute(path: '*', redirectTo: '/'),

View File

@ -734,6 +734,22 @@ class DriftTrashRoute extends PageRouteInfo<void> {
); );
} }
/// generated route for
/// [DriftVideoPage]
class DriftVideoRoute extends PageRouteInfo<void> {
const DriftVideoRoute({List<PageRouteInfo>? children})
: super(DriftVideoRoute.name, initialChildren: children);
static const String name = 'DriftVideoRoute';
static PageInfo page = PageInfo(
name,
builder: (data) {
return const DriftVideoPage();
},
);
}
/// generated route for /// generated route for
/// [EditImagePage] /// [EditImagePage]
class EditImageRoute extends PageRouteInfo<EditImageRouteArgs> { class EditImageRoute extends PageRouteInfo<EditImageRouteArgs> {