diff --git a/mobile/analysis_options.yaml b/mobile/analysis_options.yaml index 23efa2a275..6160d2ad1f 100644 --- a/mobile/analysis_options.yaml +++ b/mobile/analysis_options.yaml @@ -77,7 +77,7 @@ custom_lint: - test/**.dart # refactor the remaining providers - lib/providers/{archive,asset,authentication,db,favorite,partner,trash,user}.provider.dart - - lib/providers/{album/album,album/shared_album,asset_viewer/render_list,backup/backup,search/all_motion_photos,search/recently_added_asset}.provider.dart + - lib/providers/{asset_viewer/render_list,backup/backup,search/all_motion_photos,search/recently_added_asset}.provider.dart - import_rule_openapi: message: openapi must only be used through ApiRepositories diff --git a/mobile/immich_lint/pubspec.yaml b/mobile/immich_lint/pubspec.yaml index 5d871b03e6..4cfd8abe81 100644 --- a/mobile/immich_lint/pubspec.yaml +++ b/mobile/immich_lint/pubspec.yaml @@ -5,7 +5,7 @@ environment: sdk: '>=3.0.0 <4.0.0' dependencies: - analyzer: ^7.0.0 + analyzer: ^6.0.0 analyzer_plugin: ^0.11.3 custom_lint_builder: ^0.6.4 glob: ^2.1.2 diff --git a/mobile/lib/interfaces/album.interface.dart b/mobile/lib/interfaces/album.interface.dart index cabf2dee53..86174b7dab 100644 --- a/mobile/lib/interfaces/album.interface.dart +++ b/mobile/lib/interfaces/album.interface.dart @@ -3,6 +3,7 @@ import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/interfaces/database.interface.dart'; import 'package:immich_mobile/models/albums/album_search.model.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; abstract interface class IAlbumRepository implements IDatabaseRepository { Future create(Album album); @@ -42,6 +43,14 @@ abstract interface class IAlbumRepository implements IDatabaseRepository { Future recalculateMetadata(Album album); Future> search(String searchTerm, QuickFilterMode filterMode); + + Stream> watchRemoteAlbums(); + + Stream> watchLocalAlbums(); + + Stream watchAlbum(int id); + + Stream getRenderListStream(Album album); } enum AlbumSort { remoteId, localId } diff --git a/mobile/lib/providers/album/album.provider.dart b/mobile/lib/providers/album/album.provider.dart index 8c06faaa6a..d9dd5bcc96 100644 --- a/mobile/lib/providers/album/album.provider.dart +++ b/mobile/lib/providers/album/album.provider.dart @@ -8,43 +8,40 @@ import 'package:immich_mobile/services/album.service.dart'; import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart'; -import 'package:immich_mobile/providers/db.provider.dart'; -import 'package:immich_mobile/utils/renderlist_generator.dart'; -import 'package:isar/isar.dart'; final isRefreshingRemoteAlbumProvider = StateProvider((ref) => false); class AlbumNotifier extends StateNotifier> { - AlbumNotifier(this._albumService, this.db, this.ref) : super([]) { - final query = db.albums.filter().remoteIdIsNotNull(); - query.findAll().then((value) { + AlbumNotifier(this.albumService, this.ref) : super([]) { + albumService.getAllRemoteAlbums().then((value) { if (mounted) { state = value; } }); - _streamSub = query.watch().listen((data) => state = data); + + _streamSub = + albumService.watchRemoteAlbums().listen((data) => state = data); } - final AlbumService _albumService; - final Isar db; + final AlbumService albumService; final Ref ref; late final StreamSubscription> _streamSub; Future refreshRemoteAlbums() async { ref.read(isRefreshingRemoteAlbumProvider.notifier).state = true; - await _albumService.refreshRemoteAlbums(); + await albumService.refreshRemoteAlbums(); ref.read(isRefreshingRemoteAlbumProvider.notifier).state = false; } - Future refreshDeviceAlbums() => _albumService.refreshDeviceAlbums(); + Future refreshDeviceAlbums() => albumService.refreshDeviceAlbums(); - Future deleteAlbum(Album album) => _albumService.deleteAlbum(album); + Future deleteAlbum(Album album) => albumService.deleteAlbum(album); Future createAlbum( String albumTitle, Set assets, ) => - _albumService.createAlbum(albumTitle, assets, []); + albumService.createAlbum(albumTitle, assets, []); Future getAlbumByName( String albumName, { @@ -52,7 +49,7 @@ class AlbumNotifier extends StateNotifier> { bool? shared, bool? owner, }) => - _albumService.getAlbumByName( + albumService.getAlbumByName( albumName, remote: remote, shared: shared, @@ -74,7 +71,7 @@ class AlbumNotifier extends StateNotifier> { } Future leaveAlbum(Album album) async { - var res = await _albumService.leaveAlbum(album); + var res = await albumService.leaveAlbum(album); if (res) { await deleteAlbum(album); @@ -85,15 +82,15 @@ class AlbumNotifier extends StateNotifier> { } void searchAlbums(String searchTerm, QuickFilterMode filterMode) async { - state = await _albumService.search(searchTerm, filterMode); + state = await albumService.search(searchTerm, filterMode); } Future addUsers(Album album, List userIds) async { - await _albumService.addUsers(album, userIds); + await albumService.addUsers(album, userIds); } Future removeUser(Album album, User user) async { - final isRemoved = await _albumService.removeUser(album, user); + final isRemoved = await albumService.removeUser(album, user); if (isRemoved && album.sharedUsers.isEmpty) { state = state.where((element) => element.id != album.id).toList(); @@ -103,25 +100,25 @@ class AlbumNotifier extends StateNotifier> { } Future addAssets(Album album, Iterable assets) async { - await _albumService.addAssets(album, assets); + await albumService.addAssets(album, assets); } Future removeAsset(Album album, Iterable assets) async { - return await _albumService.removeAsset(album, assets); + return await albumService.removeAsset(album, assets); } Future setActivitystatus( Album album, bool enabled, ) { - return _albumService.setActivityStatus(album, enabled); + return albumService.setActivityStatus(album, enabled); } Future toggleSortOrder(Album album) { final order = album.sortOrder == SortOrder.asc ? SortOrder.desc : SortOrder.asc; - return _albumService.updateSortOrder(album, order); + return albumService.updateSortOrder(album, order); } @override @@ -135,57 +132,49 @@ final albumProvider = StateNotifierProvider.autoDispose>((ref) { return AlbumNotifier( ref.watch(albumServiceProvider), - ref.watch(dbProvider), ref, ); }); final albumWatcher = - StreamProvider.autoDispose.family((ref, albumId) async* { - final db = ref.watch(dbProvider); - final a = await db.albums.get(albumId); - if (a != null) yield a; - await for (final a in db.albums.watchObject(albumId, fireImmediately: true)) { - if (a != null) yield a; + StreamProvider.autoDispose.family((ref, id) async* { + final albumService = ref.watch(albumServiceProvider); + + final album = await albumService.getAlbumById(id); + if (album != null) { + yield album; + } + + await for (final album in albumService.watchAlbum(id)) { + if (album != null) { + yield album; + } } }); final albumRenderlistProvider = - StreamProvider.autoDispose.family((ref, albumId) { - final album = ref.watch(albumWatcher(albumId)).value; + StreamProvider.autoDispose.family((ref, id) { + final album = ref.watch(albumWatcher(id)).value; if (album != null) { - final query = album.assets.filter().isTrashedEqualTo(false); - if (album.sortOrder == SortOrder.asc) { - return renderListGeneratorWithGroupBy( - query.sortByFileCreatedAt(), - GroupAssetsBy.none, - ); - } else if (album.sortOrder == SortOrder.desc) { - return renderListGeneratorWithGroupBy( - query.sortByFileCreatedAtDesc(), - GroupAssetsBy.none, - ); - } + return ref.watch(albumServiceProvider).getRenderListGenerator(album); } return const Stream.empty(); }); class LocalAlbumsNotifier extends StateNotifier> { - LocalAlbumsNotifier(this.db) : super([]) { - final query = db.albums.where().remoteIdIsNull(); - - query.findAll().then((value) { + LocalAlbumsNotifier(this.albumService) : super([]) { + albumService.getAllLocalAlbums().then((value) { if (mounted) { state = value; } }); - _streamSub = query.watch().listen((data) => state = data); + _streamSub = albumService.watchLocalAlbums().listen((data) => state = data); } - final Isar db; + final AlbumService albumService; late final StreamSubscription> _streamSub; @override @@ -197,5 +186,5 @@ class LocalAlbumsNotifier extends StateNotifier> { final localAlbumsProvider = StateNotifierProvider.autoDispose>((ref) { - return LocalAlbumsNotifier(ref.watch(dbProvider)); + return LocalAlbumsNotifier(ref.watch(albumServiceProvider)); }); diff --git a/mobile/lib/repositories/album.repository.dart b/mobile/lib/repositories/album.repository.dart index 7f4beee3bb..041820f0f2 100644 --- a/mobile/lib/repositories/album.repository.dart +++ b/mobile/lib/repositories/album.repository.dart @@ -1,4 +1,5 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; @@ -7,6 +8,7 @@ import 'package:immich_mobile/interfaces/album.interface.dart'; import 'package:immich_mobile/models/albums/album_search.model.dart'; import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/repositories/database.repository.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; import 'package:isar/isar.dart'; final albumRepositoryProvider = @@ -152,4 +154,37 @@ class AlbumRepository extends DatabaseRepository implements IAlbumRepository { return await query.findAll(); } + + @override + Stream> watchRemoteAlbums() { + return db.albums.where().remoteIdIsNotNull().watch(); + } + + @override + Stream> watchLocalAlbums() { + return db.albums.where().localIdIsNotNull().watch(); + } + + @override + Stream watchAlbum(int id) { + return db.albums.watchObject(id, fireImmediately: true); + } + + @override + Stream getRenderListStream(Album album) async* { + final query = album.assets.filter().isTrashedEqualTo(false); + final withSortedOption = switch (album.sortOrder) { + SortOrder.asc => query.sortByFileCreatedAt(), + SortOrder.desc => query.sortByFileCreatedAtDesc(), + }; + + yield await RenderList.fromQuery( + withSortedOption, + GroupAssetsBy.none, + ); + + await for (final _ in query.watchLazy()) { + yield await RenderList.fromQuery(withSortedOption, GroupAssetsBy.none); + } + } } diff --git a/mobile/lib/services/album.service.dart b/mobile/lib/services/album.service.dart index a993705e11..49931f0b85 100644 --- a/mobile/lib/services/album.service.dart +++ b/mobile/lib/services/album.service.dart @@ -26,6 +26,7 @@ import 'package:immich_mobile/repositories/album_media.repository.dart'; import 'package:immich_mobile/services/entity.service.dart'; import 'package:immich_mobile/services/sync.service.dart'; import 'package:immich_mobile/services/user.service.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; import 'package:logging/logging.dart'; final albumServiceProvider = Provider( @@ -442,10 +443,35 @@ class AlbumService { } } - Future> getAll() async { + Future> getAllRemoteAlbums() async { return _albumRepository.getAll(remote: true); } + Future> getAllLocalAlbums() async { + return _albumRepository.getAll(remote: false); + } + + Stream> watchRemoteAlbums() { + return _albumRepository.watchRemoteAlbums(); + } + + Stream> watchLocalAlbums() { + return _albumRepository.watchLocalAlbums(); + } + + /// Get album by Isar ID + Future getAlbumById(int id) { + return _albumRepository.get(id); + } + + Stream watchAlbum(int id) { + return _albumRepository.watchAlbum(id); + } + + Stream getRenderListGenerator(Album album) { + return _albumRepository.getRenderListStream(album); + } + Future> search( String searchTerm, QuickFilterMode filterMode, diff --git a/mobile/openapi/devtools_options.yaml b/mobile/openapi/devtools_options.yaml new file mode 100644 index 0000000000..fa0b357c4f --- /dev/null +++ b/mobile/openapi/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: