feat: recently added assets page (#28272)

* feat(server): add ordering date option to time buckets

* feat(web): add recently added page

* feat(server): recently created assets in explore data

* feat(web): recently added in explore tab

* fix: recently added assets ordering

* fix(server): failing bucket test

* feat(web): improve recently added preview

* chore: update e2e explore/timeline tests

* chore: rename and refactor timeline ordering dates

* fix(web): invalid timeline option

* feat(mobile): recently added page

* fix(server): sync tests

* fix(mobile): resync assets to get uploadedAt column

* chore: rename assetorderby enum

* chore(mobile): formatting

* minor fixes

* stylings

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
Ben Beckford
2026-05-11 14:35:10 -07:00
committed by GitHub
parent 38438c8d9a
commit e142e3aca7
66 changed files with 14174 additions and 56 deletions
@@ -10,6 +10,7 @@ class RemoteAsset extends BaseAsset {
final AssetVisibility visibility;
final String ownerId;
final String? stackId;
final DateTime? uploadedAt;
const RemoteAsset({
required this.id,
@@ -20,6 +21,7 @@ class RemoteAsset extends BaseAsset {
required super.type,
required super.createdAt,
required super.updatedAt,
this.uploadedAt,
super.width,
super.height,
super.durationMs,
@@ -55,6 +57,7 @@ class RemoteAsset extends BaseAsset {
type: $type,
createdAt: $createdAt,
updatedAt: $updatedAt,
uploadedAt: ${uploadedAt ?? "<NA>"},
width: ${width ?? "<NA>"},
height: ${height ?? "<NA>"},
durationMs: ${durationMs ?? "<NA>"},
@@ -82,7 +85,8 @@ class RemoteAsset extends BaseAsset {
ownerId == other.ownerId &&
thumbHash == other.thumbHash &&
visibility == other.visibility &&
stackId == other.stackId;
stackId == other.stackId &&
uploadedAt == other.uploadedAt;
}
@override
@@ -93,7 +97,8 @@ class RemoteAsset extends BaseAsset {
localId.hashCode ^
thumbHash.hashCode ^
visibility.hashCode ^
stackId.hashCode;
stackId.hashCode ^
uploadedAt.hashCode;
RemoteAsset copyWith({
String? id,
@@ -104,6 +109,7 @@ class RemoteAsset extends BaseAsset {
AssetType? type,
DateTime? createdAt,
DateTime? updatedAt,
DateTime? uploadedAt,
int? width,
int? height,
int? durationMs,
@@ -123,6 +129,7 @@ class RemoteAsset extends BaseAsset {
type: type ?? this.type,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
uploadedAt: uploadedAt ?? this.uploadedAt,
width: width ?? this.width,
height: height ?? this.height,
durationMs: durationMs ?? this.durationMs,
@@ -148,6 +155,7 @@ class RemoteAssetExif extends RemoteAsset {
required super.type,
required super.createdAt,
required super.updatedAt,
super.uploadedAt,
super.width,
super.height,
super.durationMs,
@@ -184,6 +192,7 @@ class RemoteAssetExif extends RemoteAsset {
AssetType? type,
DateTime? createdAt,
DateTime? updatedAt,
DateTime? uploadedAt,
int? width,
int? height,
int? durationMs,
@@ -204,6 +213,7 @@ class RemoteAssetExif extends RemoteAsset {
type: type ?? this.type,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
uploadedAt: uploadedAt ?? this.uploadedAt,
width: width ?? this.width,
height: height ?? this.height,
durationMs: durationMs ?? this.durationMs,
@@ -2,6 +2,8 @@ enum GroupAssetsBy { day, month, auto, none }
enum HeaderType { none, month, day, monthAndDay }
enum SortAssetsBy { taken, uploaded }
class Bucket {
final int assetCount;
@@ -24,6 +24,7 @@ enum SyncMigrationTask {
v20260128_ResetExifV1, // EXIF table has incorrect width and height information.
v20260128_CopyExifWidthHeightToAsset, // Asset table has incorrect width and height for video ratio calculations.
v20260128_ResetAssetV1, // Asset v2.5.0 has width and height information that were edited assets.
v20260597_ResetAssetV1AssetV2, // Assets didn't include the uploadedAt column.
}
class SyncStreamService {
@@ -132,6 +133,13 @@ class SyncStreamService {
migrations.add(SyncMigrationTask.v20260128_CopyExifWidthHeightToAsset.name);
}
}
if (!migrations.contains(SyncMigrationTask.v20260597_ResetAssetV1AssetV2.name) &&
semVer > const SemVer(major: 2, minor: 7, patch: 5)) {
_logger.info("Running pre-sync task: v20260597_ResetAssetV1AssetV2");
await _syncApiRepository.deleteSyncAck([SyncEntityType.assetV1, SyncEntityType.assetV2]);
migrations.add(SyncMigrationTask.v20260597_ResetAssetV1AssetV2.name);
}
}
Future<void> _runPostSyncTasks(List<String> migrations) async {
@@ -35,6 +35,7 @@ enum TimelineOrigin {
deepLink,
albumActivities,
folder,
recentlyAdded,
}
class TimelineFactory {
@@ -61,6 +62,8 @@ class TimelineFactory {
TimelineService remoteAssets(String userId) => TimelineService(_timelineRepository.remote(userId, groupBy));
TimelineService recentlyAdded(String userId) => TimelineService(_timelineRepository.recentlyAdded(userId, groupBy));
TimelineService favorite(String userId) => TimelineService(_timelineRepository.favorite(userId, groupBy));
TimelineService trash(String userId) => TimelineService(_timelineRepository.trash(userId, groupBy));
@@ -11,6 +11,7 @@ extension DTOToAsset on api.AssetResponseDto {
checksum: checksum,
createdAt: fileCreatedAt,
updatedAt: updatedAt,
uploadedAt: createdAt,
ownerId: ownerId,
visibility: visibility.toAssetVisibility(),
durationMs: duration,
@@ -33,6 +34,7 @@ extension DTOToAsset on api.AssetResponseDto {
checksum: checksum,
createdAt: fileCreatedAt,
updatedAt: updatedAt,
uploadedAt: createdAt,
ownerId: ownerId,
visibility: visibility.toAssetVisibility(),
durationMs: duration,
@@ -4,7 +4,7 @@ import 'local_asset.entity.dart';
import 'local_album.entity.dart';
import 'local_album_asset.entity.dart';
mergedAsset:
mergedAsset:
SELECT
rae.id as remote_id,
(SELECT lae.id FROM local_asset_entity lae WHERE lae.checksum = rae.checksum LIMIT 1) as local_id,
@@ -27,7 +27,8 @@ SELECT
NULL as longitude,
NULL as adjustmentTime,
rae.is_edited,
0 as playback_style
0 as playback_style,
rae.uploaded_at
FROM
remote_asset_entity rae
LEFT JOIN
@@ -65,7 +66,8 @@ SELECT
lae.longitude,
lae.adjustment_time,
0 as is_edited,
lae.playback_style
lae.playback_style,
NULL as uploaded_at
FROM
local_asset_entity lae
WHERE NOT EXISTS (
+4 -1
View File
@@ -29,7 +29,7 @@ class MergedAssetDrift extends i1.ModularAccessor {
);
$arrayStartIndex += generatedlimit.amountOfVariables;
return customSelect(
'SELECT rae.id AS remote_id, (SELECT lae.id FROM local_asset_entity AS lae WHERE lae.checksum = rae.checksum LIMIT 1) AS local_id, rae.name, rae.type, rae.created_at AS created_at, rae.updated_at, rae.width, rae.height, rae.duration_ms, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id, NULL AS i_cloud_id, NULL AS latitude, NULL AS longitude, NULL AS adjustmentTime, rae.is_edited, 0 AS playback_style FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT NULL AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.created_at AS created_at, lae.updated_at, lae.width, lae.height, lae.duration_ms, lae.is_favorite, NULL AS thumb_hash, lae.checksum, NULL AS owner_id, NULL AS live_photo_video_id, lae.orientation, NULL AS stack_id, lae.i_cloud_id, lae.latitude, lae.longitude, lae.adjustment_time, 0 AS is_edited, lae.playback_style FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2) ORDER BY created_at DESC ${generatedlimit.sql}',
'SELECT rae.id AS remote_id, (SELECT lae.id FROM local_asset_entity AS lae WHERE lae.checksum = rae.checksum LIMIT 1) AS local_id, rae.name, rae.type, rae.created_at AS created_at, rae.updated_at, rae.width, rae.height, rae.duration_ms, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id, NULL AS i_cloud_id, NULL AS latitude, NULL AS longitude, NULL AS adjustmentTime, rae.is_edited, 0 AS playback_style, rae.uploaded_at FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT NULL AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.created_at AS created_at, lae.updated_at, lae.width, lae.height, lae.duration_ms, lae.is_favorite, NULL AS thumb_hash, lae.checksum, NULL AS owner_id, NULL AS live_photo_video_id, lae.orientation, NULL AS stack_id, lae.i_cloud_id, lae.latitude, lae.longitude, lae.adjustment_time, 0 AS is_edited, lae.playback_style, NULL AS uploaded_at FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2) ORDER BY created_at DESC ${generatedlimit.sql}',
variables: [
for (var $ in userIds) i0.Variable<String>($),
...generatedlimit.introducedVariables,
@@ -68,6 +68,7 @@ class MergedAssetDrift extends i1.ModularAccessor {
adjustmentTime: row.readNullable<DateTime>('adjustmentTime'),
isEdited: row.read<bool>('is_edited'),
playbackStyle: row.read<int>('playback_style'),
uploadedAt: row.readNullable<DateTime>('uploaded_at'),
),
);
}
@@ -141,6 +142,7 @@ class MergedAssetResult {
final DateTime? adjustmentTime;
final bool isEdited;
final int playbackStyle;
final DateTime? uploadedAt;
MergedAssetResult({
this.remoteId,
this.localId,
@@ -164,6 +166,7 @@ class MergedAssetResult {
this.adjustmentTime,
required this.isEdited,
required this.playbackStyle,
this.uploadedAt,
});
}
@@ -43,6 +43,8 @@ class RemoteAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin
DateTimeColumn get deletedAt => dateTime().nullable()();
DateTimeColumn get uploadedAt => dateTime().nullable()();
TextColumn get livePhotoVideoId => text().nullable()();
IntColumn get visibility => intEnum<AssetVisibility>()();
@@ -66,6 +68,7 @@ extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData {
type: type,
createdAt: createdAt,
updatedAt: updatedAt,
uploadedAt: uploadedAt,
durationMs: durationMs,
isFavorite: isFavorite,
height: height,
@@ -27,6 +27,7 @@ typedef $$RemoteAssetEntityTableCreateCompanionBuilder =
i0.Value<DateTime?> localDateTime,
i0.Value<String?> thumbHash,
i0.Value<DateTime?> deletedAt,
i0.Value<DateTime?> uploadedAt,
i0.Value<String?> livePhotoVideoId,
required i2.AssetVisibility visibility,
i0.Value<String?> stackId,
@@ -49,6 +50,7 @@ typedef $$RemoteAssetEntityTableUpdateCompanionBuilder =
i0.Value<DateTime?> localDateTime,
i0.Value<String?> thumbHash,
i0.Value<DateTime?> deletedAt,
i0.Value<DateTime?> uploadedAt,
i0.Value<String?> livePhotoVideoId,
i0.Value<i2.AssetVisibility> visibility,
i0.Value<String?> stackId,
@@ -177,6 +179,11 @@ class $$RemoteAssetEntityTableFilterComposer
builder: (column) => i0.ColumnFilters(column),
);
i0.ColumnFilters<DateTime> get uploadedAt => $composableBuilder(
column: $table.uploadedAt,
builder: (column) => i0.ColumnFilters(column),
);
i0.ColumnFilters<String> get livePhotoVideoId => $composableBuilder(
column: $table.livePhotoVideoId,
builder: (column) => i0.ColumnFilters(column),
@@ -305,6 +312,11 @@ class $$RemoteAssetEntityTableOrderingComposer
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<DateTime> get uploadedAt => $composableBuilder(
column: $table.uploadedAt,
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<String> get livePhotoVideoId => $composableBuilder(
column: $table.livePhotoVideoId,
builder: (column) => i0.ColumnOrderings(column),
@@ -412,6 +424,11 @@ class $$RemoteAssetEntityTableAnnotationComposer
i0.GeneratedColumn<DateTime> get deletedAt =>
$composableBuilder(column: $table.deletedAt, builder: (column) => column);
i0.GeneratedColumn<DateTime> get uploadedAt => $composableBuilder(
column: $table.uploadedAt,
builder: (column) => column,
);
i0.GeneratedColumn<String> get livePhotoVideoId => $composableBuilder(
column: $table.livePhotoVideoId,
builder: (column) => column,
@@ -507,6 +524,7 @@ class $$RemoteAssetEntityTableTableManager
i0.Value<DateTime?> localDateTime = const i0.Value.absent(),
i0.Value<String?> thumbHash = const i0.Value.absent(),
i0.Value<DateTime?> deletedAt = const i0.Value.absent(),
i0.Value<DateTime?> uploadedAt = const i0.Value.absent(),
i0.Value<String?> livePhotoVideoId = const i0.Value.absent(),
i0.Value<i2.AssetVisibility> visibility =
const i0.Value.absent(),
@@ -528,6 +546,7 @@ class $$RemoteAssetEntityTableTableManager
localDateTime: localDateTime,
thumbHash: thumbHash,
deletedAt: deletedAt,
uploadedAt: uploadedAt,
livePhotoVideoId: livePhotoVideoId,
visibility: visibility,
stackId: stackId,
@@ -550,6 +569,7 @@ class $$RemoteAssetEntityTableTableManager
i0.Value<DateTime?> localDateTime = const i0.Value.absent(),
i0.Value<String?> thumbHash = const i0.Value.absent(),
i0.Value<DateTime?> deletedAt = const i0.Value.absent(),
i0.Value<DateTime?> uploadedAt = const i0.Value.absent(),
i0.Value<String?> livePhotoVideoId = const i0.Value.absent(),
required i2.AssetVisibility visibility,
i0.Value<String?> stackId = const i0.Value.absent(),
@@ -570,6 +590,7 @@ class $$RemoteAssetEntityTableTableManager
localDateTime: localDateTime,
thumbHash: thumbHash,
deletedAt: deletedAt,
uploadedAt: uploadedAt,
livePhotoVideoId: livePhotoVideoId,
visibility: visibility,
stackId: stackId,
@@ -818,6 +839,18 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
type: i0.DriftSqlType.dateTime,
requiredDuringInsert: false,
);
static const i0.VerificationMeta _uploadedAtMeta = const i0.VerificationMeta(
'uploadedAt',
);
@override
late final i0.GeneratedColumn<DateTime> uploadedAt =
i0.GeneratedColumn<DateTime>(
'uploaded_at',
aliasedName,
true,
type: i0.DriftSqlType.dateTime,
requiredDuringInsert: false,
);
static const i0.VerificationMeta _livePhotoVideoIdMeta =
const i0.VerificationMeta('livePhotoVideoId');
@override
@@ -894,6 +927,7 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
localDateTime,
thumbHash,
deletedAt,
uploadedAt,
livePhotoVideoId,
visibility,
stackId,
@@ -998,6 +1032,12 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
deletedAt.isAcceptableOrUnknown(data['deleted_at']!, _deletedAtMeta),
);
}
if (data.containsKey('uploaded_at')) {
context.handle(
_uploadedAtMeta,
uploadedAt.isAcceptableOrUnknown(data['uploaded_at']!, _uploadedAtMeta),
);
}
if (data.containsKey('live_photo_video_id')) {
context.handle(
_livePhotoVideoIdMeta,
@@ -1095,6 +1135,10 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
i0.DriftSqlType.dateTime,
data['${effectivePrefix}deleted_at'],
),
uploadedAt: attachedDatabase.typeMapping.read(
i0.DriftSqlType.dateTime,
data['${effectivePrefix}uploaded_at'],
),
livePhotoVideoId: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string,
data['${effectivePrefix}live_photo_video_id'],
@@ -1153,6 +1197,7 @@ class RemoteAssetEntityData extends i0.DataClass
final DateTime? localDateTime;
final String? thumbHash;
final DateTime? deletedAt;
final DateTime? uploadedAt;
final String? livePhotoVideoId;
final i2.AssetVisibility visibility;
final String? stackId;
@@ -1173,6 +1218,7 @@ class RemoteAssetEntityData extends i0.DataClass
this.localDateTime,
this.thumbHash,
this.deletedAt,
this.uploadedAt,
this.livePhotoVideoId,
required this.visibility,
this.stackId,
@@ -1212,6 +1258,9 @@ class RemoteAssetEntityData extends i0.DataClass
if (!nullToAbsent || deletedAt != null) {
map['deleted_at'] = i0.Variable<DateTime>(deletedAt);
}
if (!nullToAbsent || uploadedAt != null) {
map['uploaded_at'] = i0.Variable<DateTime>(uploadedAt);
}
if (!nullToAbsent || livePhotoVideoId != null) {
map['live_photo_video_id'] = i0.Variable<String>(livePhotoVideoId);
}
@@ -1252,6 +1301,7 @@ class RemoteAssetEntityData extends i0.DataClass
localDateTime: serializer.fromJson<DateTime?>(json['localDateTime']),
thumbHash: serializer.fromJson<String?>(json['thumbHash']),
deletedAt: serializer.fromJson<DateTime?>(json['deletedAt']),
uploadedAt: serializer.fromJson<DateTime?>(json['uploadedAt']),
livePhotoVideoId: serializer.fromJson<String?>(json['livePhotoVideoId']),
visibility: i1.$RemoteAssetEntityTable.$convertervisibility.fromJson(
serializer.fromJson<int>(json['visibility']),
@@ -1281,6 +1331,7 @@ class RemoteAssetEntityData extends i0.DataClass
'localDateTime': serializer.toJson<DateTime?>(localDateTime),
'thumbHash': serializer.toJson<String?>(thumbHash),
'deletedAt': serializer.toJson<DateTime?>(deletedAt),
'uploadedAt': serializer.toJson<DateTime?>(uploadedAt),
'livePhotoVideoId': serializer.toJson<String?>(livePhotoVideoId),
'visibility': serializer.toJson<int>(
i1.$RemoteAssetEntityTable.$convertervisibility.toJson(visibility),
@@ -1306,6 +1357,7 @@ class RemoteAssetEntityData extends i0.DataClass
i0.Value<DateTime?> localDateTime = const i0.Value.absent(),
i0.Value<String?> thumbHash = const i0.Value.absent(),
i0.Value<DateTime?> deletedAt = const i0.Value.absent(),
i0.Value<DateTime?> uploadedAt = const i0.Value.absent(),
i0.Value<String?> livePhotoVideoId = const i0.Value.absent(),
i2.AssetVisibility? visibility,
i0.Value<String?> stackId = const i0.Value.absent(),
@@ -1328,6 +1380,7 @@ class RemoteAssetEntityData extends i0.DataClass
: this.localDateTime,
thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash,
deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt,
uploadedAt: uploadedAt.present ? uploadedAt.value : this.uploadedAt,
livePhotoVideoId: livePhotoVideoId.present
? livePhotoVideoId.value
: this.livePhotoVideoId,
@@ -1358,6 +1411,9 @@ class RemoteAssetEntityData extends i0.DataClass
: this.localDateTime,
thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash,
deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt,
uploadedAt: data.uploadedAt.present
? data.uploadedAt.value
: this.uploadedAt,
livePhotoVideoId: data.livePhotoVideoId.present
? data.livePhotoVideoId.value
: this.livePhotoVideoId,
@@ -1387,6 +1443,7 @@ class RemoteAssetEntityData extends i0.DataClass
..write('localDateTime: $localDateTime, ')
..write('thumbHash: $thumbHash, ')
..write('deletedAt: $deletedAt, ')
..write('uploadedAt: $uploadedAt, ')
..write('livePhotoVideoId: $livePhotoVideoId, ')
..write('visibility: $visibility, ')
..write('stackId: $stackId, ')
@@ -1412,6 +1469,7 @@ class RemoteAssetEntityData extends i0.DataClass
localDateTime,
thumbHash,
deletedAt,
uploadedAt,
livePhotoVideoId,
visibility,
stackId,
@@ -1436,6 +1494,7 @@ class RemoteAssetEntityData extends i0.DataClass
other.localDateTime == this.localDateTime &&
other.thumbHash == this.thumbHash &&
other.deletedAt == this.deletedAt &&
other.uploadedAt == this.uploadedAt &&
other.livePhotoVideoId == this.livePhotoVideoId &&
other.visibility == this.visibility &&
other.stackId == this.stackId &&
@@ -1459,6 +1518,7 @@ class RemoteAssetEntityCompanion
final i0.Value<DateTime?> localDateTime;
final i0.Value<String?> thumbHash;
final i0.Value<DateTime?> deletedAt;
final i0.Value<DateTime?> uploadedAt;
final i0.Value<String?> livePhotoVideoId;
final i0.Value<i2.AssetVisibility> visibility;
final i0.Value<String?> stackId;
@@ -1479,6 +1539,7 @@ class RemoteAssetEntityCompanion
this.localDateTime = const i0.Value.absent(),
this.thumbHash = const i0.Value.absent(),
this.deletedAt = const i0.Value.absent(),
this.uploadedAt = const i0.Value.absent(),
this.livePhotoVideoId = const i0.Value.absent(),
this.visibility = const i0.Value.absent(),
this.stackId = const i0.Value.absent(),
@@ -1500,6 +1561,7 @@ class RemoteAssetEntityCompanion
this.localDateTime = const i0.Value.absent(),
this.thumbHash = const i0.Value.absent(),
this.deletedAt = const i0.Value.absent(),
this.uploadedAt = const i0.Value.absent(),
this.livePhotoVideoId = const i0.Value.absent(),
required i2.AssetVisibility visibility,
this.stackId = const i0.Value.absent(),
@@ -1526,6 +1588,7 @@ class RemoteAssetEntityCompanion
i0.Expression<DateTime>? localDateTime,
i0.Expression<String>? thumbHash,
i0.Expression<DateTime>? deletedAt,
i0.Expression<DateTime>? uploadedAt,
i0.Expression<String>? livePhotoVideoId,
i0.Expression<int>? visibility,
i0.Expression<String>? stackId,
@@ -1547,6 +1610,7 @@ class RemoteAssetEntityCompanion
if (localDateTime != null) 'local_date_time': localDateTime,
if (thumbHash != null) 'thumb_hash': thumbHash,
if (deletedAt != null) 'deleted_at': deletedAt,
if (uploadedAt != null) 'uploaded_at': uploadedAt,
if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId,
if (visibility != null) 'visibility': visibility,
if (stackId != null) 'stack_id': stackId,
@@ -1570,6 +1634,7 @@ class RemoteAssetEntityCompanion
i0.Value<DateTime?>? localDateTime,
i0.Value<String?>? thumbHash,
i0.Value<DateTime?>? deletedAt,
i0.Value<DateTime?>? uploadedAt,
i0.Value<String?>? livePhotoVideoId,
i0.Value<i2.AssetVisibility>? visibility,
i0.Value<String?>? stackId,
@@ -1591,6 +1656,7 @@ class RemoteAssetEntityCompanion
localDateTime: localDateTime ?? this.localDateTime,
thumbHash: thumbHash ?? this.thumbHash,
deletedAt: deletedAt ?? this.deletedAt,
uploadedAt: uploadedAt ?? this.uploadedAt,
livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId,
visibility: visibility ?? this.visibility,
stackId: stackId ?? this.stackId,
@@ -1646,6 +1712,9 @@ class RemoteAssetEntityCompanion
if (deletedAt.present) {
map['deleted_at'] = i0.Variable<DateTime>(deletedAt.value);
}
if (uploadedAt.present) {
map['uploaded_at'] = i0.Variable<DateTime>(uploadedAt.value);
}
if (livePhotoVideoId.present) {
map['live_photo_video_id'] = i0.Variable<String>(livePhotoVideoId.value);
}
@@ -1683,6 +1752,7 @@ class RemoteAssetEntityCompanion
..write('localDateTime: $localDateTime, ')
..write('thumbHash: $thumbHash, ')
..write('deletedAt: $deletedAt, ')
..write('uploadedAt: $uploadedAt, ')
..write('livePhotoVideoId: $livePhotoVideoId, ')
..write('visibility: $visibility, ')
..write('stackId: $stackId, ')
@@ -98,7 +98,7 @@ class Drift extends $Drift {
}
@override
int get schemaVersion => 25;
int get schemaVersion => 26;
@override
MigrationStrategy get migration => MigrationStrategy(
@@ -267,6 +267,9 @@ class Drift extends $Drift {
from24To25: (m, v25) async {
await m.createTable(v25.metadata);
},
from25To26: (m, v26) async {
await m.addColumn(v26.remoteAssetEntity, v26.remoteAssetEntity.uploadedAt);
},
),
);
@@ -12943,6 +12943,602 @@ i1.GeneratedColumn<String> _column_211(String aliasedName) =>
type: i1.DriftSqlType.string,
$customConstraints: 'NOT NULL',
);
final class Schema26 extends i0.VersionedSchema {
Schema26({required super.database}) : super(version: 26);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
userEntity,
remoteAssetEntity,
stackEntity,
localAssetEntity,
remoteAlbumEntity,
localAlbumEntity,
localAlbumAssetEntity,
idxLocalAlbumAssetAlbumAsset,
idxLocalAssetChecksum,
idxLocalAssetCloudId,
idxStackPrimaryAssetId,
idxRemoteAssetOwnerChecksum,
uQRemoteAssetsOwnerChecksum,
uQRemoteAssetsOwnerLibraryChecksum,
idxRemoteAssetChecksum,
idxRemoteAssetStackId,
idxRemoteAssetLocalDateTimeDay,
idxRemoteAssetLocalDateTimeMonth,
authUserEntity,
userMetadataEntity,
partnerEntity,
remoteExifEntity,
remoteAlbumAssetEntity,
remoteAlbumUserEntity,
remoteAssetCloudIdEntity,
memoryEntity,
memoryAssetEntity,
personEntity,
assetFaceEntity,
storeEntity,
trashedLocalAssetEntity,
assetEditEntity,
metadata,
idxPartnerSharedWithId,
idxLatLng,
idxRemoteAlbumAssetAlbumAsset,
idxRemoteAssetCloudId,
idxPersonOwnerId,
idxAssetFacePersonId,
idxAssetFaceAssetId,
idxTrashedLocalAssetChecksum,
idxTrashedLocalAssetAlbum,
idxAssetEditAssetId,
];
late final Shape33 userEntity = Shape33(
source: i0.VersionedTable(
entityName: 'user_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_107,
_column_108,
_column_109,
_column_110,
_column_111,
_column_112,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape50 remoteAssetEntity = Shape50(
source: i0.VersionedTable(
entityName: 'remote_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_108,
_column_113,
_column_114,
_column_115,
_column_116,
_column_117,
_column_118,
_column_107,
_column_119,
_column_120,
_column_121,
_column_122,
_column_123,
_column_124,
_column_212,
_column_125,
_column_126,
_column_127,
_column_128,
_column_129,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape35 stackEntity = Shape35(
source: i0.VersionedTable(
entityName: 'stack_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_107,
_column_114,
_column_115,
_column_121,
_column_130,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape36 localAssetEntity = Shape36(
source: i0.VersionedTable(
entityName: 'local_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_108,
_column_113,
_column_114,
_column_115,
_column_116,
_column_117,
_column_118,
_column_107,
_column_131,
_column_120,
_column_132,
_column_133,
_column_134,
_column_135,
_column_136,
_column_137,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape48 remoteAlbumEntity = Shape48(
source: i0.VersionedTable(
entityName: 'remote_album_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_107,
_column_108,
_column_138,
_column_114,
_column_115,
_column_139,
_column_140,
_column_141,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape38 localAlbumEntity = Shape38(
source: i0.VersionedTable(
entityName: 'local_album_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_107,
_column_108,
_column_115,
_column_142,
_column_143,
_column_144,
_column_145,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape39 localAlbumAssetEntity = Shape39(
source: i0.VersionedTable(
entityName: 'local_album_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
columns: [_column_146, _column_147, _column_145],
attachedDatabase: database,
),
alias: null,
);
final i1.Index idxLocalAlbumAssetAlbumAsset = i1.Index(
'idx_local_album_asset_album_asset',
'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)',
);
final i1.Index idxLocalAssetChecksum = i1.Index(
'idx_local_asset_checksum',
'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)',
);
final i1.Index idxLocalAssetCloudId = i1.Index(
'idx_local_asset_cloud_id',
'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)',
);
final i1.Index idxStackPrimaryAssetId = i1.Index(
'idx_stack_primary_asset_id',
'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)',
);
final i1.Index idxRemoteAssetOwnerChecksum = i1.Index(
'idx_remote_asset_owner_checksum',
'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)',
);
final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index(
'UQ_remote_assets_owner_checksum',
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)',
);
final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index(
'UQ_remote_assets_owner_library_checksum',
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)',
);
final i1.Index idxRemoteAssetChecksum = i1.Index(
'idx_remote_asset_checksum',
'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)',
);
final i1.Index idxRemoteAssetStackId = i1.Index(
'idx_remote_asset_stack_id',
'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)',
);
final i1.Index idxRemoteAssetLocalDateTimeDay = i1.Index(
'idx_remote_asset_local_date_time_day',
'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME(\'%Y-%m-%d\', local_date_time))',
);
final i1.Index idxRemoteAssetLocalDateTimeMonth = i1.Index(
'idx_remote_asset_local_date_time_month',
'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME(\'%Y-%m\', local_date_time))',
);
late final Shape40 authUserEntity = Shape40(
source: i0.VersionedTable(
entityName: 'auth_user_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_107,
_column_108,
_column_109,
_column_148,
_column_110,
_column_111,
_column_149,
_column_150,
_column_151,
_column_152,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape4 userMetadataEntity = Shape4(
source: i0.VersionedTable(
entityName: 'user_metadata_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(user_id, "key")'],
columns: [_column_153, _column_154, _column_155],
attachedDatabase: database,
),
alias: null,
);
late final Shape41 partnerEntity = Shape41(
source: i0.VersionedTable(
entityName: 'partner_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'],
columns: [_column_156, _column_157, _column_158],
attachedDatabase: database,
),
alias: null,
);
late final Shape42 remoteExifEntity = Shape42(
source: i0.VersionedTable(
entityName: 'remote_exif_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id)'],
columns: [
_column_159,
_column_160,
_column_161,
_column_162,
_column_163,
_column_164,
_column_117,
_column_116,
_column_165,
_column_166,
_column_167,
_column_168,
_column_135,
_column_136,
_column_169,
_column_170,
_column_171,
_column_172,
_column_173,
_column_174,
_column_175,
_column_176,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape7 remoteAlbumAssetEntity = Shape7(
source: i0.VersionedTable(
entityName: 'remote_album_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
columns: [_column_159, _column_177],
attachedDatabase: database,
),
alias: null,
);
late final Shape10 remoteAlbumUserEntity = Shape10(
source: i0.VersionedTable(
entityName: 'remote_album_user_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(album_id, user_id)'],
columns: [_column_177, _column_153, _column_178],
attachedDatabase: database,
),
alias: null,
);
late final Shape43 remoteAssetCloudIdEntity = Shape43(
source: i0.VersionedTable(
entityName: 'remote_asset_cloud_id_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id)'],
columns: [
_column_159,
_column_179,
_column_180,
_column_134,
_column_135,
_column_136,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape44 memoryEntity = Shape44(
source: i0.VersionedTable(
entityName: 'memory_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_107,
_column_114,
_column_115,
_column_124,
_column_121,
_column_113,
_column_181,
_column_182,
_column_183,
_column_184,
_column_185,
_column_186,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape12 memoryAssetEntity = Shape12(
source: i0.VersionedTable(
entityName: 'memory_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'],
columns: [_column_159, _column_187],
attachedDatabase: database,
),
alias: null,
);
late final Shape45 personEntity = Shape45(
source: i0.VersionedTable(
entityName: 'person_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_107,
_column_114,
_column_115,
_column_121,
_column_108,
_column_188,
_column_189,
_column_190,
_column_191,
_column_192,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape46 assetFaceEntity = Shape46(
source: i0.VersionedTable(
entityName: 'asset_face_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_107,
_column_159,
_column_193,
_column_194,
_column_195,
_column_196,
_column_197,
_column_198,
_column_199,
_column_200,
_column_201,
_column_124,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape18 storeEntity = Shape18(
source: i0.VersionedTable(
entityName: 'store_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [_column_202, _column_203, _column_204],
attachedDatabase: database,
),
alias: null,
);
late final Shape47 trashedLocalAssetEntity = Shape47(
source: i0.VersionedTable(
entityName: 'trashed_local_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id, album_id)'],
columns: [
_column_108,
_column_113,
_column_114,
_column_115,
_column_116,
_column_117,
_column_118,
_column_107,
_column_205,
_column_131,
_column_120,
_column_132,
_column_206,
_column_137,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape32 assetEditEntity = Shape32(
source: i0.VersionedTable(
entityName: 'asset_edit_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_107,
_column_159,
_column_207,
_column_208,
_column_209,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape49 metadata = Shape49(
source: i0.VersionedTable(
entityName: 'metadata',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY("key")'],
columns: [_column_210, _column_211, _column_115],
attachedDatabase: database,
),
alias: null,
);
final i1.Index idxPartnerSharedWithId = i1.Index(
'idx_partner_shared_with_id',
'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)',
);
final i1.Index idxLatLng = i1.Index(
'idx_lat_lng',
'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)',
);
final i1.Index idxRemoteAlbumAssetAlbumAsset = i1.Index(
'idx_remote_album_asset_album_asset',
'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)',
);
final i1.Index idxRemoteAssetCloudId = i1.Index(
'idx_remote_asset_cloud_id',
'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)',
);
final i1.Index idxPersonOwnerId = i1.Index(
'idx_person_owner_id',
'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)',
);
final i1.Index idxAssetFacePersonId = i1.Index(
'idx_asset_face_person_id',
'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)',
);
final i1.Index idxAssetFaceAssetId = i1.Index(
'idx_asset_face_asset_id',
'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)',
);
final i1.Index idxTrashedLocalAssetChecksum = i1.Index(
'idx_trashed_local_asset_checksum',
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)',
);
final i1.Index idxTrashedLocalAssetAlbum = i1.Index(
'idx_trashed_local_asset_album',
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)',
);
final i1.Index idxAssetEditAssetId = i1.Index(
'idx_asset_edit_asset_id',
'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)',
);
}
class Shape50 extends i0.VersionedTable {
Shape50({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<String> get name =>
columnsByName['name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get type =>
columnsByName['type']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get createdAt =>
columnsByName['created_at']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get updatedAt =>
columnsByName['updated_at']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get width =>
columnsByName['width']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get height =>
columnsByName['height']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get durationMs =>
columnsByName['duration_ms']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get id =>
columnsByName['id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get checksum =>
columnsByName['checksum']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get isFavorite =>
columnsByName['is_favorite']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get ownerId =>
columnsByName['owner_id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get localDateTime =>
columnsByName['local_date_time']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get thumbHash =>
columnsByName['thumb_hash']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get deletedAt =>
columnsByName['deleted_at']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get uploadedAt =>
columnsByName['uploaded_at']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get livePhotoVideoId =>
columnsByName['live_photo_video_id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get visibility =>
columnsByName['visibility']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get stackId =>
columnsByName['stack_id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get libraryId =>
columnsByName['library_id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get isEdited =>
columnsByName['is_edited']! as i1.GeneratedColumn<int>;
}
i1.GeneratedColumn<String> _column_212(String aliasedName) =>
i1.GeneratedColumn<String>(
'uploaded_at',
aliasedName,
true,
type: i1.DriftSqlType.string,
$customConstraints: 'NULL',
);
i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
@@ -12968,6 +13564,7 @@ i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema23 schema) from22To23,
required Future<void> Function(i1.Migrator m, Schema24 schema) from23To24,
required Future<void> Function(i1.Migrator m, Schema25 schema) from24To25,
required Future<void> Function(i1.Migrator m, Schema26 schema) from25To26,
}) {
return (currentVersion, database) async {
switch (currentVersion) {
@@ -13091,6 +13688,11 @@ i0.MigrationStepWithVersion migrationSteps({
final migrator = i1.Migrator(database, schema);
await from24To25(migrator, schema);
return 25;
case 25:
final schema = Schema26(database: database);
final migrator = i1.Migrator(database, schema);
await from25To26(migrator, schema);
return 26;
default:
throw ArgumentError.value('Unknown migration from $currentVersion');
}
@@ -13122,6 +13724,7 @@ i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, Schema23 schema) from22To23,
required Future<void> Function(i1.Migrator m, Schema24 schema) from23To24,
required Future<void> Function(i1.Migrator m, Schema25 schema) from24To25,
required Future<void> Function(i1.Migrator m, Schema26 schema) from25To26,
}) => i0.VersionedSchema.stepByStepHelper(
step: migrationSteps(
from1To2: from1To2,
@@ -13148,5 +13751,6 @@ i1.OnUpgrade stepByStep({
from22To23: from22To23,
from23To24: from23To24,
from24To25: from24To25,
from25To26: from25To26,
),
);
@@ -191,6 +191,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
type: Value(asset.type.toAssetType()),
createdAt: Value.absentIfNull(asset.fileCreatedAt),
updatedAt: Value.absentIfNull(asset.fileModifiedAt),
uploadedAt: Value(asset.createdAt),
durationMs: Value(asset.duration?.toDuration()?.inMilliseconds ?? 0),
checksum: Value(asset.checksum),
isFavorite: Value(asset.isFavorite),
@@ -229,6 +230,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
type: Value(asset.type.toAssetType()),
createdAt: Value.absentIfNull(asset.fileCreatedAt),
updatedAt: Value.absentIfNull(asset.fileModifiedAt),
uploadedAt: Value(asset.createdAt),
durationMs: Value(asset.duration),
checksum: Value(asset.checksum),
isFavorite: Value(asset.isFavorite),
@@ -79,6 +79,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
type: row.type,
createdAt: row.createdAt,
updatedAt: row.updatedAt,
uploadedAt: row.uploadedAt,
thumbHash: row.thumbHash,
width: row.width,
height: row.height,
@@ -317,6 +318,17 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
origin: TimelineOrigin.remoteAssets,
);
TimelineQuery recentlyAdded(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder(
filter: (row) =>
row.uploadedAt.isNotNull() &
row.deletedAt.isNull() &
row.ownerId.equals(userId) &
(row.visibility.equalsValue(AssetVisibility.timeline) | row.visibility.equalsValue(AssetVisibility.archive)),
origin: TimelineOrigin.recentlyAdded,
groupBy: groupBy,
sortBy: SortAssetsBy.uploaded,
);
TimelineQuery favorite(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder(
filter: (row) =>
row.deletedAt.isNull() &
@@ -597,9 +609,10 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
required TimelineOrigin origin,
GroupAssetsBy groupBy = GroupAssetsBy.day,
bool joinLocal = false,
SortAssetsBy sortBy = SortAssetsBy.taken,
}) {
return (
bucketSource: () => _watchRemoteBucket(filter: filter, groupBy: groupBy),
bucketSource: () => _watchRemoteBucket(filter: filter, groupBy: groupBy, sortBy: sortBy),
assetSource: (offset, count) =>
_getRemoteAssets(filter: filter, offset: offset, count: count, joinLocal: joinLocal),
origin: origin,
@@ -609,6 +622,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
Stream<List<Bucket>> _watchRemoteBucket({
required Expression<bool> Function($RemoteAssetEntityTable row) filter,
GroupAssetsBy groupBy = GroupAssetsBy.day,
SortAssetsBy sortBy = SortAssetsBy.taken,
}) {
if (groupBy == GroupAssetsBy.none) {
final query = _db.remoteAssetEntity.count(where: filter);
@@ -616,7 +630,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
}
final assetCountExp = _db.remoteAssetEntity.id.count();
final dateExp = _db.remoteAssetEntity.effectiveCreatedAt(groupBy);
final dateExp = _db.remoteAssetEntity.effectiveCreatedAt(groupBy, sortBy: sortBy);
final query = _db.remoteAssetEntity.selectOnly()
..addColumns([assetCountExp, dateExp])
@@ -692,8 +706,13 @@ extension on Expression<DateTime> {
}
extension on $RemoteAssetEntityTable {
Expression<String> effectiveCreatedAt(GroupAssetsBy groupBy) =>
coalesce([localDateTime.dateFmt(groupBy), createdAt.dateFmt(groupBy, toLocal: true)]);
Expression<String> effectiveCreatedAt(GroupAssetsBy groupBy, {SortAssetsBy sortBy = SortAssetsBy.taken}) {
if (sortBy == SortAssetsBy.uploaded) {
return uploadedAt.dateFmt(groupBy, toLocal: true);
}
return coalesce([localDateTime.dateFmt(groupBy), createdAt.dateFmt(groupBy, toLocal: true)]);
}
}
extension on String {
@@ -0,0 +1,32 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart';
@RoutePage()
class DriftRecentlyAddedPage extends StatelessWidget {
const DriftRecentlyAddedPage({super.key});
@override
Widget build(BuildContext context) {
return ProviderScope(
overrides: [
timelineServiceProvider.overrideWith((ref) {
final user = ref.watch(currentUserProvider);
if (user == null) {
throw Exception('User must be logged in to access recently taken');
}
final timelineService = ref.watch(timelineFactoryProvider).recentlyAdded(user.id);
ref.onDispose(timelineService.dispose);
return timelineService;
}),
],
child: Timeline(appBar: MesmerizingSliverAppBar(title: 'recently_added'.t())),
);
}
}
@@ -13,6 +13,7 @@ import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/generated/translations.g.dart';
import 'package:immich_mobile/models/search/search_filter.model.dart';
import 'package:immich_mobile/presentation/pages/search/paginated_search.provider.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart';
@@ -879,6 +880,12 @@ class _QuickLinkList extends StatelessWidget {
isTop: true,
onTap: () => context.pushRoute(const DriftRecentlyTakenRoute()),
),
_QuickLink(
title: context.t.recently_added,
icon: Icons.upload_outlined,
isTop: true,
onTap: () => context.pushRoute(const DriftRecentlyAddedRoute()),
),
_QuickLink(
title: 'videos'.t(context: context),
icon: Icons.play_circle_outline_rounded,
+2
View File
@@ -58,6 +58,7 @@ import 'package:immich_mobile/presentation/pages/drift_person.page.dart';
import 'package:immich_mobile/presentation/pages/drift_place.page.dart';
import 'package:immich_mobile/presentation/pages/drift_place_detail.page.dart';
import 'package:immich_mobile/presentation/pages/drift_recently_taken.page.dart';
import 'package:immich_mobile/presentation/pages/drift_recently_added.page.dart';
import 'package:immich_mobile/presentation/pages/drift_remote_album.page.dart';
import 'package:immich_mobile/presentation/pages/drift_trash.page.dart';
import 'package:immich_mobile/presentation/pages/drift_user_selection.page.dart';
@@ -168,6 +169,7 @@ class AppRouter extends RootStackRouter {
AutoRoute(page: DriftAssetSelectionTimelineRoute.page, guards: [_authGuard, _duplicateGuard]),
AutoRoute(page: DriftPartnerDetailRoute.page, guards: [_authGuard, _duplicateGuard]),
AutoRoute(page: DriftRecentlyTakenRoute.page, guards: [_authGuard, _duplicateGuard]),
AutoRoute(page: DriftRecentlyAddedRoute.page, guards: [_authGuard, _duplicateGuard]),
AutoRoute(page: DriftLocalAlbumsRoute.page, guards: [_authGuard, _duplicateGuard]),
AutoRoute(page: DriftCreateAlbumRoute.page, guards: [_authGuard, _duplicateGuard]),
AutoRoute(page: DriftPlaceRoute.page, guards: [_authGuard, _duplicateGuard]),
+16
View File
@@ -1047,6 +1047,22 @@ class DriftPlaceRouteArgs {
int get hashCode => key.hashCode ^ currentLocation.hashCode;
}
/// generated route for
/// [DriftRecentlyAddedPage]
class DriftRecentlyAddedRoute extends PageRouteInfo<void> {
const DriftRecentlyAddedRoute({List<PageRouteInfo>? children})
: super(DriftRecentlyAddedRoute.name, initialChildren: children);
static const String name = 'DriftRecentlyAddedRoute';
static PageInfo page = PageInfo(
name,
builder: (data) {
return const DriftRecentlyAddedPage();
},
);
}
/// generated route for
/// [DriftRecentlyTakenPage]
class DriftRecentlyTakenRoute extends PageRouteInfo<void> {
@@ -329,7 +329,7 @@ class ForegroundUploadService {
'fileCreatedAt': asset.createdAt.toUtc().toIso8601String(),
'fileModifiedAt': asset.updatedAt.toUtc().toIso8601String(),
'isFavorite': asset.isFavorite.toString(),
'duration': asset.duration.toString(),
'duration': (asset.durationMs ?? 0).toString(),
};
// Upload live photo video first if available