fix(mobile): load local thumbnails in album timeline (#22329)

* join local asset in album query

* missed one

* formatting
This commit is contained in:
Mert 2025-09-25 15:08:19 -04:00 committed by GitHub
parent c5fbbee8f6
commit 5116b215a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 34 additions and 36 deletions

View file

@ -21,7 +21,7 @@ class LocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
} }
extension LocalAssetEntityDataDomainExtension on LocalAssetEntityData { extension LocalAssetEntityDataDomainExtension on LocalAssetEntityData {
LocalAsset toDto() => LocalAsset( LocalAsset toDto({String? remoteId}) => LocalAsset(
id: id, id: id,
name: name, name: name,
checksum: checksum, checksum: checksum,
@ -32,7 +32,7 @@ extension LocalAssetEntityDataDomainExtension on LocalAssetEntityData {
isFavorite: isFavorite, isFavorite: isFavorite,
height: height, height: height,
width: width, width: width,
remoteId: null, remoteId: remoteId,
orientation: orientation, orientation: orientation,
); );
} }

View file

@ -49,7 +49,7 @@ class RemoteAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin
} }
extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData { extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData {
RemoteAsset toDto() => RemoteAsset( RemoteAsset toDto({String? localId}) => RemoteAsset(
id: id, id: id,
name: name, name: name,
ownerId: ownerId, ownerId: ownerId,
@ -64,7 +64,7 @@ extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData {
thumbHash: thumbHash, thumbHash: thumbHash,
visibility: visibility, visibility: visibility,
livePhotoVideoId: livePhotoVideoId, livePhotoVideoId: livePhotoVideoId,
localId: null, localId: localId,
stackId: stackId, stackId: stackId,
); );
} }

View file

@ -148,10 +148,9 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
..orderBy([OrderingTerm.desc(_db.localAssetEntity.createdAt)]) ..orderBy([OrderingTerm.desc(_db.localAssetEntity.createdAt)])
..limit(count, offset: offset); ..limit(count, offset: offset);
return query.map((row) { return query
final asset = row.readTable(_db.localAssetEntity).toDto(); .map((row) => row.readTable(_db.localAssetEntity).toDto(remoteId: row.read(_db.remoteAssetEntity.id)))
return asset.copyWith(remoteId: row.read(_db.remoteAssetEntity.id)); .get();
}).get();
} }
TimelineQuery remoteAlbum(String albumId, GroupAssetsBy groupBy) => ( TimelineQuery remoteAlbum(String albumId, GroupAssetsBy groupBy) => (
@ -165,17 +164,15 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
.count(where: (row) => row.albumId.equals(albumId)) .count(where: (row) => row.albumId.equals(albumId))
.map(_generateBuckets) .map(_generateBuckets)
.watch() .watch()
.map((results) => results.isNotEmpty ? results.first : <Bucket>[]) .map((results) => results.isNotEmpty ? results.first : const <Bucket>[])
.handleError((error) { .handleError((error) => const <Bucket>[]);
return [];
});
} }
return (_db.remoteAlbumEntity.select()..where((row) => row.id.equals(albumId))) return (_db.remoteAlbumEntity.select()..where((row) => row.id.equals(albumId)))
.watch() .watch()
.switchMap((albums) { .switchMap((albums) {
if (albums.isEmpty) { if (albums.isEmpty) {
return Stream.value(<Bucket>[]); return Stream.value(const <Bucket>[]);
} }
final album = albums.first; final album = albums.first;
@ -207,10 +204,8 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
return TimeBucket(date: timeline, assetCount: assetCount); return TimeBucket(date: timeline, assetCount: assetCount);
}).watch(); }).watch();
}) })
.handleError((error) { // If there's an error (e.g., album was deleted), return empty buckets
// If there's an error (e.g., album was deleted), return empty buckets .handleError((error) => const <Bucket>[]);
return <Bucket>[];
});
} }
Future<List<BaseAsset>> _getRemoteAlbumBucketAssets(String albumId, {required int offset, required int count}) async { Future<List<BaseAsset>> _getRemoteAlbumBucketAssets(String albumId, {required int offset, required int count}) async {
@ -218,18 +213,23 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
// If album doesn't exist (was deleted), return empty list // If album doesn't exist (was deleted), return empty list
if (albumData == null) { if (albumData == null) {
return <BaseAsset>[]; return const <BaseAsset>[];
} }
final isAscending = albumData.order == AlbumAssetOrder.asc; final isAscending = albumData.order == AlbumAssetOrder.asc;
final query = _db.remoteAssetEntity.select().join([ final query = _db.remoteAssetEntity.select().addColumns([_db.localAssetEntity.id]).join([
innerJoin( innerJoin(
_db.remoteAlbumAssetEntity, _db.remoteAlbumAssetEntity,
_db.remoteAlbumAssetEntity.assetId.equalsExp(_db.remoteAssetEntity.id), _db.remoteAlbumAssetEntity.assetId.equalsExp(_db.remoteAssetEntity.id),
useColumns: false, useColumns: false,
), ),
])..where(_db.remoteAssetEntity.deletedAt.isNull() & _db.remoteAlbumAssetEntity.albumId.equals(albumId)); leftOuterJoin(
_db.localAssetEntity,
_db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum),
useColumns: false,
),
])..where(_db.remoteAssetEntity.deletedAt.isNull() & _db.remoteAssetEntity.id.equals(albumId));
if (isAscending) { if (isAscending) {
query.orderBy([OrderingTerm.asc(_db.remoteAssetEntity.createdAt)]); query.orderBy([OrderingTerm.asc(_db.remoteAssetEntity.createdAt)]);
@ -239,12 +239,14 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
query.limit(count, offset: offset); query.limit(count, offset: offset);
return query.map((row) => row.readTable(_db.remoteAssetEntity).toDto()).get(); return query
.map((row) => row.readTable(_db.remoteAssetEntity).toDto(localId: row.read(_db.localAssetEntity.id)))
.get();
} }
TimelineQuery fromAssets(List<BaseAsset> assets) => ( TimelineQuery fromAssets(List<BaseAsset> assets) => (
bucketSource: () => Stream.value(_generateBuckets(assets.length)), bucketSource: () => Stream.value(_generateBuckets(assets.length)),
assetSource: (offset, count) => Future.value(assets.skip(offset).take(count).toList()), assetSource: (offset, count) => Future.value(assets.skip(offset).take(count).toList(growable: false)),
); );
TimelineQuery remote(String ownerId, GroupAssetsBy groupBy) => _remoteQueryBuilder( TimelineQuery remote(String ownerId, GroupAssetsBy groupBy) => _remoteQueryBuilder(
@ -486,6 +488,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
return query.map((row) => row.readTable(_db.remoteAssetEntity).toDto()).get(); return query.map((row) => row.readTable(_db.remoteAssetEntity).toDto()).get();
} }
@pragma('vm:prefer-inline')
TimelineQuery _remoteQueryBuilder({ TimelineQuery _remoteQueryBuilder({
required Expression<bool> Function($RemoteAssetEntityTable row) filter, required Expression<bool> Function($RemoteAssetEntityTable row) filter,
GroupAssetsBy groupBy = GroupAssetsBy.day, GroupAssetsBy groupBy = GroupAssetsBy.day,
@ -523,6 +526,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
}).watch(); }).watch();
} }
@pragma('vm:prefer-inline')
Future<List<BaseAsset>> _getRemoteAssets({ Future<List<BaseAsset>> _getRemoteAssets({
required Expression<bool> Function($RemoteAssetEntityTable row) filter, required Expression<bool> Function($RemoteAssetEntityTable row) filter,
required int offset, required int offset,
@ -543,11 +547,9 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
..orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)]) ..orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)])
..limit(count, offset: offset); ..limit(count, offset: offset);
return query.map((row) { return query
final asset = row.readTable(_db.remoteAssetEntity).toDto(); .map((row) => row.readTable(_db.remoteAssetEntity).toDto(localId: row.read(_db.localAssetEntity.id)))
final localId = row.read(_db.localAssetEntity.id); .get();
return asset.copyWith(localId: localId);
}).get();
} else { } else {
final query = _db.remoteAssetEntity.select() final query = _db.remoteAssetEntity.select()
..where(filter) ..where(filter)
@ -560,12 +562,12 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
} }
List<Bucket> _generateBuckets(int count) { List<Bucket> _generateBuckets(int count) {
final buckets = List.generate( final buckets = List.filled(
(count / kTimelineNoneSegmentSize).floor(), (count / kTimelineNoneSegmentSize).ceil(),
(_) => const Bucket(assetCount: kTimelineNoneSegmentSize), const Bucket(assetCount: kTimelineNoneSegmentSize),
); );
if (count % kTimelineNoneSegmentSize != 0) { if (count % kTimelineNoneSegmentSize != 0) {
buckets.add(Bucket(assetCount: count % kTimelineNoneSegmentSize)); buckets[buckets.length - 1] = Bucket(assetCount: count % kTimelineNoneSegmentSize);
} }
return buckets; return buckets;
} }
@ -590,10 +592,6 @@ extension on String {
GroupAssetsBy.month => "y-M", GroupAssetsBy.month => "y-M",
GroupAssetsBy.none => throw ArgumentError("GroupAssetsBy.none is not supported for date formatting"), GroupAssetsBy.none => throw ArgumentError("GroupAssetsBy.none is not supported for date formatting"),
}; };
try { return DateFormat(format, 'en').parse(this);
return DateFormat(format, 'en').parse(this);
} catch (e) {
throw FormatException("Invalid date format: $this", e);
}
} }
} }