From bea31eb06470bdb2fd052b8120aea20db2dca933 Mon Sep 17 00:00:00 2001 From: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Date: Tue, 15 Sep 2026 18:41:31 +0530 Subject: [PATCH] fix: make local album provider reactive (#31582) Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> --- .../lib/domain/services/local_album.service.dart | 4 ++++ .../repositories/local_album.repository.dart | 8 ++++++-- mobile/lib/pages/common/tab_shell.page.dart | 5 ----- .../providers/infrastructure/album.provider.dart | 6 +++--- .../repositories/local_album_repository_test.dart | 15 +++++++++++++++ 5 files changed, 28 insertions(+), 10 deletions(-) diff --git a/mobile/lib/domain/services/local_album.service.dart b/mobile/lib/domain/services/local_album.service.dart index 60cf74590..b89d03299 100644 --- a/mobile/lib/domain/services/local_album.service.dart +++ b/mobile/lib/domain/services/local_album.service.dart @@ -11,6 +11,10 @@ class LocalAlbumService { return _repository.getAll(sortBy: sortBy); } + Stream> watchAll({Set sortBy = const {}}) { + return _repository.watchAll(sortBy: sortBy); + } + Future getThumbnail(String albumId) { return _repository.getThumbnail(albumId); } diff --git a/mobile/lib/infrastructure/repositories/local_album.repository.dart b/mobile/lib/infrastructure/repositories/local_album.repository.dart index aefac0b28..c28357a2e 100644 --- a/mobile/lib/infrastructure/repositories/local_album.repository.dart +++ b/mobile/lib/infrastructure/repositories/local_album.repository.dart @@ -20,7 +20,11 @@ class LocalAlbumRepository extends DatabaseAccessor with $LocalAlbumRepos Drift get _db => attachedDatabase; - Future> getAll({Set sortBy = const {}}) { + Future> getAll({Set sortBy = const {}}) => _selectAll(sortBy: sortBy).get(); + + Stream> watchAll({Set sortBy = const {}}) => _selectAll(sortBy: sortBy).watch(); + + Selectable _selectAll({Set sortBy = const {}}) { final assetCount = _db.localAlbumAssetEntity.assetId.count(); final query = _db.localAlbumEntity.select().join([ @@ -49,7 +53,7 @@ class LocalAlbumRepository extends DatabaseAccessor with $LocalAlbumRepos query.orderBy(orderings); } - return query.map((row) => row.readTable(_db.localAlbumEntity).toDto(assetCount: row.read(assetCount) ?? 0)).get(); + return query.map((row) => row.readTable(_db.localAlbumEntity).toDto(assetCount: row.read(assetCount) ?? 0)); } Future> getBackupAlbums() async { diff --git a/mobile/lib/pages/common/tab_shell.page.dart b/mobile/lib/pages/common/tab_shell.page.dart index 23e28e928..409c6f091 100644 --- a/mobile/lib/pages/common/tab_shell.page.dart +++ b/mobile/lib/pages/common/tab_shell.page.dart @@ -128,11 +128,6 @@ void _onNavigationSelected(TabsRouter router, int index, WidgetRef ref) { unawaited(ref.read(remoteAlbumProvider.notifier).refresh()); } - // Library page - if (index == kLibraryTabIndex) { - ref.invalidate(localAlbumProvider); - } - ref.read(hapticFeedbackProvider.notifier).selectionClick(); router.setActiveIndex(index); ref.read(tabProvider.notifier).state = TabEnum.values[index]; diff --git a/mobile/lib/providers/infrastructure/album.provider.dart b/mobile/lib/providers/infrastructure/album.provider.dart index 47fd2360b..2aae7bafc 100644 --- a/mobile/lib/providers/infrastructure/album.provider.dart +++ b/mobile/lib/providers/infrastructure/album.provider.dart @@ -10,10 +10,10 @@ import 'package:immich_mobile/providers/infrastructure/remote_album.provider.dar import 'package:immich_mobile/repositories/album_api_repository.dart'; import 'package:immich_mobile/services/foreground_upload.service.dart'; -final localAlbumProvider = FutureProvider>( +final localAlbumProvider = StreamProvider>( (ref) => LocalAlbumService(ref.watch(driftProvider).localAlbumRepository) - .getAll(sortBy: {SortLocalAlbumsBy.newestAsset}) - .then((albums) => albums.where((album) => album.assetCount > 0).toList()), + .watchAll(sortBy: {SortLocalAlbumsBy.newestAsset}) + .map((albums) => albums.where((album) => album.assetCount > 0).toList()), ); final localAlbumThumbnailProvider = FutureProvider.family( diff --git a/mobile/test/infrastructure/repositories/local_album_repository_test.dart b/mobile/test/infrastructure/repositories/local_album_repository_test.dart index 2923f1573..a5ab82bad 100644 --- a/mobile/test/infrastructure/repositories/local_album_repository_test.dart +++ b/mobile/test/infrastructure/repositories/local_album_repository_test.dart @@ -1,3 +1,4 @@ +import 'package:async/async.dart'; import 'package:drift/drift.dart'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -41,6 +42,20 @@ void main() { }); }); + group('watchAll', () { + test('re-emits when an album loses its assets', () async { + final localAlbumRepo = mediumFactory.getRepository(); + await localAlbumRepo.upsert(mediumFactory.localAlbum(), toUpsert: [_localAsset('1'), _localAsset('2')]); + + final counts = StreamQueue(localAlbumRepo.watchAll().map((albums) => albums.single.assetCount)); + addTearDown(counts.cancel); + + expect(await counts.next, 2); + await localAlbumRepo.processDelta(updates: [], deletes: ['1', '2'], assetAlbums: {}); + expect(await counts.next, 0); + }); + }); + group('processDelta', () { // Regression for #22844: an asset moved (not copied) into a backup album on // Android was dropped. The delta reports only the asset's new album, and the