1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-28 11:15:54 +02:00
immich/mobile/lib/shared/providers/asset.provider.dart

393 lines
12 KiB
Dart
Raw Normal View History

import 'package:flutter/material.dart';
2022-02-03 18:06:44 +02:00
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:immich_mobile/entities/exif_info.entity.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/entities/user.entity.dart';
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';
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/entities/asset.entity.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:immich_mobile/utils/renderlist_generator.dart';
import 'package:isar/isar.dart';
import 'package:logging/logging.dart';
import 'package:photo_manager/photo_manager.dart';
2022-02-03 18:06:44 +02:00
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;
2022-02-03 18:06:44 +02:00
AssetNotifier(
this._assetService,
this._albumService,
this._userService,
this._syncService,
this._db,
) : super(false);
2022-10-15 23:20:15 +02:00
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;
2022-10-14 23:57:55 +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;
}
}
Future<void> clearAllAsset() {
return clearAssetsAndAlbums(_db);
}
2022-02-03 18:06:44 +02:00
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<bool> deleteLocalOnlyAssets(
Iterable<Asset> deleteAssets, {
bool onlyBackedUp = false,
}) async {
_deleteInProgress = true;
state = true;
try {
final assets = onlyBackedUp
? deleteAssets.where((e) => e.storage == AssetState.merged)
: deleteAssets;
final localDeleted = await _deleteLocalAssets(assets);
if (localDeleted.isNotEmpty) {
final localOnlyIds = deleteAssets
.where((e) => e.storage == AssetState.local)
.map((e) => e.id)
.toList();
// Update merged assets to remote only
final mergedAssets =
deleteAssets.where((e) => e.storage == AssetState.merged).map((e) {
e.localId = null;
return e;
}).toList();
await _db.writeTxn(() async {
if (mergedAssets.isNotEmpty) {
await _db.assets.putAll(mergedAssets);
}
await _db.exifInfos.deleteAll(localOnlyIds);
await _db.assets.deleteAll(localOnlyIds);
});
return true;
}
} finally {
_deleteInProgress = false;
state = false;
}
return false;
}
Future<bool> deleteRemoteOnlyAssets(
Iterable<Asset> deleteAssets, {
bool force = false,
}) async {
_deleteInProgress = true;
state = true;
try {
final remoteDeleted = await _deleteRemoteAssets(deleteAssets, force);
if (remoteDeleted.isNotEmpty) {
final assetsToUpdate = force
/// If force, only update merged only assets and remove remote assets
? remoteDeleted
.where((e) => e.storage == AssetState.merged)
.map((e) {
e.remoteId = null;
return e;
})
// If not force, trash everything
: remoteDeleted.where((e) => e.isRemote).map((e) {
e.isTrashed = true;
return e;
});
await _db.writeTxn(() async {
if (assetsToUpdate.isNotEmpty) {
await _db.assets.putAll(assetsToUpdate.toList());
}
if (force) {
final remoteOnly = remoteDeleted
.where((e) => e.storage == AssetState.remote)
.map((e) => e.id)
.toList();
await _db.exifInfos.deleteAll(remoteOnly);
await _db.assets.deleteAll(remoteOnly);
}
});
return true;
}
} finally {
_deleteInProgress = false;
state = false;
}
return false;
}
feat(server): trash asset (#4015) * refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-06 09:01:14 +02:00
Future<bool> deleteAssets(
Iterable<Asset> deleteAssets, {
bool force = false,
feat(server): trash asset (#4015) * refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-06 09:01:14 +02:00
}) async {
_deleteInProgress = true;
state = true;
try {
final hasLocal = deleteAssets.any((a) => a.storage != AssetState.remote);
final localDeleted = await _deleteLocalAssets(deleteAssets);
final remoteDeleted = (hasLocal && localDeleted.isNotEmpty) || !hasLocal
? await _deleteRemoteAssets(deleteAssets, force)
: [];
if (localDeleted.isNotEmpty || remoteDeleted.isNotEmpty) {
final dbIds = <int>[];
final dbUpdates = <Asset>[];
// Local assets are removed
if (localDeleted.isNotEmpty) {
// Permanently remove local only assets from isar
dbIds.addAll(
deleteAssets
.where((a) => a.storage == AssetState.local)
.map((e) => e.id),
);
if (remoteDeleted.any((e) => e.isLocal)) {
// Force delete: Add all local assets including merged assets
if (force) {
dbIds.addAll(remoteDeleted.map((e) => e.id));
// Soft delete: Remove local Id from asset and trash it
} else {
dbUpdates.addAll(
remoteDeleted.map((e) {
e.localId = null;
e.isTrashed = true;
return e;
}),
);
}
}
feat(server): trash asset (#4015) * refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-06 09:01:14 +02:00
}
// Handle remote deletion
if (remoteDeleted.isNotEmpty) {
if (force) {
// Remove remote only assets
dbIds.addAll(
deleteAssets
.where((a) => a.storage == AssetState.remote)
.map((e) => e.id),
);
// Local assets are not removed and there are merged assets
final hasLocal = remoteDeleted.any((e) => e.isLocal);
if (localDeleted.isEmpty && hasLocal) {
// Remove remote Id from local assets
dbUpdates.addAll(
remoteDeleted.map((e) {
e.remoteId = null;
// Remove from trashed if remote asset is removed
e.isTrashed = false;
return e;
}),
);
}
} else {
dbUpdates.addAll(
remoteDeleted.map((e) {
e.isTrashed = true;
return e;
}),
);
feat(server): trash asset (#4015) * refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-06 09:01:14 +02:00
}
}
await _db.writeTxn(() async {
await _db.assets.putAll(dbUpdates);
await _db.exifInfos.deleteAll(dbIds);
await _db.assets.deleteAll(dbIds);
});
feat(server): trash asset (#4015) * refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-06 09:01:14 +02:00
return true;
}
} finally {
_deleteInProgress = false;
state = false;
}
feat(server): trash asset (#4015) * refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-06 09:01:14 +02:00
return 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 [];
}
2022-10-15 23:20:15 +02:00
Future<List<Asset>> _deleteRemoteAssets(
Iterable<Asset> assetsToDelete,
feat(server): trash asset (#4015) * refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-06 09:01:14 +02:00
bool? force,
) async {
final Iterable<Asset> remote = assetsToDelete.where((e) => e.isRemote);
feat(server): trash asset (#4015) * refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-06 09:01:14 +02:00
final isSuccess = await _assetService.deleteAssets(remote, force: force);
return isSuccess ? remote.toList() : [];
}
Future<void> toggleFavorite(List<Asset> assets, [bool? status]) async {
status ??= !assets.every((a) => a.isFavorite);
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 {
status ??= !assets.every((a) => a.isArchived);
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;
}
}
}
2022-02-03 18:06:44 +02:00
}
final assetProvider = StateNotifierProvider<AssetNotifier, bool>((ref) {
2022-10-14 23:57:55 +02:00
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 assetWatcher =
StreamProvider.autoDispose.family<Asset?, Asset>((ref, asset) {
final db = ref.watch(dbProvider);
return db.assets.watchObject(asset.id, fireImmediately: true);
});
final assetsProvider = StreamProvider.family<RenderList, int?>((ref, userId) {
if (userId == null) return const Stream.empty();
final query = _commonFilterAndSort(
_assets(ref).where().ownerIdEqualToAnyChecksum(userId),
);
return renderListGenerator(query, ref);
});
final multiUserAssetsProvider =
StreamProvider.family<RenderList, List<int>>((ref, userIds) {
if (userIds.isEmpty) return const Stream.empty();
final query = _commonFilterAndSort(
_assets(ref)
.where()
.anyOf(userIds, (q, u) => q.ownerIdEqualToAnyChecksum(u)),
);
return renderListGenerator(query, ref);
});
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
.watch(dbProvider)
.assets
.where()
.remoteIdIsNotNull()
.filter()
.ownerIdEqualTo(userId)
feat(server): trash asset (#4015) * refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-06 09:01:14 +02:00
.isTrashedEqualTo(false)
2023-10-22 04:38:07 +02:00
.stackParentIdIsNull()
.sortByFileCreatedAtDesc();
2023-10-22 04:38:07 +02:00
}
IsarCollection<Asset> _assets(StreamProviderRef<RenderList> ref) =>
ref.watch(dbProvider).assets;
QueryBuilder<Asset, Asset, QAfterSortBy> _commonFilterAndSort(
QueryBuilder<Asset, Asset, QAfterWhereClause> query,
) {
return query
.filter()
.isArchivedEqualTo(false)
.isTrashedEqualTo(false)
.stackParentIdIsNull()
.sortByFileCreatedAtDesc();
}