feat: free up space (#24999)

* feat(server): Support camera `make`, `model`, and `lensModel` in Storage Template (#24650)

* add support for make, model, lensModel in storage template

* no pkg lock

* Apply suggestion from @danieldietzler

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>

* query and formatting

---------

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>

* wip: copy-writing

* feat: cutoff date preset options and filter options

* fix: don't include iCloud Shared Album

* chore: message about excluding shared album assets

* feat: show preview in a separate page

* feat: show clean up hint modal after success deletion

* pr feedback

* pr feedback

* pr feedback

---------

Co-authored-by: Rahul Kumar Saini <rahul-kumar-saini@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
This commit is contained in:
Alex
2026-01-07 20:55:28 -06:00
committed by GitHub
parent 0a9f1a3cbf
commit 1d6a9f6e80
14 changed files with 1488 additions and 0 deletions
@@ -1,6 +1,7 @@
import 'package:collection/collection.dart';
import 'package:drift/drift.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
@@ -126,4 +127,49 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
}
return result;
}
Future<List<LocalAsset>> getRemovalCandidates(
String userId,
DateTime cutoffDate, {
AssetFilterType filterType = AssetFilterType.all,
bool keepFavorites = true,
}) async {
final iosSharedAlbumAssets = _db.localAlbumAssetEntity.selectOnly()
..addColumns([_db.localAlbumAssetEntity.assetId])
..join([
innerJoin(
_db.localAlbumEntity,
_db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id),
useColumns: false,
),
])
..where(_db.localAlbumEntity.isIosSharedAlbum.equals(true));
final query = _db.localAssetEntity.select().join([
innerJoin(_db.remoteAssetEntity, _db.localAssetEntity.checksum.equalsExp(_db.remoteAssetEntity.checksum)),
]);
Expression<bool> whereClause =
_db.localAssetEntity.createdAt.isSmallerOrEqualValue(cutoffDate) &
_db.remoteAssetEntity.ownerId.equals(userId) &
_db.remoteAssetEntity.deletedAt.isNull();
// Exclude assets that are in iOS shared albums
whereClause = whereClause & _db.localAssetEntity.id.isNotInQuery(iosSharedAlbumAssets);
if (filterType == AssetFilterType.photosOnly) {
whereClause = whereClause & _db.localAssetEntity.type.equalsValue(AssetType.image);
} else if (filterType == AssetFilterType.videosOnly) {
whereClause = whereClause & _db.localAssetEntity.type.equalsValue(AssetType.video);
}
if (keepFavorites) {
whereClause = whereClause & _db.localAssetEntity.isFavorite.equals(false);
}
query.where(whereClause);
final rows = await query.get();
return rows.map((row) => row.readTable(_db.localAssetEntity).toDto()).toList();
}
}
@@ -253,6 +253,24 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
origin: origin,
);
TimelineQuery fromAssetsWithBuckets(List<BaseAsset> assets, TimelineOrigin origin) {
// Sort assets by date descending and group by day
final sorted = List<BaseAsset>.from(assets)..sort((a, b) => b.createdAt.compareTo(a.createdAt));
final Map<DateTime, int> bucketCounts = {};
for (final asset in sorted) {
final date = DateTime(asset.createdAt.year, asset.createdAt.month, asset.createdAt.day);
bucketCounts[date] = (bucketCounts[date] ?? 0) + 1;
}
final buckets = bucketCounts.entries.map((e) => TimeBucket(date: e.key, assetCount: e.value)).toList();
return (
bucketSource: () => Stream.value(buckets),
assetSource: (offset, count) => Future.value(sorted.skip(offset).take(count).toList(growable: false)),
origin: origin,
);
}
TimelineQuery remote(String ownerId, GroupAssetsBy groupBy) => _remoteQueryBuilder(
filter: (row) =>
row.deletedAt.isNull() & row.visibility.equalsValue(AssetVisibility.timeline) & row.ownerId.equals(ownerId),