123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- import 'package:flutter/material.dart';
- import 'package:hooks_riverpod/hooks_riverpod.dart';
- import 'package:immich_mobile/modules/album/services/album.service.dart';
- import 'package:immich_mobile/shared/models/exif_info.dart';
- import 'package:immich_mobile/shared/models/store.dart';
- import 'package:immich_mobile/shared/models/user.dart';
- import 'package:immich_mobile/shared/providers/db.provider.dart';
- import 'package:immich_mobile/shared/services/asset.service.dart';
- import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
- import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
- import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
- import 'package:immich_mobile/shared/models/asset.dart';
- import 'package:immich_mobile/shared/services/sync.service.dart';
- import 'package:immich_mobile/shared/services/user.service.dart';
- import 'package:immich_mobile/utils/db.dart';
- import 'package:isar/isar.dart';
- import 'package:logging/logging.dart';
- import 'package:openapi/api.dart';
- import 'package:photo_manager/photo_manager.dart';
- class AssetNotifier extends StateNotifier<bool> {
- final AssetService _assetService;
- final AlbumService _albumService;
- final UserService _userService;
- final SyncService _syncService;
- final Isar _db;
- final log = Logger('AssetNotifier');
- bool _getAllAssetInProgress = false;
- bool _deleteInProgress = false;
- bool _getPartnerAssetsInProgress = false;
- AssetNotifier(
- this._assetService,
- this._albumService,
- this._userService,
- this._syncService,
- this._db,
- ) : super(false);
- Future<void> getAllAsset({bool clear = false}) async {
- if (_getAllAssetInProgress || _deleteInProgress) {
- // guard against multiple calls to this method while it's still working
- return;
- }
- final stopwatch = Stopwatch()..start();
- try {
- _getAllAssetInProgress = true;
- state = true;
- if (clear) {
- await clearAssetsAndAlbums(_db);
- log.info("Manual refresh requested, cleared assets and albums from db");
- }
- final bool newRemote = await _assetService.refreshRemoteAssets();
- final bool newLocal = await _albumService.refreshDeviceAlbums();
- debugPrint("newRemote: $newRemote, newLocal: $newLocal");
- log.info("Load assets: ${stopwatch.elapsedMilliseconds}ms");
- } finally {
- _getAllAssetInProgress = false;
- state = false;
- }
- }
- Future<void> getPartnerAssets([User? partner]) async {
- if (_getPartnerAssetsInProgress) return;
- try {
- final stopwatch = Stopwatch()..start();
- _getPartnerAssetsInProgress = true;
- if (partner == null) {
- await _userService.refreshUsers();
- final List<User> partners =
- await _db.users.filter().isPartnerSharedWithEqualTo(true).findAll();
- for (User u in partners) {
- await _assetService.refreshRemoteAssets(u);
- }
- } else {
- await _assetService.refreshRemoteAssets(partner);
- }
- log.info("Load partner assets: ${stopwatch.elapsedMilliseconds}ms");
- } finally {
- _getPartnerAssetsInProgress = false;
- }
- }
- Future<void> clearAllAsset() {
- return clearAssetsAndAlbums(_db);
- }
- Future<void> onNewAssetUploaded(Asset newAsset) async {
- // eTag on device is not valid after partially modifying the assets
- Store.delete(StoreKey.assetETag);
- await _syncService.syncNewAssetToDb(newAsset);
- }
- Future<void> deleteAssets(Iterable<Asset> deleteAssets) async {
- _deleteInProgress = true;
- state = true;
- try {
- final localDeleted = await _deleteLocalAssets(deleteAssets);
- final remoteDeleted = await _deleteRemoteAssets(deleteAssets);
- if (localDeleted.isNotEmpty || remoteDeleted.isNotEmpty) {
- final dbIds = deleteAssets.map((e) => e.id).toList();
- await _db.writeTxn(() async {
- await _db.exifInfos.deleteAll(dbIds);
- await _db.assets.deleteAll(dbIds);
- });
- }
- } finally {
- _deleteInProgress = false;
- state = false;
- }
- }
- Future<List<String>> _deleteLocalAssets(
- Iterable<Asset> assetsToDelete,
- ) async {
- final List<String> local =
- assetsToDelete.where((a) => a.isLocal).map((a) => a.localId!).toList();
- // Delete asset from device
- if (local.isNotEmpty) {
- try {
- return await PhotoManager.editor.deleteWithIds(local);
- } catch (e, stack) {
- log.severe("Failed to delete asset from device", e, stack);
- }
- }
- return [];
- }
- Future<Iterable<String>> _deleteRemoteAssets(
- Iterable<Asset> assetsToDelete,
- ) async {
- final Iterable<Asset> remote = assetsToDelete.where((e) => e.isRemote);
- final List<DeleteAssetResponseDto> deleteAssetResult =
- await _assetService.deleteAssets(remote) ?? [];
- return deleteAssetResult
- .where((a) => a.status == DeleteAssetStatus.SUCCESS)
- .map((a) => a.id);
- }
- Future<void> toggleFavorite(List<Asset> assets, bool status) async {
- final newAssets = await _assetService.changeFavoriteStatus(assets, status);
- for (Asset? newAsset in newAssets) {
- if (newAsset == null) {
- log.severe("Change favorite status failed for asset");
- continue;
- }
- }
- }
- Future<void> toggleArchive(List<Asset> assets, bool status) async {
- final newAssets = await _assetService.changeArchiveStatus(assets, status);
- int i = 0;
- for (Asset oldAsset in assets) {
- final newAsset = newAssets[i++];
- if (newAsset == null) {
- log.severe("Change archive status failed for asset ${oldAsset.id}");
- continue;
- }
- }
- }
- }
- final assetProvider = StateNotifierProvider<AssetNotifier, bool>((ref) {
- return AssetNotifier(
- ref.watch(assetServiceProvider),
- ref.watch(albumServiceProvider),
- ref.watch(userServiceProvider),
- ref.watch(syncServiceProvider),
- ref.watch(dbProvider),
- );
- });
- final assetDetailProvider =
- StreamProvider.autoDispose.family<Asset, Asset>((ref, asset) async* {
- yield await ref.watch(assetServiceProvider).loadExif(asset);
- final db = ref.watch(dbProvider);
- await for (final a in db.assets.watchObject(asset.id)) {
- if (a != null) yield await ref.watch(assetServiceProvider).loadExif(a);
- }
- });
- final assetsProvider =
- StreamProvider.family<RenderList, int?>((ref, userId) async* {
- if (userId == null) return;
- final query = ref
- .watch(dbProvider)
- .assets
- .where()
- .ownerIdEqualToAnyChecksum(userId)
- .filter()
- .isArchivedEqualTo(false)
- .sortByFileCreatedAtDesc();
- final settings = ref.watch(appSettingsServiceProvider);
- final groupBy =
- GroupAssetsBy.values[settings.getSetting(AppSettingsEnum.groupAssetsBy)];
- yield await RenderList.fromQuery(query, groupBy);
- await for (final _ in query.watchLazy()) {
- yield await RenderList.fromQuery(query, groupBy);
- }
- });
- final remoteAssetsProvider =
- StreamProvider.family<RenderList, int?>((ref, userId) async* {
- if (userId == null) return;
- final query = ref
- .watch(dbProvider)
- .assets
- .where()
- .remoteIdIsNotNull()
- .filter()
- .ownerIdEqualTo(userId)
- .sortByFileCreatedAtDesc();
- final settings = ref.watch(appSettingsServiceProvider);
- final groupBy =
- GroupAssetsBy.values[settings.getSetting(AppSettingsEnum.groupAssetsBy)];
- yield await RenderList.fromQuery(query, groupBy);
- await for (final _ in query.watchLazy()) {
- yield await RenderList.fromQuery(query, groupBy);
- }
- });
|