0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-04-01 02:51:27 -05:00

refactor(mobile): album provider (#16099)

This commit is contained in:
Alex 2025-02-14 19:27:39 -06:00 committed by GitHub
parent 47203d2760
commit 4f912de018
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 115 additions and 53 deletions

View file

@ -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

View file

@ -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

View file

@ -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<Album> create(Album album);
@ -42,6 +43,14 @@ abstract interface class IAlbumRepository implements IDatabaseRepository {
Future<Album> recalculateMetadata(Album album);
Future<List<Album>> search(String searchTerm, QuickFilterMode filterMode);
Stream<List<Album>> watchRemoteAlbums();
Stream<List<Album>> watchLocalAlbums();
Stream<Album?> watchAlbum(int id);
Stream<RenderList> getRenderListStream(Album album);
}
enum AlbumSort { remoteId, localId }

View file

@ -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<bool>((ref) => false);
class AlbumNotifier extends StateNotifier<List<Album>> {
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<List<Album>> _streamSub;
Future<void> refreshRemoteAlbums() async {
ref.read(isRefreshingRemoteAlbumProvider.notifier).state = true;
await _albumService.refreshRemoteAlbums();
await albumService.refreshRemoteAlbums();
ref.read(isRefreshingRemoteAlbumProvider.notifier).state = false;
}
Future<void> refreshDeviceAlbums() => _albumService.refreshDeviceAlbums();
Future<void> refreshDeviceAlbums() => albumService.refreshDeviceAlbums();
Future<bool> deleteAlbum(Album album) => _albumService.deleteAlbum(album);
Future<bool> deleteAlbum(Album album) => albumService.deleteAlbum(album);
Future<Album?> createAlbum(
String albumTitle,
Set<Asset> assets,
) =>
_albumService.createAlbum(albumTitle, assets, []);
albumService.createAlbum(albumTitle, assets, []);
Future<Album?> getAlbumByName(
String albumName, {
@ -52,7 +49,7 @@ class AlbumNotifier extends StateNotifier<List<Album>> {
bool? shared,
bool? owner,
}) =>
_albumService.getAlbumByName(
albumService.getAlbumByName(
albumName,
remote: remote,
shared: shared,
@ -74,7 +71,7 @@ class AlbumNotifier extends StateNotifier<List<Album>> {
}
Future<bool> 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<List<Album>> {
}
void searchAlbums(String searchTerm, QuickFilterMode filterMode) async {
state = await _albumService.search(searchTerm, filterMode);
state = await albumService.search(searchTerm, filterMode);
}
Future<void> addUsers(Album album, List<String> userIds) async {
await _albumService.addUsers(album, userIds);
await albumService.addUsers(album, userIds);
}
Future<bool> 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<List<Album>> {
}
Future<void> addAssets(Album album, Iterable<Asset> assets) async {
await _albumService.addAssets(album, assets);
await albumService.addAssets(album, assets);
}
Future<bool> removeAsset(Album album, Iterable<Asset> assets) async {
return await _albumService.removeAsset(album, assets);
return await albumService.removeAsset(album, assets);
}
Future<bool> setActivitystatus(
Album album,
bool enabled,
) {
return _albumService.setActivityStatus(album, enabled);
return albumService.setActivityStatus(album, enabled);
}
Future<Album?> 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<AlbumNotifier, List<Album>>((ref) {
return AlbumNotifier(
ref.watch(albumServiceProvider),
ref.watch(dbProvider),
ref,
);
});
final albumWatcher =
StreamProvider.autoDispose.family<Album, int>((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<Album, int>((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<RenderList, int>((ref, albumId) {
final album = ref.watch(albumWatcher(albumId)).value;
StreamProvider.autoDispose.family<RenderList, int>((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<List<Album>> {
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<List<Album>> _streamSub;
@override
@ -197,5 +186,5 @@ class LocalAlbumsNotifier extends StateNotifier<List<Album>> {
final localAlbumsProvider =
StateNotifierProvider.autoDispose<LocalAlbumsNotifier, List<Album>>((ref) {
return LocalAlbumsNotifier(ref.watch(dbProvider));
return LocalAlbumsNotifier(ref.watch(albumServiceProvider));
});

View file

@ -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<List<Album>> watchRemoteAlbums() {
return db.albums.where().remoteIdIsNotNull().watch();
}
@override
Stream<List<Album>> watchLocalAlbums() {
return db.albums.where().localIdIsNotNull().watch();
}
@override
Stream<Album?> watchAlbum(int id) {
return db.albums.watchObject(id, fireImmediately: true);
}
@override
Stream<RenderList> 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);
}
}
}

View file

@ -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<List<Album>> getAll() async {
Future<List<Album>> getAllRemoteAlbums() async {
return _albumRepository.getAll(remote: true);
}
Future<List<Album>> getAllLocalAlbums() async {
return _albumRepository.getAll(remote: false);
}
Stream<List<Album>> watchRemoteAlbums() {
return _albumRepository.watchRemoteAlbums();
}
Stream<List<Album>> watchLocalAlbums() {
return _albumRepository.watchLocalAlbums();
}
/// Get album by Isar ID
Future<Album?> getAlbumById(int id) {
return _albumRepository.get(id);
}
Stream<Album?> watchAlbum(int id) {
return _albumRepository.watchAlbum(id);
}
Stream<RenderList> getRenderListGenerator(Album album) {
return _albumRepository.getRenderListStream(album);
}
Future<List<Album>> search(
String searchTerm,
QuickFilterMode filterMode,

View file

@ -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: