2023-04-17 07:02:07 +02:00
|
|
|
import 'package:flutter/material.dart';
|
2022-02-03 18:06:44 +02:00
|
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
2023-03-04 00:38:30 +02:00
|
|
|
import 'package:immich_mobile/modules/album/services/album.service.dart';
|
|
|
|
import 'package:immich_mobile/shared/models/exif_info.dart';
|
2023-02-09 19:32:08 +02:00
|
|
|
import 'package:immich_mobile/shared/models/store.dart';
|
2023-05-25 05:52:43 +02:00
|
|
|
import 'package:immich_mobile/shared/models/user.dart';
|
2023-03-04 00:38:30 +02:00
|
|
|
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
2023-10-22 04:38:07 +02:00
|
|
|
import 'package:immich_mobile/shared/providers/user.provider.dart';
|
2023-02-04 22:42:42 +02:00
|
|
|
import 'package:immich_mobile/shared/services/asset.service.dart';
|
2023-01-18 17:59:23 +02:00
|
|
|
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';
|
2022-11-08 19:00:24 +02:00
|
|
|
import 'package:immich_mobile/shared/models/asset.dart';
|
2023-03-27 04:35:52 +02:00
|
|
|
import 'package:immich_mobile/shared/services/sync.service.dart';
|
2023-05-25 05:52:43 +02:00
|
|
|
import 'package:immich_mobile/shared/services/user.service.dart';
|
2023-03-27 04:35:52 +02:00
|
|
|
import 'package:immich_mobile/utils/db.dart';
|
2023-03-04 00:38:30 +02:00
|
|
|
import 'package:isar/isar.dart';
|
2022-11-27 22:34:19 +02:00
|
|
|
import 'package:logging/logging.dart';
|
2022-02-13 23:10:42 +02:00
|
|
|
import 'package:photo_manager/photo_manager.dart';
|
2022-02-03 18:06:44 +02:00
|
|
|
|
2023-06-10 20:13:59 +02:00
|
|
|
class AssetNotifier extends StateNotifier<bool> {
|
2022-06-25 20:46:51 +02:00
|
|
|
final AssetService _assetService;
|
2023-03-04 00:38:30 +02:00
|
|
|
final AlbumService _albumService;
|
2023-05-25 05:52:43 +02:00
|
|
|
final UserService _userService;
|
2023-03-27 04:35:52 +02:00
|
|
|
final SyncService _syncService;
|
2023-03-04 00:38:30 +02:00
|
|
|
final Isar _db;
|
2022-11-27 22:34:19 +02:00
|
|
|
final log = Logger('AssetNotifier');
|
2022-11-08 19:00:24 +02:00
|
|
|
bool _getAllAssetInProgress = false;
|
|
|
|
bool _deleteInProgress = false;
|
2023-09-10 14:51:18 +02:00
|
|
|
bool _getPartnerAssetsInProgress = false;
|
2022-02-03 18:06:44 +02:00
|
|
|
|
2023-01-18 17:59:23 +02:00
|
|
|
AssetNotifier(
|
|
|
|
this._assetService,
|
2023-03-04 00:38:30 +02:00
|
|
|
this._albumService,
|
2023-05-25 05:52:43 +02:00
|
|
|
this._userService,
|
2023-03-27 04:35:52 +02:00
|
|
|
this._syncService,
|
2023-03-04 00:38:30 +02:00
|
|
|
this._db,
|
2023-06-10 20:13:59 +02:00
|
|
|
) : super(false);
|
2022-10-15 23:20:15 +02:00
|
|
|
|
2023-03-27 04:35:52 +02:00
|
|
|
Future<void> getAllAsset({bool clear = false}) async {
|
2022-11-08 19:00:24 +02:00
|
|
|
if (_getAllAssetInProgress || _deleteInProgress) {
|
|
|
|
// guard against multiple calls to this method while it's still working
|
|
|
|
return;
|
|
|
|
}
|
2023-03-27 04:35:52 +02:00
|
|
|
final stopwatch = Stopwatch()..start();
|
2022-11-08 19:00:24 +02:00
|
|
|
try {
|
|
|
|
_getAllAssetInProgress = true;
|
2023-06-10 20:13:59 +02:00
|
|
|
state = true;
|
2023-03-27 04:35:52 +02:00
|
|
|
if (clear) {
|
|
|
|
await clearAssetsAndAlbums(_db);
|
|
|
|
log.info("Manual refresh requested, cleared assets and albums from db");
|
2023-02-04 22:42:42 +02:00
|
|
|
}
|
2023-03-04 00:38:30 +02:00
|
|
|
final bool newRemote = await _assetService.refreshRemoteAssets();
|
|
|
|
final bool newLocal = await _albumService.refreshDeviceAlbums();
|
2023-04-17 07:02:07 +02:00
|
|
|
debugPrint("newRemote: $newRemote, newLocal: $newLocal");
|
2023-09-10 14:51:18 +02:00
|
|
|
|
2022-11-27 22:34:19 +02:00
|
|
|
log.info("Load assets: ${stopwatch.elapsedMilliseconds}ms");
|
2022-11-08 19:00:24 +02:00
|
|
|
} finally {
|
|
|
|
_getAllAssetInProgress = false;
|
2023-06-10 20:13:59 +02:00
|
|
|
state = false;
|
2022-10-14 23:57:55 +02:00
|
|
|
}
|
2022-11-26 18:16:02 +02:00
|
|
|
}
|
|
|
|
|
2023-09-10 14:51:18 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-04 00:38:30 +02:00
|
|
|
Future<void> clearAllAsset() {
|
2023-03-27 04:35:52 +02:00
|
|
|
return clearAssetsAndAlbums(_db);
|
2022-02-13 23:10:42 +02:00
|
|
|
}
|
2022-02-03 18:06:44 +02:00
|
|
|
|
2023-03-04 00:38:30 +02:00
|
|
|
Future<void> onNewAssetUploaded(Asset newAsset) async {
|
2023-05-17 19:36:02 +02:00
|
|
|
// eTag on device is not valid after partially modifying the assets
|
|
|
|
Store.delete(StoreKey.assetETag);
|
|
|
|
await _syncService.syncNewAssetToDb(newAsset);
|
2022-02-14 18:40:41 +02:00
|
|
|
}
|
|
|
|
|
2023-10-06 09:01:14 +02:00
|
|
|
Future<bool> deleteAssets(
|
|
|
|
Iterable<Asset> deleteAssets, {
|
|
|
|
bool? force = false,
|
|
|
|
}) async {
|
2022-11-08 19:00:24 +02:00
|
|
|
_deleteInProgress = true;
|
2023-06-10 20:13:59 +02:00
|
|
|
state = true;
|
2022-11-08 19:00:24 +02:00
|
|
|
try {
|
|
|
|
final localDeleted = await _deleteLocalAssets(deleteAssets);
|
2023-10-06 09:01:14 +02:00
|
|
|
final remoteDeleted = await _deleteRemoteAssets(deleteAssets, force);
|
2023-03-04 00:38:30 +02:00
|
|
|
if (localDeleted.isNotEmpty || remoteDeleted.isNotEmpty) {
|
2023-10-06 09:01:14 +02:00
|
|
|
List<Asset>? assetsToUpdate;
|
|
|
|
// Local only assets are permanently deleted for now. So always remove them from db
|
|
|
|
final dbIds = deleteAssets
|
|
|
|
.where((a) => a.isLocal && !a.isRemote)
|
|
|
|
.map((e) => e.id)
|
|
|
|
.toList();
|
|
|
|
if (force == null || !force) {
|
|
|
|
assetsToUpdate = remoteDeleted.map((e) {
|
|
|
|
e.isTrashed = true;
|
|
|
|
return e;
|
|
|
|
}).toList();
|
|
|
|
} else {
|
|
|
|
// Add all remote assets to be deleted from isar as since they are permanently deleted
|
|
|
|
dbIds.addAll(remoteDeleted.map((e) => e.id));
|
|
|
|
}
|
2023-03-04 00:38:30 +02:00
|
|
|
await _db.writeTxn(() async {
|
2023-10-06 09:01:14 +02:00
|
|
|
if (assetsToUpdate != null) {
|
|
|
|
await _db.assets.putAll(assetsToUpdate);
|
|
|
|
}
|
2023-03-04 00:38:30 +02:00
|
|
|
await _db.exifInfos.deleteAll(dbIds);
|
|
|
|
await _db.assets.deleteAll(dbIds);
|
|
|
|
});
|
2023-10-06 09:01:14 +02:00
|
|
|
return true;
|
2022-11-08 19:00:24 +02:00
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
_deleteInProgress = false;
|
2023-06-10 20:13:59 +02:00
|
|
|
state = false;
|
2022-11-08 19:00:24 +02:00
|
|
|
}
|
2023-10-06 09:01:14 +02:00
|
|
|
return false;
|
2022-11-08 19:00:24 +02:00
|
|
|
}
|
|
|
|
|
2023-06-27 19:25:00 +02:00
|
|
|
Future<List<String>> _deleteLocalAssets(
|
|
|
|
Iterable<Asset> assetsToDelete,
|
|
|
|
) async {
|
2023-06-10 20:13:59 +02:00
|
|
|
final List<String> local =
|
|
|
|
assetsToDelete.where((a) => a.isLocal).map((a) => a.localId!).toList();
|
2022-02-13 23:10:42 +02:00
|
|
|
// Delete asset from device
|
2022-11-08 19:00:24 +02:00
|
|
|
if (local.isNotEmpty) {
|
|
|
|
try {
|
2023-05-17 19:36:02 +02:00
|
|
|
return await PhotoManager.editor.deleteWithIds(local);
|
2022-11-27 22:34:19 +02:00
|
|
|
} catch (e, stack) {
|
|
|
|
log.severe("Failed to delete asset from device", e, stack);
|
2022-02-07 04:31:32 +02:00
|
|
|
}
|
|
|
|
}
|
2022-11-08 19:00:24 +02:00
|
|
|
return [];
|
|
|
|
}
|
2022-10-15 23:20:15 +02:00
|
|
|
|
2023-10-06 09:01:14 +02:00
|
|
|
Future<Iterable<Asset>> _deleteRemoteAssets(
|
2023-06-27 19:25:00 +02:00
|
|
|
Iterable<Asset> assetsToDelete,
|
2023-10-06 09:01:14 +02:00
|
|
|
bool? force,
|
2022-11-08 19:00:24 +02:00
|
|
|
) async {
|
2023-02-04 22:42:42 +02:00
|
|
|
final Iterable<Asset> remote = assetsToDelete.where((e) => e.isRemote);
|
2023-10-06 09:01:14 +02:00
|
|
|
|
|
|
|
final isSuccess = await _assetService.deleteAssets(remote, force: force);
|
|
|
|
return isSuccess ? remote : [];
|
2022-02-07 04:31:32 +02:00
|
|
|
}
|
2023-02-05 05:25:11 +02:00
|
|
|
|
2023-05-17 19:36:02 +02:00
|
|
|
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;
|
|
|
|
}
|
2023-02-06 14:59:56 +02:00
|
|
|
}
|
2023-02-05 05:25:11 +02:00
|
|
|
}
|
2023-04-17 07:02:07 +02:00
|
|
|
|
2023-05-17 19:36:02 +02:00
|
|
|
Future<void> toggleArchive(List<Asset> assets, bool status) async {
|
|
|
|
final newAssets = await _assetService.changeArchiveStatus(assets, status);
|
2023-04-17 07:02:07 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-02-03 18:06:44 +02:00
|
|
|
}
|
|
|
|
|
2023-06-10 20:13:59 +02:00
|
|
|
final assetProvider = StateNotifierProvider<AssetNotifier, bool>((ref) {
|
2022-10-14 23:57:55 +02:00
|
|
|
return AssetNotifier(
|
2022-11-21 14:13:14 +02:00
|
|
|
ref.watch(assetServiceProvider),
|
2023-03-04 00:38:30 +02:00
|
|
|
ref.watch(albumServiceProvider),
|
2023-05-25 05:52:43 +02:00
|
|
|
ref.watch(userServiceProvider),
|
2023-03-27 04:35:52 +02:00
|
|
|
ref.watch(syncServiceProvider),
|
2023-03-04 00:38:30 +02:00
|
|
|
ref.watch(dbProvider),
|
2022-07-13 14:23:48 +02:00
|
|
|
);
|
2022-02-13 23:10:42 +02:00
|
|
|
});
|
2022-04-24 04:08:45 +02:00
|
|
|
|
2023-05-17 19:36:02 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
});
|
2022-04-24 04:08:45 +02:00
|
|
|
|
2023-10-12 04:10:59 +02:00
|
|
|
final assetWatcher =
|
|
|
|
StreamProvider.autoDispose.family<Asset?, Asset>((ref, asset) {
|
|
|
|
final db = ref.watch(dbProvider);
|
|
|
|
return db.assets.watchObject(asset.id, fireImmediately: true);
|
|
|
|
});
|
|
|
|
|
2023-05-25 05:52:43 +02:00
|
|
|
final assetsProvider =
|
|
|
|
StreamProvider.family<RenderList, int?>((ref, userId) async* {
|
|
|
|
if (userId == null) return;
|
2023-05-17 19:36:02 +02:00
|
|
|
final query = ref
|
|
|
|
.watch(dbProvider)
|
|
|
|
.assets
|
2023-09-28 05:43:55 +02:00
|
|
|
.where()
|
|
|
|
.ownerIdEqualToAnyChecksum(userId)
|
2023-05-17 19:36:02 +02:00
|
|
|
.filter()
|
|
|
|
.isArchivedEqualTo(false)
|
2023-10-06 09:01:14 +02:00
|
|
|
.isTrashedEqualTo(false)
|
2023-10-22 04:38:07 +02:00
|
|
|
.stackParentIdIsNull()
|
2023-05-17 19:36:02 +02:00
|
|
|
.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);
|
|
|
|
}
|
|
|
|
});
|
2022-07-13 14:23:48 +02:00
|
|
|
|
2023-10-22 04:38:07 +02:00
|
|
|
QueryBuilder<Asset, Asset, QAfterSortBy>? getRemoteAssetQuery(WidgetRef ref) {
|
|
|
|
final userId = ref.watch(currentUserProvider)?.isarId;
|
|
|
|
if (userId == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return ref
|
2023-05-17 19:36:02 +02:00
|
|
|
.watch(dbProvider)
|
|
|
|
.assets
|
|
|
|
.where()
|
|
|
|
.remoteIdIsNotNull()
|
|
|
|
.filter()
|
2023-05-25 05:52:43 +02:00
|
|
|
.ownerIdEqualTo(userId)
|
2023-10-06 09:01:14 +02:00
|
|
|
.isTrashedEqualTo(false)
|
2023-10-22 04:38:07 +02:00
|
|
|
.stackParentIdIsNull()
|
2023-06-18 06:09:55 +02:00
|
|
|
.sortByFileCreatedAtDesc();
|
2023-10-22 04:38:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QueryBuilder<Asset, Asset, QAfterSortBy>? getAssetStackSelectionQuery(
|
|
|
|
WidgetRef ref,
|
|
|
|
Asset parentAsset,
|
|
|
|
) {
|
|
|
|
final userId = ref.watch(currentUserProvider)?.isarId;
|
|
|
|
if (userId == null || !parentAsset.isRemote) {
|
|
|
|
return null;
|
2023-05-17 19:36:02 +02:00
|
|
|
}
|
2023-10-22 04:38:07 +02:00
|
|
|
return ref
|
|
|
|
.watch(dbProvider)
|
|
|
|
.assets
|
|
|
|
.where()
|
|
|
|
.remoteIdIsNotNull()
|
|
|
|
.filter()
|
|
|
|
.isArchivedEqualTo(false)
|
|
|
|
.ownerIdEqualTo(userId)
|
|
|
|
.not()
|
|
|
|
.remoteIdEqualTo(parentAsset.remoteId)
|
|
|
|
// Show existing stack children in selection page
|
|
|
|
.group(
|
|
|
|
(q) => q
|
|
|
|
.stackParentIdIsNull()
|
|
|
|
.or()
|
|
|
|
.stackParentIdEqualTo(parentAsset.remoteId),
|
|
|
|
)
|
|
|
|
.sortByFileCreatedAtDesc();
|
|
|
|
}
|