1
0
mirror of https://github.com/immich-app/immich.git synced 2025-08-07 23:03:36 +02:00

feat: album edit (#19936)

This commit is contained in:
Alex
2025-07-15 20:37:44 -05:00
committed by GitHub
parent bcb968e3d1
commit 34620e1e9a
22 changed files with 2271 additions and 102 deletions

View File

@ -1,7 +1,13 @@
import 'dart:async';
import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
enum SortRemoteAlbumsBy { id, updatedAt }
@ -99,11 +105,169 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
});
}
Future<void> update(RemoteAlbum album) async {
await _db.remoteAlbumEntity.update().replace(
RemoteAlbumEntityCompanion(
id: Value(album.id),
name: Value(album.name),
ownerId: Value(album.ownerId),
createdAt: Value(album.createdAt),
updatedAt: Value(album.updatedAt),
description: Value(album.description),
thumbnailAssetId: Value(album.thumbnailAssetId),
isActivityEnabled: Value(album.isActivityEnabled),
order: Value(album.order),
),
);
}
Future<int> removeAssets(String albumId, List<String> assetIds) {
return _db.remoteAlbumAssetEntity.deleteWhere(
(tbl) => tbl.albumId.equals(albumId) & tbl.assetId.isIn(assetIds),
);
}
FutureOr<(DateTime, DateTime)> getDateRange(String albumId) {
final query = _db.remoteAlbumAssetEntity.selectOnly()
..where(_db.remoteAlbumAssetEntity.albumId.equals(albumId))
..addColumns([
_db.remoteAssetEntity.createdAt.min(),
_db.remoteAssetEntity.createdAt.max(),
])
..join([
innerJoin(
_db.remoteAssetEntity,
_db.remoteAssetEntity.id
.equalsExp(_db.remoteAlbumAssetEntity.assetId),
),
]);
return query.map((row) {
final minDate = row.read(_db.remoteAssetEntity.createdAt.min());
final maxDate = row.read(_db.remoteAssetEntity.createdAt.max());
return (minDate ?? DateTime.now(), maxDate ?? DateTime.now());
}).getSingle();
}
Future<List<UserDto>> getSharedUsers(String albumId) async {
final albumUserRows = await (_db.select(_db.remoteAlbumUserEntity)
..where((row) => row.albumId.equals(albumId)))
.get();
if (albumUserRows.isEmpty) {
return [];
}
final userIds = albumUserRows.map((row) => row.userId);
return (_db.select(_db.userEntity)..where((row) => row.id.isIn(userIds)))
.map(
(user) => UserDto(
id: user.id,
email: user.email,
name: user.name,
profileImagePath: user.profileImagePath?.isEmpty == true
? null
: user.profileImagePath,
isAdmin: user.isAdmin,
updatedAt: user.updatedAt,
quotaSizeInBytes: user.quotaSizeInBytes ?? 0,
quotaUsageInBytes: user.quotaUsageInBytes,
memoryEnabled: true,
inTimeline: false,
isPartnerSharedBy: false,
isPartnerSharedWith: false,
),
)
.get();
}
Future<List<RemoteAsset>> getAssets(String albumId) {
final query = _db.remoteAlbumAssetEntity.select().join([
innerJoin(
_db.remoteAssetEntity,
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
),
])
..where(_db.remoteAlbumAssetEntity.albumId.equals(albumId));
return query
.map((row) => row.readTable(_db.remoteAssetEntity).toDto())
.get();
}
Future<int> addAssets(String albumId, List<String> assetIds) async {
final albumAssets = assetIds.map(
(assetId) => RemoteAlbumAssetEntityCompanion(
albumId: Value(albumId),
assetId: Value(assetId),
),
);
await _db.batch((batch) {
batch.insertAll(
_db.remoteAlbumAssetEntity,
albumAssets,
);
});
return assetIds.length;
}
Future<void> addUsers(String albumId, List<String> userIds) {
final albumUsers = userIds.map(
(assetId) => RemoteAlbumUserEntityCompanion(
albumId: Value(albumId),
userId: Value(assetId),
role: const Value(AlbumUserRole.editor),
),
);
return _db.batch((batch) {
batch.insertAll(
_db.remoteAlbumUserEntity,
albumUsers,
);
});
}
Future<void> deleteAlbum(String albumId) async {
return _db.transaction(() async {
await _db.remoteAlbumEntity.deleteWhere(
(table) => table.id.equals(albumId),
);
});
}
Stream<RemoteAlbum?> watchAlbum(String albumId) {
final query = _db.remoteAlbumEntity.select().join([
leftOuterJoin(
_db.remoteAlbumAssetEntity,
_db.remoteAlbumAssetEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
useColumns: false,
),
leftOuterJoin(
_db.remoteAssetEntity,
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
useColumns: false,
),
leftOuterJoin(
_db.userEntity,
_db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId),
useColumns: false,
),
])
..where(_db.remoteAlbumEntity.id.equals(albumId))
..addColumns([_db.userEntity.name])
..groupBy([_db.remoteAlbumEntity.id]);
return query.map((row) {
final album = row.readTable(_db.remoteAlbumEntity).toDto(
ownerName: row.read(_db.userEntity.name)!,
);
return album;
}).watchSingleOrNull();
}
}
extension on RemoteAlbumEntityData {

View File

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:drift/drift.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
@ -195,41 +196,75 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
return _db.remoteAlbumAssetEntity
.count(where: (row) => row.albumId.equals(albumId))
.map(_generateBuckets)
.watchSingle();
.watch()
.map((results) => results.isNotEmpty ? results.first : <Bucket>[])
.handleError((error) {
return [];
});
}
final assetCountExp = _db.remoteAssetEntity.id.count();
final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy);
return (_db.remoteAlbumEntity.select()
..where((row) => row.id.equals(albumId)))
.watch()
.switchMap((albums) {
if (albums.isEmpty) {
return Stream.value(<Bucket>[]);
}
final query = _db.remoteAssetEntity.selectOnly()
..addColumns([assetCountExp, dateExp])
..join([
innerJoin(
_db.remoteAlbumAssetEntity,
_db.remoteAlbumAssetEntity.assetId
.equalsExp(_db.remoteAssetEntity.id),
useColumns: false,
),
])
..where(
_db.remoteAssetEntity.deletedAt.isNull() &
_db.remoteAlbumAssetEntity.albumId.equals(albumId),
)
..groupBy([dateExp])
..orderBy([OrderingTerm.desc(dateExp)]);
final album = albums.first;
final isAscending = album.order == AlbumAssetOrder.asc;
final assetCountExp = _db.remoteAssetEntity.id.count();
final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy);
return query.map((row) {
final timeline = row.read(dateExp)!.dateFmt(groupBy);
final assetCount = row.read(assetCountExp)!;
return TimeBucket(date: timeline, assetCount: assetCount);
}).watch();
final query = _db.remoteAssetEntity.selectOnly()
..addColumns([assetCountExp, dateExp])
..join([
innerJoin(
_db.remoteAlbumAssetEntity,
_db.remoteAlbumAssetEntity.assetId
.equalsExp(_db.remoteAssetEntity.id),
useColumns: false,
),
])
..where(
_db.remoteAssetEntity.deletedAt.isNull() &
_db.remoteAlbumAssetEntity.albumId.equals(albumId),
)
..groupBy([dateExp]);
if (isAscending) {
query.orderBy([OrderingTerm.asc(dateExp)]);
} else {
query.orderBy([OrderingTerm.desc(dateExp)]);
}
return query.map((row) {
final timeline = row.read(dateExp)!.dateFmt(groupBy);
final assetCount = row.read(assetCountExp)!;
return TimeBucket(date: timeline, assetCount: assetCount);
}).watch();
}).handleError((error) {
// If there's an error (e.g., album was deleted), return empty buckets
return <Bucket>[];
});
}
Future<List<BaseAsset>> _getRemoteAlbumBucketAssets(
String albumId, {
required int offset,
required int count,
}) {
}) async {
final albumData = await (_db.remoteAlbumEntity.select()
..where((row) => row.id.equals(albumId)))
.getSingleOrNull();
// If album doesn't exist (was deleted), return empty list
if (albumData == null) {
return <BaseAsset>[];
}
final isAscending = albumData.order == AlbumAssetOrder.asc;
final query = _db.remoteAssetEntity.select().join(
[
innerJoin(
@ -239,13 +274,19 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
useColumns: false,
),
],
)
..where(
)..where(
_db.remoteAssetEntity.deletedAt.isNull() &
_db.remoteAlbumAssetEntity.albumId.equals(albumId),
)
..orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)])
..limit(count, offset: offset);
);
if (isAscending) {
query.orderBy([OrderingTerm.asc(_db.remoteAssetEntity.createdAt)]);
} else {
query.orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)]);
}
query.limit(count, offset: offset);
return query
.map((row) => row.readTable(_db.remoteAssetEntity).toDto())
.get();