Skip to content

Commit

Permalink
refactor(mobile): album provider (immich-app#16099)
Browse files Browse the repository at this point in the history
  • Loading branch information
alextran1502 authored Feb 15, 2025
1 parent 47203d2 commit 4f912de
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 53 deletions.
2 changes: 1 addition & 1 deletion mobile/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion mobile/immich_lint/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions mobile/lib/interfaces/album.interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 }
89 changes: 39 additions & 50 deletions mobile/lib/providers/album/album.provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,51 +8,48 @@ 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, {
bool? remote,
bool? shared,
bool? owner,
}) =>
_albumService.getAlbumByName(
albumService.getAlbumByName(
albumName,
remote: remote,
shared: shared,
Expand All @@ -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);
Expand All @@ -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();
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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));
});
35 changes: 35 additions & 0 deletions mobile/lib/repositories/album.repository.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 =
Expand Down Expand Up @@ -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);
}
}
}
28 changes: 27 additions & 1 deletion mobile/lib/services/album.service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions mobile/openapi/devtools_options.yaml
Original file line number Diff line number Diff line change
@@ -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:

0 comments on commit 4f912de

Please sign in to comment.