fix: show only local assets from albums selected for backup (#20050)

* show only local assets from albums selected for backup

# Conflicts:
#	mobile/lib/infrastructure/repositories/db.repository.drift.dart

* ignore backup selection

* fix: backup album ownerId

* fix: backup album ownerId

* only show local only assets that are selected for backup

* add index on visibility and backup selection

* fix: video not playing in search view

* remove safe area from bottom bar

* refactor stack count with a CTE and local asset with a SELECT

* fix lint

* remove redundant COALESCE

* remove stack count from main timeline query

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
shenlong 2025-07-23 19:43:15 +05:30 committed by GitHub
parent 92384c28de
commit 08122d6871
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 916 additions and 935 deletions

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,6 @@ class RemoteAsset extends BaseAsset {
final AssetVisibility visibility; final AssetVisibility visibility;
final String ownerId; final String ownerId;
final String? stackId; final String? stackId;
final int stackCount;
const RemoteAsset({ const RemoteAsset({
required this.id, required this.id,
@ -34,7 +33,6 @@ class RemoteAsset extends BaseAsset {
this.visibility = AssetVisibility.timeline, this.visibility = AssetVisibility.timeline,
super.livePhotoVideoId, super.livePhotoVideoId,
this.stackId, this.stackId,
this.stackCount = 0,
}); });
@override @override
@ -61,7 +59,6 @@ class RemoteAsset extends BaseAsset {
thumbHash: ${thumbHash ?? "<NA>"}, thumbHash: ${thumbHash ?? "<NA>"},
visibility: $visibility, visibility: $visibility,
stackId: ${stackId ?? "<NA>"}, stackId: ${stackId ?? "<NA>"},
stackCount: $stackCount,
checksum: $checksum, checksum: $checksum,
livePhotoVideoId: ${livePhotoVideoId ?? "<NA>"}, livePhotoVideoId: ${livePhotoVideoId ?? "<NA>"},
}'''; }''';
@ -77,8 +74,7 @@ class RemoteAsset extends BaseAsset {
ownerId == other.ownerId && ownerId == other.ownerId &&
thumbHash == other.thumbHash && thumbHash == other.thumbHash &&
visibility == other.visibility && visibility == other.visibility &&
stackId == other.stackId && stackId == other.stackId;
stackCount == other.stackCount;
} }
@override @override
@ -89,8 +85,7 @@ class RemoteAsset extends BaseAsset {
localId.hashCode ^ localId.hashCode ^
thumbHash.hashCode ^ thumbHash.hashCode ^
visibility.hashCode ^ visibility.hashCode ^
stackId.hashCode ^ stackId.hashCode;
stackCount.hashCode;
RemoteAsset copyWith({ RemoteAsset copyWith({
String? id, String? id,
@ -109,7 +104,6 @@ class RemoteAsset extends BaseAsset {
AssetVisibility? visibility, AssetVisibility? visibility,
String? livePhotoVideoId, String? livePhotoVideoId,
String? stackId, String? stackId,
int? stackCount,
}) { }) {
return RemoteAsset( return RemoteAsset(
id: id ?? this.id, id: id ?? this.id,
@ -128,7 +122,6 @@ class RemoteAsset extends BaseAsset {
visibility: visibility ?? this.visibility, visibility: visibility ?? this.visibility,
livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId,
stackId: stackId ?? this.stackId, stackId: stackId ?? this.stackId,
stackCount: stackCount ?? this.stackCount,
); );
} }
} }

View File

@ -1,76 +1,68 @@
import 'remote_asset.entity.dart'; import 'remote_asset.entity.dart';
import 'local_asset.entity.dart';
import 'stack.entity.dart'; import 'stack.entity.dart';
import 'local_asset.entity.dart';
import 'local_album.entity.dart';
import 'local_album_asset.entity.dart';
mergedAsset: SELECT * FROM mergedAsset:
( SELECT
SELECT rae.id as remote_id,
rae.id as remote_id, (SELECT lae.id FROM local_asset_entity lae WHERE lae.checksum = rae.checksum LIMIT 1) as local_id,
lae.id as local_id, rae.name,
rae.name, rae."type",
rae."type", rae.created_at as created_at,
rae.created_at, rae.updated_at,
rae.updated_at, rae.width,
rae.width, rae.height,
rae.height, rae.duration_in_seconds,
rae.duration_in_seconds, rae.is_favorite,
rae.is_favorite, rae.thumb_hash,
rae.thumb_hash, rae.checksum,
rae.checksum, rae.owner_id,
rae.owner_id, rae.live_photo_video_id,
rae.live_photo_video_id, 0 as orientation,
0 as orientation, rae.stack_id
rae.stack_id, FROM
COALESCE(stack_count.total_count, 0) AS stack_count remote_asset_entity rae
FROM LEFT JOIN
remote_asset_entity rae stack_entity se ON rae.stack_id = se.id
LEFT JOIN WHERE
local_asset_entity lae ON rae.checksum = lae.checksum rae.deleted_at IS NULL
LEFT JOIN AND rae.visibility = 0 -- timeline visibility
stack_entity se ON rae.stack_id = se.id AND rae.owner_id in ?
LEFT JOIN AND (
(SELECT rae.stack_id IS NULL
stack_id, OR rae.id = se.primary_asset_id
COUNT(*) AS total_count )
FROM remote_asset_entity
WHERE deleted_at IS NULL UNION ALL
AND visibility = 0
AND stack_id IS NOT NULL SELECT
GROUP BY stack_id NULL as remote_id,
) AS stack_count ON rae.stack_id = stack_count.stack_id lae.id as local_id,
WHERE lae.name,
rae.deleted_at IS NULL lae."type",
AND rae.visibility = 0 lae.created_at as created_at,
AND rae.owner_id in ? lae.updated_at,
AND ( lae.width,
rae.stack_id IS NULL lae.height,
OR rae.id = se.primary_asset_id lae.duration_in_seconds,
) lae.is_favorite,
UNION ALL NULL as thumb_hash,
SELECT lae.checksum,
NULL as remote_id, NULL as owner_id,
lae.id as local_id, NULL as live_photo_video_id,
lae.name, lae.orientation,
lae."type", NULL as stack_id
lae.created_at, FROM
lae.updated_at, local_asset_entity lae
lae.width, WHERE NOT EXISTS (
lae.height, SELECT 1 FROM remote_asset_entity rae WHERE rae.checksum = lae.checksum
lae.duration_in_seconds, )
lae.is_favorite, AND EXISTS (
NULL as thumb_hash, SELECT 1 FROM local_album_asset_entity laa
lae.checksum, INNER JOIN local_album_entity la on laa.album_id = la.id
NULL as owner_id, WHERE laa.asset_id = lae.id AND la.backup_selection = 0 -- selected
NULL as live_photo_video_id,
lae.orientation,
NULL as stack_id,
0 AS stack_count
FROM
local_asset_entity lae
LEFT JOIN
remote_asset_entity rae ON rae.checksum = lae.checksum
WHERE
rae.id IS NULL
) )
ORDER BY created_at DESC ORDER BY created_at DESC
LIMIT $limit; LIMIT $limit;
@ -85,17 +77,14 @@ SELECT
FROM FROM
( (
SELECT SELECT
rae.name,
rae.created_at rae.created_at
FROM FROM
remote_asset_entity rae remote_asset_entity rae
LEFT JOIN
local_asset_entity lae ON rae.checksum = lae.checksum
LEFT JOIN LEFT JOIN
stack_entity se ON rae.stack_id = se.id stack_entity se ON rae.stack_id = se.id
WHERE WHERE
rae.deleted_at IS NULL rae.deleted_at IS NULL
AND rae.visibility = 0 AND rae.visibility = 0 -- timeline visibility
AND rae.owner_id in ? AND rae.owner_id in ?
AND ( AND (
rae.stack_id IS NULL rae.stack_id IS NULL
@ -103,14 +92,18 @@ FROM
) )
UNION ALL UNION ALL
SELECT SELECT
lae.name,
lae.created_at lae.created_at
FROM FROM
local_asset_entity lae local_asset_entity lae
LEFT JOIN LEFT JOIN
remote_asset_entity rae ON rae.checksum = lae.checksum remote_asset_entity rae ON rae.checksum = lae.checksum
LEFT JOIN
local_album_asset_entity laa ON laa.asset_id = lae.id
LEFT JOIN
local_album_entity la ON la.id = laa.album_id
WHERE WHERE
rae.id IS NULL rae.id IS NULL
AND la.backup_selection = 0 -- selected
) )
GROUP BY bucket_date GROUP BY bucket_date
ORDER BY bucket_date DESC; ORDER BY bucket_date DESC;

View File

@ -3,24 +3,29 @@
import 'package:drift/drift.dart' as i0; import 'package:drift/drift.dart' as i0;
import 'package:drift/internal/modular.dart' as i1; import 'package:drift/internal/modular.dart' as i1;
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' as i2; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' as i2;
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'
as i3;
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart' import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'
as i3;
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'
as i4; as i4;
import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart' import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart'
as i5; as i5;
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart'
as i6;
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart'
as i7;
class MergedAssetDrift extends i1.ModularAccessor { class MergedAssetDrift extends i1.ModularAccessor {
MergedAssetDrift(i0.GeneratedDatabase db) : super(db); MergedAssetDrift(i0.GeneratedDatabase db) : super(db);
i0.Selectable<MergedAssetResult> mergedAsset(List<String> var1, i0.Selectable<MergedAssetResult> mergedAsset(List<String> var1,
{required i0.Limit limit}) { {required MergedAsset$limit limit}) {
var $arrayStartIndex = 1; var $arrayStartIndex = 1;
final expandedvar1 = $expandVar($arrayStartIndex, var1.length); final expandedvar1 = $expandVar($arrayStartIndex, var1.length);
$arrayStartIndex += var1.length; $arrayStartIndex += var1.length;
final generatedlimit = $write(limit, startIndex: $arrayStartIndex); final generatedlimit = $write(limit(alias(this.localAssetEntity, 'lae')),
startIndex: $arrayStartIndex);
$arrayStartIndex += generatedlimit.amountOfVariables; $arrayStartIndex += generatedlimit.amountOfVariables;
return customSelect( return customSelect(
'SELECT * FROM (SELECT rae.id AS remote_id, lae.id AS local_id, rae.name, rae.type, rae.created_at, rae.updated_at, rae.width, rae.height, rae.duration_in_seconds, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id, COALESCE(stack_count.total_count, 0) AS stack_count FROM remote_asset_entity AS rae LEFT JOIN local_asset_entity AS lae ON rae.checksum = lae.checksum LEFT JOIN stack_entity AS se ON rae.stack_id = se.id LEFT JOIN (SELECT stack_id, COUNT(*) AS total_count FROM remote_asset_entity WHERE deleted_at IS NULL AND visibility = 0 AND stack_id IS NOT NULL GROUP BY stack_id) AS stack_count ON rae.stack_id = stack_count.stack_id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandedvar1) 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, lae.updated_at, lae.width, lae.height, lae.duration_in_seconds, 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, 0 AS stack_count FROM local_asset_entity AS lae LEFT JOIN remote_asset_entity AS rae ON rae.checksum = lae.checksum WHERE rae.id IS NULL) 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_in_seconds, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id 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 ($expandedvar1) 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_in_seconds, 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 FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum) 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) ORDER BY created_at DESC ${generatedlimit.sql}',
variables: [ variables: [
for (var $ in var1) i0.Variable<String>($), for (var $ in var1) i0.Variable<String>($),
...generatedlimit.introducedVariables ...generatedlimit.introducedVariables
@ -29,12 +34,14 @@ class MergedAssetDrift extends i1.ModularAccessor {
remoteAssetEntity, remoteAssetEntity,
localAssetEntity, localAssetEntity,
stackEntity, stackEntity,
localAlbumAssetEntity,
localAlbumEntity,
...generatedlimit.watchedTables, ...generatedlimit.watchedTables,
}).map((i0.QueryRow row) => MergedAssetResult( }).map((i0.QueryRow row) => MergedAssetResult(
remoteId: row.readNullable<String>('remote_id'), remoteId: row.readNullable<String>('remote_id'),
localId: row.readNullable<String>('local_id'), localId: row.readNullable<String>('local_id'),
name: row.read<String>('name'), name: row.read<String>('name'),
type: i3.$RemoteAssetEntityTable.$convertertype type: i4.$RemoteAssetEntityTable.$convertertype
.fromSql(row.read<int>('type')), .fromSql(row.read<int>('type')),
createdAt: row.read<DateTime>('created_at'), createdAt: row.read<DateTime>('created_at'),
updatedAt: row.read<DateTime>('updated_at'), updatedAt: row.read<DateTime>('updated_at'),
@ -48,7 +55,6 @@ class MergedAssetDrift extends i1.ModularAccessor {
livePhotoVideoId: row.readNullable<String>('live_photo_video_id'), livePhotoVideoId: row.readNullable<String>('live_photo_video_id'),
orientation: row.read<int>('orientation'), orientation: row.read<int>('orientation'),
stackId: row.readNullable<String>('stack_id'), stackId: row.readNullable<String>('stack_id'),
stackCount: row.read<int>('stack_count'),
)); ));
} }
@ -58,30 +64,39 @@ class MergedAssetDrift extends i1.ModularAccessor {
final expandedvar2 = $expandVar($arrayStartIndex, var2.length); final expandedvar2 = $expandVar($arrayStartIndex, var2.length);
$arrayStartIndex += var2.length; $arrayStartIndex += var2.length;
return customSelect( return customSelect(
'SELECT COUNT(*) AS asset_count, CASE WHEN ?1 = 0 THEN STRFTIME(\'%Y-%m-%d\', created_at, \'localtime\') WHEN ?1 = 1 THEN STRFTIME(\'%Y-%m\', created_at, \'localtime\') END AS bucket_date FROM (SELECT rae.name, rae.created_at FROM remote_asset_entity AS rae LEFT JOIN local_asset_entity AS lae ON rae.checksum = lae.checksum 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 ($expandedvar2) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT lae.name, lae.created_at FROM local_asset_entity AS lae LEFT JOIN remote_asset_entity AS rae ON rae.checksum = lae.checksum WHERE rae.id IS NULL) GROUP BY bucket_date ORDER BY bucket_date DESC', 'SELECT COUNT(*) AS asset_count, CASE WHEN ?1 = 0 THEN STRFTIME(\'%Y-%m-%d\', created_at, \'localtime\') WHEN ?1 = 1 THEN STRFTIME(\'%Y-%m\', created_at, \'localtime\') END AS bucket_date FROM (SELECT rae.created_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 ($expandedvar2) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT lae.created_at FROM local_asset_entity AS lae LEFT JOIN remote_asset_entity AS rae ON rae.checksum = lae.checksum LEFT JOIN local_album_asset_entity AS laa ON laa.asset_id = lae.id LEFT JOIN local_album_entity AS la ON la.id = laa.album_id WHERE rae.id IS NULL AND la.backup_selection = 0) GROUP BY bucket_date ORDER BY bucket_date DESC',
variables: [ variables: [
i0.Variable<int>(groupBy), i0.Variable<int>(groupBy),
for (var $ in var2) i0.Variable<String>($) for (var $ in var2) i0.Variable<String>($)
], ],
readsFrom: { readsFrom: {
remoteAssetEntity, remoteAssetEntity,
localAssetEntity,
stackEntity, stackEntity,
localAssetEntity,
localAlbumAssetEntity,
localAlbumEntity,
}).map((i0.QueryRow row) => MergedBucketResult( }).map((i0.QueryRow row) => MergedBucketResult(
assetCount: row.read<int>('asset_count'), assetCount: row.read<int>('asset_count'),
bucketDate: row.read<String>('bucket_date'), bucketDate: row.read<String>('bucket_date'),
)); ));
} }
i3.$RemoteAssetEntityTable get remoteAssetEntity => i4.$RemoteAssetEntityTable get remoteAssetEntity =>
i1.ReadDatabaseContainer(attachedDatabase) i1.ReadDatabaseContainer(attachedDatabase)
.resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity'); .resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity');
i4.$LocalAssetEntityTable get localAssetEntity =>
i1.ReadDatabaseContainer(attachedDatabase)
.resultSet<i4.$LocalAssetEntityTable>('local_asset_entity');
i5.$StackEntityTable get stackEntity => i5.$StackEntityTable get stackEntity =>
i1.ReadDatabaseContainer(attachedDatabase) i1.ReadDatabaseContainer(attachedDatabase)
.resultSet<i5.$StackEntityTable>('stack_entity'); .resultSet<i5.$StackEntityTable>('stack_entity');
i3.$LocalAssetEntityTable get localAssetEntity =>
i1.ReadDatabaseContainer(attachedDatabase)
.resultSet<i3.$LocalAssetEntityTable>('local_asset_entity');
i6.$LocalAlbumAssetEntityTable get localAlbumAssetEntity =>
i1.ReadDatabaseContainer(attachedDatabase)
.resultSet<i6.$LocalAlbumAssetEntityTable>(
'local_album_asset_entity');
i7.$LocalAlbumEntityTable get localAlbumEntity =>
i1.ReadDatabaseContainer(attachedDatabase)
.resultSet<i7.$LocalAlbumEntityTable>('local_album_entity');
} }
class MergedAssetResult { class MergedAssetResult {
@ -101,7 +116,6 @@ class MergedAssetResult {
final String? livePhotoVideoId; final String? livePhotoVideoId;
final int orientation; final int orientation;
final String? stackId; final String? stackId;
final int stackCount;
MergedAssetResult({ MergedAssetResult({
this.remoteId, this.remoteId,
this.localId, this.localId,
@ -119,10 +133,11 @@ class MergedAssetResult {
this.livePhotoVideoId, this.livePhotoVideoId,
required this.orientation, required this.orientation,
this.stackId, this.stackId,
required this.stackCount,
}); });
} }
typedef MergedAsset$limit = i0.Limit Function(i3.$LocalAssetEntityTable lae);
class MergedBucketResult { class MergedBucketResult {
final int assetCount; final int assetCount;
final String bucketDate; final String bucketDate;

View File

@ -50,7 +50,7 @@ class DriftBackupRepository extends DriftDatabaseRepository {
return query.get().then((rows) => rows.length); return query.get().then((rows) => rows.length);
} }
Future<int> getRemainderCount() async { Future<int> getRemainderCount(String userId) async {
final query = _db.localAlbumAssetEntity.selectOnly(distinct: true) final query = _db.localAlbumAssetEntity.selectOnly(distinct: true)
..addColumns([_db.localAlbumAssetEntity.assetId]) ..addColumns([_db.localAlbumAssetEntity.assetId])
..join([ ..join([
@ -74,7 +74,8 @@ class DriftBackupRepository extends DriftDatabaseRepository {
..where( ..where(
_db.localAlbumEntity.backupSelection _db.localAlbumEntity.backupSelection
.equalsValue(BackupSelection.selected) & .equalsValue(BackupSelection.selected) &
_db.remoteAssetEntity.id.isNull() & (_db.remoteAssetEntity.id.isNull() |
_db.remoteAssetEntity.ownerId.equals(userId).not()) &
_db.localAlbumAssetEntity.assetId _db.localAlbumAssetEntity.assetId
.isNotInQuery(_getExcludedSubquery()), .isNotInQuery(_getExcludedSubquery()),
); );
@ -82,7 +83,7 @@ class DriftBackupRepository extends DriftDatabaseRepository {
return query.get().then((rows) => rows.length); return query.get().then((rows) => rows.length);
} }
Future<int> getBackupCount() async { Future<int> getBackupCount(String userId) async {
final query = _db.localAlbumAssetEntity.selectOnly(distinct: true) final query = _db.localAlbumAssetEntity.selectOnly(distinct: true)
..addColumns( ..addColumns(
[_db.localAlbumAssetEntity.assetId], [_db.localAlbumAssetEntity.assetId],
@ -109,6 +110,7 @@ class DriftBackupRepository extends DriftDatabaseRepository {
_db.localAlbumEntity.backupSelection _db.localAlbumEntity.backupSelection
.equalsValue(BackupSelection.selected) & .equalsValue(BackupSelection.selected) &
_db.remoteAssetEntity.id.isNotNull() & _db.remoteAssetEntity.id.isNotNull() &
_db.remoteAssetEntity.ownerId.equals(userId) &
_db.localAlbumAssetEntity.assetId _db.localAlbumAssetEntity.assetId
.isNotInQuery(_getExcludedSubquery()), .isNotInQuery(_getExcludedSubquery()),
); );
@ -116,7 +118,7 @@ class DriftBackupRepository extends DriftDatabaseRepository {
return query.get().then((rows) => rows.length); return query.get().then((rows) => rows.length);
} }
Future<List<LocalAsset>> getCandidates() async { Future<List<LocalAsset>> getCandidates(String userId) async {
final selectedAlbumIds = _db.localAlbumEntity.selectOnly(distinct: true) final selectedAlbumIds = _db.localAlbumEntity.selectOnly(distinct: true)
..addColumns([_db.localAlbumEntity.id]) ..addColumns([_db.localAlbumEntity.id])
..where( ..where(
@ -141,6 +143,7 @@ class DriftBackupRepository extends DriftDatabaseRepository {
..addColumns([_db.remoteAssetEntity.checksum]) ..addColumns([_db.remoteAssetEntity.checksum])
..where( ..where(
_db.remoteAssetEntity.checksum.equalsExp(lae.checksum) & _db.remoteAssetEntity.checksum.equalsExp(lae.checksum) &
_db.remoteAssetEntity.ownerId.equals(userId) &
lae.checksum.isNotNull(), lae.checksum.isNotNull(),
), ),
) & ) &

View File

@ -4,8 +4,8 @@ import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart'; import 'package:drift_flutter/drift_flutter.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:immich_mobile/domain/interfaces/db.interface.dart'; import 'package:immich_mobile/domain/interfaces/db.interface.dart';
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.dart'; import 'package:immich_mobile/infrastructure/entities/asset_face.entity.dart';
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
@ -97,7 +97,9 @@ class Drift extends $Drift implements IDatabaseRepository {
await m.alterTable(TableMigration(v3.stackEntity)); await m.alterTable(TableMigration(v3.stackEntity));
}, },
from3To4: (m, v4) async { from3To4: (m, v4) async {
// Thumbnail path column got removed from person_entity
await m.alterTable(TableMigration(v4.personEntity)); await m.alterTable(TableMigration(v4.personEntity));
// asset_face_entity is added
await m.create(v4.assetFaceEntity); await m.create(v4.assetFaceEntity);
}, },
), ),

View File

@ -5,17 +5,17 @@ import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'
as i1; as i1;
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart' import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'
as i2; as i2;
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'
as i3;
import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart' import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart'
as i3;
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'
as i4; as i4;
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift.dart'
as i5;
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart'
as i6;
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart' import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart'
as i7; as i5;
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart' import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart'
as i6;
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift.dart'
as i7;
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart'
as i8; as i8;
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart' import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart'
as i9; as i9;
@ -43,17 +43,17 @@ abstract class $Drift extends i0.GeneratedDatabase {
late final i1.$UserEntityTable userEntity = i1.$UserEntityTable(this); late final i1.$UserEntityTable userEntity = i1.$UserEntityTable(this);
late final i2.$RemoteAssetEntityTable remoteAssetEntity = late final i2.$RemoteAssetEntityTable remoteAssetEntity =
i2.$RemoteAssetEntityTable(this); i2.$RemoteAssetEntityTable(this);
late final i3.$LocalAssetEntityTable localAssetEntity = late final i3.$StackEntityTable stackEntity = i3.$StackEntityTable(this);
i3.$LocalAssetEntityTable(this); late final i4.$LocalAssetEntityTable localAssetEntity =
late final i4.$StackEntityTable stackEntity = i4.$StackEntityTable(this); i4.$LocalAssetEntityTable(this);
late final i5.$UserMetadataEntityTable userMetadataEntity = late final i5.$LocalAlbumEntityTable localAlbumEntity =
i5.$UserMetadataEntityTable(this); i5.$LocalAlbumEntityTable(this);
late final i6.$PartnerEntityTable partnerEntity = late final i6.$LocalAlbumAssetEntityTable localAlbumAssetEntity =
i6.$PartnerEntityTable(this); i6.$LocalAlbumAssetEntityTable(this);
late final i7.$LocalAlbumEntityTable localAlbumEntity = late final i7.$UserMetadataEntityTable userMetadataEntity =
i7.$LocalAlbumEntityTable(this); i7.$UserMetadataEntityTable(this);
late final i8.$LocalAlbumAssetEntityTable localAlbumAssetEntity = late final i8.$PartnerEntityTable partnerEntity =
i8.$LocalAlbumAssetEntityTable(this); i8.$PartnerEntityTable(this);
late final i9.$RemoteExifEntityTable remoteExifEntity = late final i9.$RemoteExifEntityTable remoteExifEntity =
i9.$RemoteExifEntityTable(this); i9.$RemoteExifEntityTable(this);
late final i10.$RemoteAlbumEntityTable remoteAlbumEntity = late final i10.$RemoteAlbumEntityTable remoteAlbumEntity =
@ -77,15 +77,15 @@ abstract class $Drift extends i0.GeneratedDatabase {
List<i0.DatabaseSchemaEntity> get allSchemaEntities => [ List<i0.DatabaseSchemaEntity> get allSchemaEntities => [
userEntity, userEntity,
remoteAssetEntity, remoteAssetEntity,
localAssetEntity,
stackEntity, stackEntity,
i3.idxLocalAssetChecksum, localAssetEntity,
localAlbumEntity,
localAlbumAssetEntity,
i4.idxLocalAssetChecksum,
i2.uQRemoteAssetOwnerChecksum, i2.uQRemoteAssetOwnerChecksum,
i2.idxRemoteAssetChecksum, i2.idxRemoteAssetChecksum,
userMetadataEntity, userMetadataEntity,
partnerEntity, partnerEntity,
localAlbumEntity,
localAlbumAssetEntity,
remoteExifEntity, remoteExifEntity,
remoteAlbumEntity, remoteAlbumEntity,
remoteAlbumAssetEntity, remoteAlbumAssetEntity,
@ -113,6 +113,22 @@ abstract class $Drift extends i0.GeneratedDatabase {
i0.TableUpdate('stack_entity', kind: i0.UpdateKind.delete), i0.TableUpdate('stack_entity', kind: i0.UpdateKind.delete),
], ],
), ),
i0.WritePropagation(
on: i0.TableUpdateQuery.onTableName('local_asset_entity',
limitUpdateKind: i0.UpdateKind.delete),
result: [
i0.TableUpdate('local_album_asset_entity',
kind: i0.UpdateKind.delete),
],
),
i0.WritePropagation(
on: i0.TableUpdateQuery.onTableName('local_album_entity',
limitUpdateKind: i0.UpdateKind.delete),
result: [
i0.TableUpdate('local_album_asset_entity',
kind: i0.UpdateKind.delete),
],
),
i0.WritePropagation( i0.WritePropagation(
on: i0.TableUpdateQuery.onTableName('user_entity', on: i0.TableUpdateQuery.onTableName('user_entity',
limitUpdateKind: i0.UpdateKind.delete), limitUpdateKind: i0.UpdateKind.delete),
@ -135,22 +151,6 @@ abstract class $Drift extends i0.GeneratedDatabase {
i0.TableUpdate('partner_entity', kind: i0.UpdateKind.delete), i0.TableUpdate('partner_entity', kind: i0.UpdateKind.delete),
], ],
), ),
i0.WritePropagation(
on: i0.TableUpdateQuery.onTableName('local_asset_entity',
limitUpdateKind: i0.UpdateKind.delete),
result: [
i0.TableUpdate('local_album_asset_entity',
kind: i0.UpdateKind.delete),
],
),
i0.WritePropagation(
on: i0.TableUpdateQuery.onTableName('local_album_entity',
limitUpdateKind: i0.UpdateKind.delete),
result: [
i0.TableUpdate('local_album_asset_entity',
kind: i0.UpdateKind.delete),
],
),
i0.WritePropagation( i0.WritePropagation(
on: i0.TableUpdateQuery.onTableName('remote_asset_entity', on: i0.TableUpdateQuery.onTableName('remote_asset_entity',
limitUpdateKind: i0.UpdateKind.delete), limitUpdateKind: i0.UpdateKind.delete),
@ -260,18 +260,18 @@ class $DriftManager {
i1.$$UserEntityTableTableManager(_db, _db.userEntity); i1.$$UserEntityTableTableManager(_db, _db.userEntity);
i2.$$RemoteAssetEntityTableTableManager get remoteAssetEntity => i2.$$RemoteAssetEntityTableTableManager get remoteAssetEntity =>
i2.$$RemoteAssetEntityTableTableManager(_db, _db.remoteAssetEntity); i2.$$RemoteAssetEntityTableTableManager(_db, _db.remoteAssetEntity);
i3.$$LocalAssetEntityTableTableManager get localAssetEntity => i3.$$StackEntityTableTableManager get stackEntity =>
i3.$$LocalAssetEntityTableTableManager(_db, _db.localAssetEntity); i3.$$StackEntityTableTableManager(_db, _db.stackEntity);
i4.$$StackEntityTableTableManager get stackEntity => i4.$$LocalAssetEntityTableTableManager get localAssetEntity =>
i4.$$StackEntityTableTableManager(_db, _db.stackEntity); i4.$$LocalAssetEntityTableTableManager(_db, _db.localAssetEntity);
i5.$$UserMetadataEntityTableTableManager get userMetadataEntity => i5.$$LocalAlbumEntityTableTableManager get localAlbumEntity =>
i5.$$UserMetadataEntityTableTableManager(_db, _db.userMetadataEntity); i5.$$LocalAlbumEntityTableTableManager(_db, _db.localAlbumEntity);
i6.$$PartnerEntityTableTableManager get partnerEntity => i6.$$LocalAlbumAssetEntityTableTableManager get localAlbumAssetEntity => i6
i6.$$PartnerEntityTableTableManager(_db, _db.partnerEntity);
i7.$$LocalAlbumEntityTableTableManager get localAlbumEntity =>
i7.$$LocalAlbumEntityTableTableManager(_db, _db.localAlbumEntity);
i8.$$LocalAlbumAssetEntityTableTableManager get localAlbumAssetEntity => i8
.$$LocalAlbumAssetEntityTableTableManager(_db, _db.localAlbumAssetEntity); .$$LocalAlbumAssetEntityTableTableManager(_db, _db.localAlbumAssetEntity);
i7.$$UserMetadataEntityTableTableManager get userMetadataEntity =>
i7.$$UserMetadataEntityTableTableManager(_db, _db.userMetadataEntity);
i8.$$PartnerEntityTableTableManager get partnerEntity =>
i8.$$PartnerEntityTableTableManager(_db, _db.partnerEntity);
i9.$$RemoteExifEntityTableTableManager get remoteExifEntity => i9.$$RemoteExifEntityTableTableManager get remoteExifEntity =>
i9.$$RemoteExifEntityTableTableManager(_db, _db.remoteExifEntity); i9.$$RemoteExifEntityTableTableManager(_db, _db.remoteExifEntity);
i10.$$RemoteAlbumEntityTableTableManager get remoteAlbumEntity => i10.$$RemoteAlbumEntityTableTableManager get remoteAlbumEntity =>

View File

@ -1273,15 +1273,15 @@ final class Schema4 extends i0.VersionedSchema {
late final List<i1.DatabaseSchemaEntity> entities = [ late final List<i1.DatabaseSchemaEntity> entities = [
userEntity, userEntity,
remoteAssetEntity, remoteAssetEntity,
localAssetEntity,
stackEntity, stackEntity,
localAssetEntity,
localAlbumEntity,
localAlbumAssetEntity,
idxLocalAssetChecksum, idxLocalAssetChecksum,
uQRemoteAssetOwnerChecksum, uQRemoteAssetOwnerChecksum,
idxRemoteAssetChecksum, idxRemoteAssetChecksum,
userMetadataEntity, userMetadataEntity,
partnerEntity, partnerEntity,
localAlbumEntity,
localAlbumAssetEntity,
remoteExifEntity, remoteExifEntity,
remoteAlbumEntity, remoteAlbumEntity,
remoteAlbumAssetEntity, remoteAlbumAssetEntity,
@ -1342,6 +1342,24 @@ final class Schema4 extends i0.VersionedSchema {
attachedDatabase: database, attachedDatabase: database,
), ),
alias: null); alias: null);
late final Shape3 stackEntity = Shape3(
source: i0.VersionedTable(
entityName: 'stack_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: [
'PRIMARY KEY(id)',
],
columns: [
_column_0,
_column_9,
_column_5,
_column_15,
_column_75,
],
attachedDatabase: database,
),
alias: null);
late final Shape2 localAssetEntity = Shape2( late final Shape2 localAssetEntity = Shape2(
source: i0.VersionedTable( source: i0.VersionedTable(
entityName: 'local_asset_entity', entityName: 'local_asset_entity',
@ -1366,9 +1384,9 @@ final class Schema4 extends i0.VersionedSchema {
attachedDatabase: database, attachedDatabase: database,
), ),
alias: null); alias: null);
late final Shape3 stackEntity = Shape3( late final Shape6 localAlbumEntity = Shape6(
source: i0.VersionedTable( source: i0.VersionedTable(
entityName: 'stack_entity', entityName: 'local_album_entity',
withoutRowId: true, withoutRowId: true,
isStrict: true, isStrict: true,
tableConstraints: [ tableConstraints: [
@ -1376,10 +1394,26 @@ final class Schema4 extends i0.VersionedSchema {
], ],
columns: [ columns: [
_column_0, _column_0,
_column_9, _column_1,
_column_5, _column_5,
_column_15, _column_31,
_column_75, _column_32,
_column_33,
],
attachedDatabase: database,
),
alias: null);
late final Shape7 localAlbumAssetEntity = Shape7(
source: i0.VersionedTable(
entityName: 'local_album_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: [
'PRIMARY KEY(asset_id, album_id)',
],
columns: [
_column_34,
_column_35,
], ],
attachedDatabase: database, attachedDatabase: database,
), ),
@ -1423,40 +1457,6 @@ final class Schema4 extends i0.VersionedSchema {
attachedDatabase: database, attachedDatabase: database,
), ),
alias: null); alias: null);
late final Shape6 localAlbumEntity = Shape6(
source: i0.VersionedTable(
entityName: 'local_album_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: [
'PRIMARY KEY(id)',
],
columns: [
_column_0,
_column_1,
_column_5,
_column_31,
_column_32,
_column_33,
],
attachedDatabase: database,
),
alias: null);
late final Shape7 localAlbumAssetEntity = Shape7(
source: i0.VersionedTable(
entityName: 'local_album_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: [
'PRIMARY KEY(asset_id, album_id)',
],
columns: [
_column_34,
_column_35,
],
attachedDatabase: database,
),
alias: null);
late final Shape8 remoteExifEntity = Shape8( late final Shape8 remoteExifEntity = Shape8(
source: i0.VersionedTable( source: i0.VersionedTable(
entityName: 'remote_exif_entity', entityName: 'remote_exif_entity',

View File

@ -32,49 +32,21 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
} }
Stream<RemoteAsset?> watchAsset(String id) { Stream<RemoteAsset?> watchAsset(String id) {
final stackCountRef = _db.stackEntity.id.count();
final query = _db.remoteAssetEntity.select().addColumns([ final query = _db.remoteAssetEntity.select().addColumns([
_db.localAssetEntity.id, _db.localAssetEntity.id,
_db.stackEntity.primaryAssetId,
stackCountRef,
]).join([ ]).join([
leftOuterJoin( leftOuterJoin(
_db.localAssetEntity, _db.localAssetEntity,
_db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum), _db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum),
useColumns: false, useColumns: false,
), ),
leftOuterJoin(
_db.stackEntity,
_db.stackEntity.primaryAssetId.equalsExp(_db.remoteAssetEntity.id),
useColumns: false,
),
leftOuterJoin(
_db.remoteAssetEntity.createAlias('stacked_assets'),
_db.stackEntity.id.equalsExp(
_db.remoteAssetEntity.createAlias('stacked_assets').stackId,
),
useColumns: false,
),
]) ])
..where(_db.remoteAssetEntity.id.equals(id)) ..where(_db.remoteAssetEntity.id.equals(id))
..groupBy([
_db.remoteAssetEntity.id,
_db.localAssetEntity.id,
_db.stackEntity.primaryAssetId,
])
..limit(1); ..limit(1);
return query.map((row) { return query.map((row) {
final asset = row.readTable(_db.remoteAssetEntity).toDto(); final asset = row.readTable(_db.remoteAssetEntity).toDto();
final primaryAssetId = row.read(_db.stackEntity.primaryAssetId); return asset.copyWith(localId: row.read(_db.localAssetEntity.id));
final stackCount =
primaryAssetId == id ? (row.read(stackCountRef) ?? 0) : 0;
return asset.copyWith(
localId: row.read(_db.localAssetEntity.id),
stackCount: stackCount,
);
}).watchSingleOrNull(); }).watchSingleOrNull();
} }

View File

@ -71,7 +71,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
required int count, required int count,
}) { }) {
return _db.mergedAssetDrift return _db.mergedAssetDrift
.mergedAsset(userIds, limit: Limit(count, offset)) .mergedAsset(userIds, limit: (_) => Limit(count, offset))
.map( .map(
(row) => row.remoteId != null && row.ownerId != null (row) => row.remoteId != null && row.ownerId != null
? RemoteAsset( ? RemoteAsset(
@ -90,7 +90,6 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
durationInSeconds: row.durationInSeconds, durationInSeconds: row.durationInSeconds,
livePhotoVideoId: row.livePhotoVideoId, livePhotoVideoId: row.livePhotoVideoId,
stackId: row.stackId, stackId: row.stackId,
stackCount: row.stackCount,
) )
: LocalAsset( : LocalAsset(
id: row.localId!, id: row.localId!,

View File

@ -9,6 +9,7 @@ import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/backup/backup_toggle_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/backup/backup_toggle_button.widget.dart';
import 'package:immich_mobile/providers/backup/backup_album.provider.dart'; import 'package:immich_mobile/providers/backup/backup_album.provider.dart';
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/widgets/backup/backup_info_card.dart'; import 'package:immich_mobile/widgets/backup/backup_info_card.dart';
@ -24,12 +25,24 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
ref.read(driftBackupProvider.notifier).getBackupStatus(); final currentUser = ref.read(currentUserProvider);
if (currentUser == null) {
return;
}
ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id);
} }
Future<void> startBackup() async { Future<void> startBackup() async {
await ref.read(driftBackupProvider.notifier).getBackupStatus(); final currentUser = ref.read(currentUserProvider);
await ref.read(driftBackupProvider.notifier).backup(); if (currentUser == null) {
return;
}
await ref
.read(driftBackupProvider.notifier)
.getBackupStatus(currentUser.id);
await ref.read(driftBackupProvider.notifier).backup(currentUser.id);
} }
Future<void> stopBackup() async { Future<void> stopBackup() async {
@ -207,7 +220,13 @@ class _BackupAlbumSelectionCard extends ConsumerWidget {
trailing: ElevatedButton( trailing: ElevatedButton(
onPressed: () async { onPressed: () async {
await context.pushRoute(const DriftBackupAlbumSelectionRoute()); await context.pushRoute(const DriftBackupAlbumSelectionRoute());
ref.read(driftBackupProvider.notifier).getBackupStatus(); final currentUser = ref.read(currentUserProvider);
if (currentUser == null) {
return;
}
ref
.read(driftBackupProvider.notifier)
.getBackupStatus(currentUser.id);
}, },
child: const Text( child: const Text(
"select", "select",

View File

@ -4,17 +4,17 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/album/album.provider.dart'; import 'package:immich_mobile/providers/album/album.provider.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/backup/backup_album.provider.dart'; import 'package:immich_mobile/providers/backup/backup_album.provider.dart';
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/widgets/backup/drift_album_info_list_tile.dart'; import 'package:immich_mobile/widgets/backup/drift_album_info_list_tile.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
import 'package:immich_mobile/widgets/common/search_field.dart'; import 'package:immich_mobile/widgets/common/search_field.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
@RoutePage() @RoutePage()
class DriftBackupAlbumSelectionPage extends ConsumerStatefulWidget { class DriftBackupAlbumSelectionPage extends ConsumerStatefulWidget {
@ -92,7 +92,15 @@ class _DriftBackupAlbumSelectionPageState
if (didPop && !_hasPopped) { if (didPop && !_hasPopped) {
_hasPopped = true; _hasPopped = true;
await ref.read(driftBackupProvider.notifier).getBackupStatus(); super.initState();
final currentUser = ref.read(currentUserProvider);
if (currentUser == null) {
return;
}
await ref
.read(driftBackupProvider.notifier)
.getBackupStatus(currentUser.id);
final currentTotalAssetCount = final currentTotalAssetCount =
ref.read(driftBackupProvider.select((p) => p.totalCount)); ref.read(driftBackupProvider.select((p) => p.totalCount));
@ -107,7 +115,7 @@ class _DriftBackupAlbumSelectionPageState
final backupNotifier = ref.read(driftBackupProvider.notifier); final backupNotifier = ref.read(driftBackupProvider.notifier);
backupNotifier.cancel().then((_) { backupNotifier.cancel().then((_) {
backupNotifier.backup(); backupNotifier.backup(currentUser.id);
}); });
} }
} }

View File

@ -11,6 +11,7 @@ import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/search/search_input_focus.provider.dart'; import 'package:immich_mobile/providers/search/search_input_focus.provider.dart';
import 'package:immich_mobile/providers/tab.provider.dart'; import 'package:immich_mobile/providers/tab.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/providers/websocket.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
@ -38,7 +39,14 @@ class _TabShellPageState extends ConsumerState<TabShellPage> {
await runNewSync(ref, full: true).then((_) async { await runNewSync(ref, full: true).then((_) async {
if (isEnableBackup) { if (isEnableBackup) {
await ref.read(driftBackupProvider.notifier).handleBackupResume(); final currentUser = ref.read(currentUserProvider);
if (currentUser == null) {
return;
}
await ref
.read(driftBackupProvider.notifier)
.handleBackupResume(currentUser.id);
} }
}); });
}); });

View File

@ -6,11 +6,7 @@ class StackChildrenNotifier
extends AutoDisposeFamilyAsyncNotifier<List<RemoteAsset>, BaseAsset?> { extends AutoDisposeFamilyAsyncNotifier<List<RemoteAsset>, BaseAsset?> {
@override @override
Future<List<RemoteAsset>> build(BaseAsset? asset) async { Future<List<RemoteAsset>> build(BaseAsset? asset) async {
if (asset == null || if (asset == null || asset is! RemoteAsset || asset.stackId == null) {
asset is! RemoteAsset ||
asset.stackId == null ||
// The stackCount check is to ensure we only fetch stacks for timelines that have stacks
asset.stackCount == 0) {
return const []; return const [];
} }

View File

@ -54,7 +54,7 @@ class ThumbnailTile extends ConsumerWidget {
: const BoxDecoration(); : const BoxDecoration();
final hasStack = final hasStack =
asset is RemoteAsset && (asset as RemoteAsset).stackCount > 0; asset is RemoteAsset && (asset as RemoteAsset).stackId != null;
return Stack( return Stack(
children: [ children: [
@ -86,9 +86,7 @@ class ThumbnailTile extends ConsumerWidget {
right: 10.0, right: 10.0,
top: asset.isVideo ? 24.0 : 6.0, top: asset.isVideo ? 24.0 : 6.0,
), ),
child: _StackIndicator( child: const _TileOverlayIcon(Icons.burst_mode_rounded),
stackCount: (asset as RemoteAsset).stackCount,
),
), ),
), ),
if (asset.isVideo) if (asset.isVideo)
@ -198,40 +196,6 @@ class _SelectionIndicator extends StatelessWidget {
} }
} }
class _StackIndicator extends StatelessWidget {
final int stackCount;
const _StackIndicator({required this.stackCount});
@override
Widget build(BuildContext context) {
return Row(
spacing: 3,
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
// CrossAxisAlignment.start looks more centered vertically than CrossAxisAlignment.center
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
stackCount.toString(),
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.bold,
shadows: [
Shadow(
blurRadius: 5.0,
color: Color.fromRGBO(0, 0, 0, 0.6),
),
],
),
),
const _TileOverlayIcon(Icons.burst_mode_rounded),
],
);
}
}
class _VideoIndicator extends StatelessWidget { class _VideoIndicator extends StatelessWidget {
final Duration duration; final Duration duration;
const _VideoIndicator(this.duration); const _VideoIndicator(this.duration);

View File

@ -19,6 +19,7 @@ import 'package:immich_mobile/providers/memory.provider.dart';
import 'package:immich_mobile/providers/notification_permission.provider.dart'; import 'package:immich_mobile/providers/notification_permission.provider.dart';
import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/providers/tab.provider.dart'; import 'package:immich_mobile/providers/tab.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/providers/websocket.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/services/background.service.dart'; import 'package:immich_mobile/services/background.service.dart';
@ -110,7 +111,14 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
.getSetting(AppSettingsEnum.enableBackup); .getSetting(AppSettingsEnum.enableBackup);
if (isEnableBackup) { if (isEnableBackup) {
await _ref.read(driftBackupProvider.notifier).handleBackupResume(); final currentUser = _ref.read(currentUserProvider);
if (currentUser == null) {
return;
}
await _ref
.read(driftBackupProvider.notifier)
.handleBackupResume(currentUser.id);
} }
}); });
} catch (e, stackTrace) { } catch (e, stackTrace) {

View File

@ -329,11 +329,11 @@ class ExpBackupNotifier extends StateNotifier<DriftBackupState> {
); );
} }
Future<void> getBackupStatus() async { Future<void> getBackupStatus(String userId) async {
final [totalCount, backupCount, remainderCount] = await Future.wait([ final [totalCount, backupCount, remainderCount] = await Future.wait([
_backupService.getTotalCount(), _backupService.getTotalCount(),
_backupService.getBackupCount(), _backupService.getBackupCount(userId),
_backupService.getRemainderCount(), _backupService.getRemainderCount(userId),
]); ]);
state = state.copyWith( state = state.copyWith(
@ -343,8 +343,8 @@ class ExpBackupNotifier extends StateNotifier<DriftBackupState> {
); );
} }
Future<void> backup() { Future<void> backup(String userId) {
return _backupService.backup(_updateEnqueueCount); return _backupService.backup(userId, _updateEnqueueCount);
} }
void _updateEnqueueCount(EnqueueStatus status) { void _updateEnqueueCount(EnqueueStatus status) {
@ -379,11 +379,11 @@ class ExpBackupNotifier extends StateNotifier<DriftBackupState> {
} }
} }
Future<void> handleBackupResume() async { Future<void> handleBackupResume(String userId) async {
final tasks = await FileDownloader().allTasks(group: kBackupGroup); final tasks = await FileDownloader().allTasks(group: kBackupGroup);
if (tasks.isEmpty) { if (tasks.isEmpty) {
// Start a new backup queue // Start a new backup queue
await backup(); await backup(userId);
} }
debugPrint("Tasks to resume: ${tasks.length}"); debugPrint("Tasks to resume: ${tasks.length}");

View File

@ -48,20 +48,21 @@ class DriftBackupService {
return _backupRepository.getTotalCount(); return _backupRepository.getTotalCount();
} }
Future<int> getRemainderCount() { Future<int> getRemainderCount(String userId) {
return _backupRepository.getRemainderCount(); return _backupRepository.getRemainderCount(userId);
} }
Future<int> getBackupCount() { Future<int> getBackupCount(String userId) {
return _backupRepository.getBackupCount(); return _backupRepository.getBackupCount(userId);
} }
Future<void> backup( Future<void> backup(
String userId,
void Function(EnqueueStatus status) onEnqueueTasks, void Function(EnqueueStatus status) onEnqueueTasks,
) async { ) async {
shouldCancel = false; shouldCancel = false;
final candidates = await _backupRepository.getCandidates(); final candidates = await _backupRepository.getCandidates(userId);
if (candidates.isEmpty) { if (candidates.isEmpty) {
return; return;
} }

File diff suppressed because it is too large Load Diff