Compare commits

...

2 Commits

Author SHA1 Message Date
Alex 34777a811b Merge branch 'main' into plan-local-image-display 2026-05-09 07:49:21 -05:00
alextran1502 13446022d9 feat(mobile): show local gallery in guest mode before login 2026-05-07 15:21:20 +00:00
7 changed files with 94 additions and 2 deletions
@@ -56,6 +56,8 @@ class TimelineFactory {
TimelineService localAlbum({required String albumId}) =>
TimelineService(_timelineRepository.localAlbum(albumId, groupBy));
TimelineService allLocal() => TimelineService(_timelineRepository.allLocal(groupBy));
TimelineService remoteAlbum({required String albumId}) =>
TimelineService(_timelineRepository.remoteAlbum(albumId, groupBy));
@@ -118,6 +118,40 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
origin: TimelineOrigin.localAlbum,
);
TimelineQuery allLocal(GroupAssetsBy groupBy) => (
bucketSource: () => _watchAllLocalBucket(groupBy: groupBy),
assetSource: (offset, count) => _getAllLocalAssets(offset: offset, count: count),
origin: TimelineOrigin.localAlbum,
);
Stream<List<Bucket>> _watchAllLocalBucket({GroupAssetsBy groupBy = GroupAssetsBy.day}) {
if (groupBy == GroupAssetsBy.none) {
return _db.localAssetEntity.count().map(_generateBuckets).watchSingle();
}
final assetCountExp = _db.localAssetEntity.id.count();
final dateExp = _db.localAssetEntity.createdAt.dateFmt(groupBy, toLocal: true);
final query = _db.localAssetEntity.selectOnly()
..addColumns([assetCountExp, dateExp])
..groupBy([dateExp])
..orderBy([OrderingTerm.desc(dateExp)]);
return query.map((row) {
final timeline = row.read(dateExp)!.truncateDate(groupBy);
final assetCount = row.read(assetCountExp)!;
return TimeBucket(date: timeline, assetCount: assetCount);
}).watch();
}
Future<List<BaseAsset>> _getAllLocalAssets({required int offset, required int count}) {
final query = _db.localAssetEntity.select()
..orderBy([(row) => OrderingTerm.desc(row.createdAt)])
..limit(count, offset: offset);
return query.map((row) => row.toDto()).get();
}
Stream<List<Bucket>> _watchLocalAlbumBucket(String albumId, {GroupAssetsBy groupBy = GroupAssetsBy.day}) {
if (groupBy == GroupAssetsBy.none) {
return _db.localAlbumAssetEntity
@@ -355,9 +355,9 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
),
);
} else {
log.severe('Missing crucial offline login info - Logging out completely');
log.info('No stored credentials - showing local gallery in guest mode');
unawaited(ref.read(authProvider.notifier).logout());
unawaited(context.replaceRoute(const LoginRoute()));
unawaited(context.replaceRoute(const GuestGalleryRoute()));
return;
}
@@ -0,0 +1,32 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.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/routing/router.dart';
@RoutePage()
class GuestGalleryPage extends StatelessWidget {
const GuestGalleryPage({super.key});
@override
Widget build(BuildContext context) {
return ProviderScope(
overrides: [timelineServiceProvider.overrideWith((ref) => ref.watch(localOnlyTimelineServiceProvider))],
child: Timeline(
appBar: SliverAppBar(
title: const Text('On this device'),
floating: true,
actions: [
TextButton.icon(
onPressed: () => context.replaceRoute(const LoginRoute()),
icon: const Icon(Icons.login),
label: const Text('Sign in'),
),
],
),
showStorageIndicator: false,
),
);
}
}
@@ -26,6 +26,12 @@ final timelineServiceProvider = Provider<TimelineService>(
dependencies: [],
);
final localOnlyTimelineServiceProvider = Provider<TimelineService>((ref) {
final timelineService = ref.watch(timelineFactoryProvider).allLocal();
ref.onDispose(timelineService.dispose);
return timelineService;
});
final timelineFactoryProvider = Provider<TimelineFactory>(
(ref) => TimelineFactory(
timelineRepository: ref.watch(timelineRepositoryProvider),
+2
View File
@@ -63,6 +63,7 @@ import 'package:immich_mobile/presentation/pages/drift_trash.page.dart';
import 'package:immich_mobile/presentation/pages/drift_user_selection.page.dart';
import 'package:immich_mobile/presentation/pages/drift_video.page.dart';
import 'package:immich_mobile/presentation/pages/edit/drift_edit.page.dart';
import 'package:immich_mobile/presentation/pages/guest_gallery.page.dart';
import 'package:immich_mobile/presentation/pages/local_timeline.page.dart';
import 'package:immich_mobile/presentation/pages/profile/profile_picture_crop.page.dart';
import 'package:immich_mobile/presentation/pages/search/drift_search.page.dart';
@@ -114,6 +115,7 @@ class AppRouter extends RootStackRouter {
@override
late final List<AutoRoute> routes = [
AutoRoute(page: SplashScreenRoute.page, initial: true),
AutoRoute(page: GuestGalleryRoute.page),
AutoRoute(page: LoginRoute.page),
AutoRoute(page: ChangePasswordRoute.page),
AutoRoute(
+16
View File
@@ -1224,6 +1224,22 @@ class FolderRouteArgs {
int get hashCode => key.hashCode ^ folder.hashCode;
}
/// generated route for
/// [GuestGalleryPage]
class GuestGalleryRoute extends PageRouteInfo<void> {
const GuestGalleryRoute({List<PageRouteInfo>? children})
: super(GuestGalleryRoute.name, initialChildren: children);
static const String name = 'GuestGalleryRoute';
static PageInfo page = PageInfo(
name,
builder: (data) {
return const GuestGalleryPage();
},
);
}
/// generated route for
/// [HeaderSettingsPage]
class HeaderSettingsRoute extends PageRouteInfo<void> {