fix: make local album provider reactive (#31582)

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
shenlong
2026-09-15 08:11:31 -05:00
committed by GitHub
co-authored by shenlong-tanwen
parent 1fb13c24a7
commit bea31eb064
5 changed files with 28 additions and 10 deletions
@@ -11,6 +11,10 @@ class LocalAlbumService {
return _repository.getAll(sortBy: sortBy);
}
Stream<List<LocalAlbum>> watchAll({Set<SortLocalAlbumsBy> sortBy = const {}}) {
return _repository.watchAll(sortBy: sortBy);
}
Future<LocalAsset?> getThumbnail(String albumId) {
return _repository.getThumbnail(albumId);
}
@@ -20,7 +20,11 @@ class LocalAlbumRepository extends DatabaseAccessor<Drift> with $LocalAlbumRepos
Drift get _db => attachedDatabase;
Future<List<LocalAlbum>> getAll({Set<SortLocalAlbumsBy> sortBy = const {}}) {
Future<List<LocalAlbum>> getAll({Set<SortLocalAlbumsBy> sortBy = const {}}) => _selectAll(sortBy: sortBy).get();
Stream<List<LocalAlbum>> watchAll({Set<SortLocalAlbumsBy> sortBy = const {}}) => _selectAll(sortBy: sortBy).watch();
Selectable<LocalAlbum> _selectAll({Set<SortLocalAlbumsBy> sortBy = const {}}) {
final assetCount = _db.localAlbumAssetEntity.assetId.count();
final query = _db.localAlbumEntity.select().join([
@@ -49,7 +53,7 @@ class LocalAlbumRepository extends DatabaseAccessor<Drift> 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<List<LocalAlbum>> getBackupAlbums() async {
@@ -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];
@@ -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<List<LocalAlbum>>(
final localAlbumProvider = StreamProvider<List<LocalAlbum>>(
(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<LocalAsset?, String>(
@@ -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<LocalAlbumRepository>();
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