2023-04-17 05:02:07 +00:00
|
|
|
import 'package:flutter/material.dart';
|
2022-02-03 16:06:44 +00:00
|
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
2024-11-19 18:09:29 +00:00
|
|
|
import 'package:immich_mobile/providers/locale_provider.dart';
|
2024-05-14 15:35:37 +00:00
|
|
|
import 'package:immich_mobile/providers/memory.provider.dart';
|
2024-09-18 15:15:52 +00:00
|
|
|
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
2024-05-02 20:59:14 +00:00
|
|
|
import 'package:immich_mobile/services/album.service.dart';
|
2024-05-01 02:36:40 +00:00
|
|
|
import 'package:immich_mobile/entities/exif_info.entity.dart';
|
|
|
|
import 'package:immich_mobile/entities/store.entity.dart';
|
2024-05-02 20:59:14 +00:00
|
|
|
import 'package:immich_mobile/providers/db.provider.dart';
|
|
|
|
import 'package:immich_mobile/providers/user.provider.dart';
|
|
|
|
import 'package:immich_mobile/services/asset.service.dart';
|
2024-05-07 04:04:21 +00:00
|
|
|
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
|
2024-05-01 02:36:40 +00:00
|
|
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
2024-05-02 20:59:14 +00:00
|
|
|
import 'package:immich_mobile/services/sync.service.dart';
|
|
|
|
import 'package:immich_mobile/services/user.service.dart';
|
2023-03-27 02:35:52 +00:00
|
|
|
import 'package:immich_mobile/utils/db.dart';
|
2023-11-13 15:54:41 +00:00
|
|
|
import 'package:immich_mobile/utils/renderlist_generator.dart';
|
2023-03-03 22:38:30 +00:00
|
|
|
import 'package:isar/isar.dart';
|
2022-11-27 20:34:19 +00:00
|
|
|
import 'package:logging/logging.dart';
|
2022-02-03 16:06:44 +00:00
|
|
|
|
2023-06-10 18:13:59 +00:00
|
|
|
class AssetNotifier extends StateNotifier<bool> {
|
2022-06-25 18:46:51 +00:00
|
|
|
final AssetService _assetService;
|
2023-03-03 22:38:30 +00:00
|
|
|
final AlbumService _albumService;
|
2023-05-25 03:52:43 +00:00
|
|
|
final UserService _userService;
|
2023-03-27 02:35:52 +00:00
|
|
|
final SyncService _syncService;
|
2023-03-03 22:38:30 +00:00
|
|
|
final Isar _db;
|
2024-05-14 15:35:37 +00:00
|
|
|
final StateNotifierProviderRef _ref;
|
2022-11-27 20:34:19 +00:00
|
|
|
final log = Logger('AssetNotifier');
|
2022-11-08 17:00:24 +00:00
|
|
|
bool _getAllAssetInProgress = false;
|
|
|
|
bool _deleteInProgress = false;
|
2022-02-03 16:06:44 +00:00
|
|
|
|
2023-01-18 15:59:23 +00:00
|
|
|
AssetNotifier(
|
|
|
|
this._assetService,
|
2023-03-03 22:38:30 +00:00
|
|
|
this._albumService,
|
2023-05-25 03:52:43 +00:00
|
|
|
this._userService,
|
2023-03-27 02:35:52 +00:00
|
|
|
this._syncService,
|
2023-03-03 22:38:30 +00:00
|
|
|
this._db,
|
2024-05-14 15:35:37 +00:00
|
|
|
this._ref,
|
2023-06-10 18:13:59 +00:00
|
|
|
) : super(false);
|
2022-10-15 21:20:15 +00:00
|
|
|
|
2023-03-27 02:35:52 +00:00
|
|
|
Future<void> getAllAsset({bool clear = false}) async {
|
2022-11-08 17:00:24 +00:00
|
|
|
if (_getAllAssetInProgress || _deleteInProgress) {
|
|
|
|
// guard against multiple calls to this method while it's still working
|
|
|
|
return;
|
|
|
|
}
|
2023-03-27 02:35:52 +00:00
|
|
|
final stopwatch = Stopwatch()..start();
|
2022-11-08 17:00:24 +00:00
|
|
|
try {
|
|
|
|
_getAllAssetInProgress = true;
|
2023-06-10 18:13:59 +00:00
|
|
|
state = true;
|
2023-03-27 02:35:52 +00:00
|
|
|
if (clear) {
|
|
|
|
await clearAssetsAndAlbums(_db);
|
|
|
|
log.info("Manual refresh requested, cleared assets and albums from db");
|
2023-02-04 20:42:42 +00:00
|
|
|
}
|
2024-05-14 15:35:37 +00:00
|
|
|
final bool changedUsers = await _userService.refreshUsers();
|
2023-03-03 22:38:30 +00:00
|
|
|
final bool newRemote = await _assetService.refreshRemoteAssets();
|
|
|
|
final bool newLocal = await _albumService.refreshDeviceAlbums();
|
2024-05-14 15:35:37 +00:00
|
|
|
debugPrint(
|
|
|
|
"changedUsers: $changedUsers, newRemote: $newRemote, newLocal: $newLocal",
|
|
|
|
);
|
|
|
|
if (newRemote) {
|
|
|
|
_ref.invalidate(memoryFutureProvider);
|
|
|
|
}
|
2023-09-10 12:51:18 +00:00
|
|
|
|
2022-11-27 20:34:19 +00:00
|
|
|
log.info("Load assets: ${stopwatch.elapsedMilliseconds}ms");
|
2022-11-08 17:00:24 +00:00
|
|
|
} finally {
|
|
|
|
_getAllAssetInProgress = false;
|
2023-06-10 18:13:59 +00:00
|
|
|
state = false;
|
2022-10-14 21:57:55 +00:00
|
|
|
}
|
2022-11-26 16:16:02 +00:00
|
|
|
}
|
|
|
|
|
2023-03-03 22:38:30 +00:00
|
|
|
Future<void> clearAllAsset() {
|
2023-03-27 02:35:52 +00:00
|
|
|
return clearAssetsAndAlbums(_db);
|
2022-02-13 21:10:42 +00:00
|
|
|
}
|
2022-02-03 16:06:44 +00:00
|
|
|
|
2023-03-03 22:38:30 +00:00
|
|
|
Future<void> onNewAssetUploaded(Asset newAsset) async {
|
2023-05-17 17:36:02 +00:00
|
|
|
// eTag on device is not valid after partially modifying the assets
|
|
|
|
Store.delete(StoreKey.assetETag);
|
|
|
|
await _syncService.syncNewAssetToDb(newAsset);
|
2022-02-14 16:40:41 +00:00
|
|
|
}
|
|
|
|
|
2024-01-17 03:28:23 +00:00
|
|
|
Future<bool> deleteLocalOnlyAssets(
|
|
|
|
Iterable<Asset> deleteAssets, {
|
|
|
|
bool onlyBackedUp = false,
|
|
|
|
}) async {
|
|
|
|
_deleteInProgress = true;
|
|
|
|
state = true;
|
|
|
|
try {
|
2024-11-01 14:03:03 +00:00
|
|
|
// Filter the assets based on the backed-up status
|
2024-01-17 03:28:23 +00:00
|
|
|
final assets = onlyBackedUp
|
|
|
|
? deleteAssets.where((e) => e.storage == AssetState.merged)
|
|
|
|
: deleteAssets;
|
2024-11-01 14:03:03 +00:00
|
|
|
|
|
|
|
if (assets.isEmpty) {
|
|
|
|
return false; // No assets to delete
|
|
|
|
}
|
|
|
|
|
|
|
|
// Proceed with local deletion of the filtered assets
|
2024-01-17 03:28:23 +00:00
|
|
|
final localDeleted = await _deleteLocalAssets(assets);
|
2024-11-01 14:03:03 +00:00
|
|
|
|
2024-01-17 03:28:23 +00:00
|
|
|
if (localDeleted.isNotEmpty) {
|
2024-11-01 14:03:03 +00:00
|
|
|
final localOnlyIds = assets
|
2024-01-17 03:28:23 +00:00
|
|
|
.where((e) => e.storage == AssetState.local)
|
|
|
|
.map((e) => e.id)
|
|
|
|
.toList();
|
2024-11-01 14:03:03 +00:00
|
|
|
|
|
|
|
// Update merged assets to remote-only
|
2024-01-17 03:28:23 +00:00
|
|
|
final mergedAssets =
|
2024-11-01 14:03:03 +00:00
|
|
|
assets.where((e) => e.storage == AssetState.merged).map((e) {
|
2024-01-17 03:28:23 +00:00
|
|
|
e.localId = null;
|
|
|
|
return e;
|
|
|
|
}).toList();
|
2024-11-01 14:03:03 +00:00
|
|
|
|
|
|
|
// Update the local database
|
2024-01-17 03:28:23 +00:00
|
|
|
await _db.writeTxn(() async {
|
|
|
|
if (mergedAssets.isNotEmpty) {
|
2024-11-01 14:03:03 +00:00
|
|
|
await _db.assets
|
|
|
|
.putAll(mergedAssets); // Use the filtered merged assets
|
2024-01-17 03:28:23 +00:00
|
|
|
}
|
|
|
|
await _db.exifInfos.deleteAll(localOnlyIds);
|
|
|
|
await _db.assets.deleteAll(localOnlyIds);
|
|
|
|
});
|
2024-11-01 14:03:03 +00:00
|
|
|
|
2024-01-17 03:28:23 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
_deleteInProgress = false;
|
|
|
|
state = false;
|
|
|
|
}
|
2024-11-01 14:03:03 +00:00
|
|
|
|
2024-01-17 03:28:23 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-10-06 07:01:14 +00:00
|
|
|
Future<bool> deleteAssets(
|
|
|
|
Iterable<Asset> deleteAssets, {
|
2023-10-25 16:02:59 +00:00
|
|
|
bool force = false,
|
2023-10-06 07:01:14 +00:00
|
|
|
}) async {
|
2022-11-08 17:00:24 +00:00
|
|
|
_deleteInProgress = true;
|
2023-06-10 18:13:59 +00:00
|
|
|
state = true;
|
2022-11-08 17:00:24 +00:00
|
|
|
try {
|
2024-01-17 03:28:23 +00:00
|
|
|
final hasLocal = deleteAssets.any((a) => a.storage != AssetState.remote);
|
2022-11-08 17:00:24 +00:00
|
|
|
final localDeleted = await _deleteLocalAssets(deleteAssets);
|
2024-01-17 03:28:23 +00:00
|
|
|
final remoteDeleted = (hasLocal && localDeleted.isNotEmpty) || !hasLocal
|
|
|
|
? await _deleteRemoteAssets(deleteAssets, force)
|
|
|
|
: [];
|
2023-03-03 22:38:30 +00:00
|
|
|
if (localDeleted.isNotEmpty || remoteDeleted.isNotEmpty) {
|
2023-10-25 16:02:59 +00:00
|
|
|
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;
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2023-10-06 07:01:14 +00:00
|
|
|
}
|
2023-10-25 16:02:59 +00: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;
|
|
|
|
}),
|
|
|
|
);
|
2023-10-06 07:01:14 +00:00
|
|
|
}
|
2023-10-25 16:02:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
await _db.writeTxn(() async {
|
|
|
|
await _db.assets.putAll(dbUpdates);
|
2023-03-03 22:38:30 +00:00
|
|
|
await _db.exifInfos.deleteAll(dbIds);
|
|
|
|
await _db.assets.deleteAll(dbIds);
|
|
|
|
});
|
2023-10-06 07:01:14 +00:00
|
|
|
return true;
|
2022-11-08 17:00:24 +00:00
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
_deleteInProgress = false;
|
2023-06-10 18:13:59 +00:00
|
|
|
state = false;
|
2022-11-08 17:00:24 +00:00
|
|
|
}
|
2023-10-06 07:01:14 +00:00
|
|
|
return false;
|
2022-11-08 17:00:24 +00:00
|
|
|
}
|
|
|
|
|
2023-06-27 17:25:00 +00:00
|
|
|
Future<List<String>> _deleteLocalAssets(
|
|
|
|
Iterable<Asset> assetsToDelete,
|
|
|
|
) async {
|
2023-06-10 18:13:59 +00:00
|
|
|
final List<String> local =
|
|
|
|
assetsToDelete.where((a) => a.isLocal).map((a) => a.localId!).toList();
|
2022-02-13 21:10:42 +00:00
|
|
|
// Delete asset from device
|
2022-11-08 17:00:24 +00:00
|
|
|
if (local.isNotEmpty) {
|
|
|
|
try {
|
2024-09-18 15:15:52 +00:00
|
|
|
return await _ref.read(assetMediaRepositoryProvider).deleteAll(local);
|
2022-11-27 20:34:19 +00:00
|
|
|
} catch (e, stack) {
|
|
|
|
log.severe("Failed to delete asset from device", e, stack);
|
2022-02-07 02:31:32 +00:00
|
|
|
}
|
|
|
|
}
|
2022-11-08 17:00:24 +00:00
|
|
|
return [];
|
|
|
|
}
|
2022-10-15 21:20:15 +00:00
|
|
|
|
2024-01-17 03:28:23 +00:00
|
|
|
Future<List<Asset>> _deleteRemoteAssets(
|
2023-06-27 17:25:00 +00:00
|
|
|
Iterable<Asset> assetsToDelete,
|
2023-10-06 07:01:14 +00:00
|
|
|
bool? force,
|
2022-11-08 17:00:24 +00:00
|
|
|
) async {
|
2023-02-04 20:42:42 +00:00
|
|
|
final Iterable<Asset> remote = assetsToDelete.where((e) => e.isRemote);
|
2023-10-06 07:01:14 +00:00
|
|
|
|
|
|
|
final isSuccess = await _assetService.deleteAssets(remote, force: force);
|
2024-01-17 03:28:23 +00:00
|
|
|
return isSuccess ? remote.toList() : [];
|
2022-02-07 02:31:32 +00:00
|
|
|
}
|
2023-02-05 03:25:11 +00:00
|
|
|
|
2024-09-30 14:37:30 +00:00
|
|
|
Future<void> toggleFavorite(List<Asset> assets, [bool? status]) {
|
2023-12-07 15:38:22 +00:00
|
|
|
status ??= !assets.every((a) => a.isFavorite);
|
2024-09-30 14:37:30 +00:00
|
|
|
return _assetService.changeFavoriteStatus(assets, status);
|
2023-02-05 03:25:11 +00:00
|
|
|
}
|
2023-04-17 05:02:07 +00:00
|
|
|
|
2024-09-30 14:37:30 +00:00
|
|
|
Future<void> toggleArchive(List<Asset> assets, [bool? status]) {
|
2024-01-27 18:57:51 +00:00
|
|
|
status ??= !assets.every((a) => a.isArchived);
|
2024-09-30 14:37:30 +00:00
|
|
|
return _assetService.changeArchiveStatus(assets, status);
|
2023-04-17 05:02:07 +00:00
|
|
|
}
|
2022-02-03 16:06:44 +00:00
|
|
|
}
|
|
|
|
|
2023-06-10 18:13:59 +00:00
|
|
|
final assetProvider = StateNotifierProvider<AssetNotifier, bool>((ref) {
|
2022-10-14 21:57:55 +00:00
|
|
|
return AssetNotifier(
|
2022-11-21 12:13:14 +00:00
|
|
|
ref.watch(assetServiceProvider),
|
2023-03-03 22:38:30 +00:00
|
|
|
ref.watch(albumServiceProvider),
|
2023-05-25 03:52:43 +00:00
|
|
|
ref.watch(userServiceProvider),
|
2023-03-27 02:35:52 +00:00
|
|
|
ref.watch(syncServiceProvider),
|
2023-03-03 22:38:30 +00:00
|
|
|
ref.watch(dbProvider),
|
2024-05-14 15:35:37 +00:00
|
|
|
ref,
|
2022-07-13 12:23:48 +00:00
|
|
|
);
|
2022-02-13 21:10:42 +00:00
|
|
|
});
|
2022-04-24 02:08:45 +00:00
|
|
|
|
2023-05-17 17:36:02 +00: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)) {
|
2024-03-07 03:27:33 +00:00
|
|
|
if (a != null) {
|
|
|
|
yield await ref.watch(assetServiceProvider).loadExif(a);
|
|
|
|
}
|
2023-05-17 17:36:02 +00:00
|
|
|
}
|
|
|
|
});
|
2022-04-24 02:08:45 +00:00
|
|
|
|
2023-10-12 02:10:59 +00:00
|
|
|
final assetWatcher =
|
|
|
|
StreamProvider.autoDispose.family<Asset?, Asset>((ref, asset) {
|
|
|
|
final db = ref.watch(dbProvider);
|
|
|
|
return db.assets.watchObject(asset.id, fireImmediately: true);
|
|
|
|
});
|
|
|
|
|
2024-11-19 18:09:29 +00:00
|
|
|
final assetsProvider = StreamProvider.family<RenderList, int?>(
|
|
|
|
(ref, userId) {
|
|
|
|
if (userId == null) return const Stream.empty();
|
|
|
|
ref.watch(localeProvider);
|
|
|
|
final query = _commonFilterAndSort(
|
|
|
|
_assets(ref).where().ownerIdEqualToAnyChecksum(userId),
|
|
|
|
);
|
|
|
|
return renderListGenerator(query, ref);
|
|
|
|
},
|
|
|
|
dependencies: [localeProvider],
|
|
|
|
);
|
|
|
|
|
|
|
|
final multiUserAssetsProvider = StreamProvider.family<RenderList, List<int>>(
|
|
|
|
(ref, userIds) {
|
|
|
|
if (userIds.isEmpty) return const Stream.empty();
|
|
|
|
ref.watch(localeProvider);
|
|
|
|
final query = _commonFilterAndSort(
|
|
|
|
_assets(ref)
|
|
|
|
.where()
|
|
|
|
.anyOf(userIds, (q, u) => q.ownerIdEqualToAnyChecksum(u)),
|
|
|
|
);
|
|
|
|
return renderListGenerator(query, ref);
|
|
|
|
},
|
|
|
|
dependencies: [localeProvider],
|
|
|
|
);
|
2022-07-13 12:23:48 +00:00
|
|
|
|
2023-10-22 02:38:07 +00:00
|
|
|
QueryBuilder<Asset, Asset, QAfterSortBy>? getRemoteAssetQuery(WidgetRef ref) {
|
|
|
|
final userId = ref.watch(currentUserProvider)?.isarId;
|
|
|
|
if (userId == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return ref
|
2023-05-17 17:36:02 +00:00
|
|
|
.watch(dbProvider)
|
|
|
|
.assets
|
|
|
|
.where()
|
|
|
|
.remoteIdIsNotNull()
|
|
|
|
.filter()
|
2023-05-25 03:52:43 +00:00
|
|
|
.ownerIdEqualTo(userId)
|
2023-10-06 07:01:14 +00:00
|
|
|
.isTrashedEqualTo(false)
|
2024-08-19 17:37:15 +00:00
|
|
|
.stackPrimaryAssetIdIsNull()
|
2023-06-18 04:09:55 +00:00
|
|
|
.sortByFileCreatedAtDesc();
|
2023-10-22 02:38:07 +00:00
|
|
|
}
|
2023-11-13 15:54:41 +00: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)
|
2024-08-19 17:37:15 +00:00
|
|
|
.stackPrimaryAssetIdIsNull()
|
2023-11-13 15:54:41 +00:00
|
|
|
.sortByFileCreatedAtDesc();
|
|
|
|
}
|