feat(mobile): drift trash page (#19745)

* feat(mobile): drift trash page

* feat: include local indicator

* remove join in bucket

* remove indicator join

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Daimolean 2025-07-07 12:05:40 +08:00 committed by GitHub
parent 7e35e6985e
commit 5579554532
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 114 additions and 0 deletions

View File

@ -54,6 +54,13 @@ class TimelineFactory {
_timelineRepository.watchRemoteBucket(albumId, groupBy: groupBy),
);
TimelineService trash(String userId) => TimelineService(
assetSource: (offset, count) => _timelineRepository
.getTrashBucketAssets(userId, offset: offset, count: count),
bucketSource: () =>
_timelineRepository.watchTrashBucket(userId, groupBy: groupBy),
);
TimelineService archive(String userId) => TimelineService(
assetSource: (offset, count) => _timelineRepository
.getArchiveBucketAssets(userId, offset: offset, count: count),

View File

@ -214,6 +214,54 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
.get();
}
Stream<List<Bucket>> watchTrashBucket(
String userId, {
GroupAssetsBy groupBy = GroupAssetsBy.day,
}) {
if (groupBy == GroupAssetsBy.none) {
return _db.remoteAssetEntity
.count(
where: (row) =>
row.deletedAt.isNotNull() & 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.deletedAt.isNotNull(),
)
..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>> getTrashBucketAssets(
String userId, {
required int offset,
required int count,
}) {
final query = _db.remoteAssetEntity.select()
..where(
(row) => row.deletedAt.isNotNull() & row.ownerId.equals(userId),
)
..orderBy([(row) => OrderingTerm.desc(row.createdAt)])
..limit(count, offset: offset);
return query.map((row) => row.toDto()).get();
}
Stream<List<Bucket>> watchArchiveBucket(
String userId, {
GroupAssetsBy groupBy = GroupAssetsBy.day,

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 DriftTrashPage extends StatelessWidget {
const DriftTrashPage({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 trash');
}
final timelineService =
ref.watch(timelineFactoryProvider).trash(user.id);
ref.onDispose(timelineService.dispose);
return timelineService;
},
),
],
child: const Timeline(),
);
}
}

View File

@ -99,6 +99,11 @@ final _features = [
icon: Icons.timeline_rounded,
onTap: (ctx, _) => ctx.pushRoute(const TabShellRoute()),
),
_Feature(
name: 'Trash',
icon: Icons.delete_outline_rounded,
onTap: (ctx, _) => ctx.pushRoute(const DriftTrashRoute()),
),
_Feature(
name: 'Archive',
icon: Icons.archive_outlined,

View File

@ -66,6 +66,7 @@ import 'package:immich_mobile/pages/search/person_result.page.dart';
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_trash.page.dart';
import 'package:immich_mobile/presentation/pages/dev/drift_archive.page.dart';
import 'package:immich_mobile/presentation/pages/dev/feat_in_development.page.dart';
import 'package:immich_mobile/presentation/pages/dev/local_timeline.page.dart';
@ -393,6 +394,10 @@ class AppRouter extends RootStackRouter {
page: DriftMemoryRoute.page,
guards: [_authGuard, _duplicateGuard],
),
AutoRoute(
page: DriftTrashRoute.page,
guards: [_authGuard, _duplicateGuard],
),
AutoRoute(
page: DriftArchiveRoute.page,
guards: [_authGuard, _duplicateGuard],

View File

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