mirror of
https://github.com/immich-app/immich
synced 2026-08-15 13:03:57 +00:00
refactor: migrate app metadata
This commit is contained in:
parent
1b0187a664
commit
cca199795d
41 changed files with 4825 additions and 243 deletions
3740
mobile/drift_schemas/main/drift_schema_v33.json
generated
Normal file
3740
mobile/drift_schemas/main/drift_schema_v33.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -2,9 +2,7 @@ import 'dart:async';
|
|||
|
||||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/utils/background_sync.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/main.dart' as app;
|
||||
|
|
@ -34,13 +32,11 @@ void main() {
|
|||
server = await FakeImmichServer.start();
|
||||
await ApiService().resolveAndSetEndpoint(server.endpoint);
|
||||
await drift.delete(drift.userEntity).go();
|
||||
await Store.delete(StoreKey.syncMigrationStatus);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await workerManagerPatch.dispose();
|
||||
await server.close();
|
||||
await Store.delete(StoreKey.syncMigrationStatus);
|
||||
});
|
||||
|
||||
void sendUser(SyncStream stream, String id, String name) {
|
||||
|
|
|
|||
20
mobile/lib/domain/models/app_metadata_key.dart
Normal file
20
mobile/lib/domain/models/app_metadata_key.dart
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import 'package:immich_mobile/domain/models/value_codec.dart';
|
||||
|
||||
const int kCurrentVersion = 29;
|
||||
|
||||
enum AppMetadataKey<T> {
|
||||
version<int>(kCurrentVersion),
|
||||
syncMigrationStatus<List<String>>([], codec: ListCodec(PrimitiveCodec.string)),
|
||||
manageLocalMediaAndroid<bool>(false);
|
||||
|
||||
const AppMetadataKey(this.defaultValue, {ValueCodec<T>? codec}) : _codecOverride = codec;
|
||||
|
||||
final T defaultValue;
|
||||
|
||||
final ValueCodec<T>? _codecOverride;
|
||||
ValueCodec<T> get _codec => _codecOverride ?? ValueCodec.forType(T);
|
||||
|
||||
String encode(T value) => _codec.encode(value);
|
||||
|
||||
T decode(String raw) => _codec.decode(raw);
|
||||
}
|
||||
|
|
@ -1,14 +1,12 @@
|
|||
/// Key for each possible value in the `Store`.
|
||||
/// Defines the data type for each value
|
||||
enum StoreKey<T> {
|
||||
version<int>._(0),
|
||||
deviceId<String>._(4),
|
||||
|
||||
manageLocalMediaAndroid<bool>._(137),
|
||||
|
||||
syncMigrationStatus<String>._(1013),
|
||||
|
||||
// Legacy keys that have been migrated to the new metadata store
|
||||
legacyVersion<int>._(0),
|
||||
legacyManageLocalMediaAndroid<bool>._(137),
|
||||
legacySyncMigrationStatus<String>._(1013),
|
||||
legacyAdvancedTroubleshooting<bool>._(114),
|
||||
legacyEnableHapticFeedback<bool>._(126),
|
||||
legacyReadonlyModeEnabled<bool>._(138),
|
||||
|
|
|
|||
|
|
@ -116,19 +116,19 @@ final class ListCodec<T extends Object> extends ValueCodec<List<T>> {
|
|||
try {
|
||||
final decoded = jsonDecode(raw);
|
||||
if (decoded is! List) {
|
||||
return [];
|
||||
return const [];
|
||||
}
|
||||
final result = <T>[];
|
||||
for (final item in decoded) {
|
||||
if (item is! String) {
|
||||
return [];
|
||||
return const [];
|
||||
}
|
||||
final element = _elementCodec.decode(item);
|
||||
result.add(element);
|
||||
}
|
||||
return result;
|
||||
} on FormatException {
|
||||
return [];
|
||||
return const [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@ import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
|||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset_edit.model.dart';
|
||||
import 'package:immich_mobile/domain/models/exif.model.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/app_metadata.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/remote_exif.repository.dart';
|
||||
|
|
@ -21,6 +20,7 @@ class AssetService {
|
|||
final AssetApiRepository _apiRepository;
|
||||
final AssetMediaRepository _mediaRepository;
|
||||
final DriftTrashedLocalAssetRepository _trashedLocalRepository;
|
||||
final AppMetadataRepository _appMetadataRepository;
|
||||
|
||||
const AssetService({
|
||||
required this._remoteRepository,
|
||||
|
|
@ -29,6 +29,7 @@ class AssetService {
|
|||
required this._apiRepository,
|
||||
required this._mediaRepository,
|
||||
required this._trashedLocalRepository,
|
||||
required this._appMetadataRepository,
|
||||
});
|
||||
|
||||
Future<BaseAsset?> getAsset(BaseAsset asset) {
|
||||
|
|
@ -186,7 +187,7 @@ class AssetService {
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
||||
if (CurrentPlatform.isAndroid && await _appMetadataRepository.get(.manageLocalMediaAndroid)) {
|
||||
await _trashedLocalRepository.applyTrashedAssets(deletedIds);
|
||||
} else {
|
||||
await _localRepository.delete(deletedIds);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
|
|||
import 'package:immich_mobile/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/app_metadata.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||
|
|
@ -83,6 +84,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||
trashedLocalAssetRepository: ref.read(trashedLocalAssetRepository),
|
||||
assetMediaRepository: ref.read(assetMediaRepositoryProvider),
|
||||
permissionRepository: ref.read(permissionRepositoryProvider),
|
||||
appMetadataRepository: ref.read(appMetadataRepositoryProvider),
|
||||
cancellation: _cancellationToken,
|
||||
);
|
||||
_remoteSyncService = SyncStreamService(
|
||||
|
|
@ -94,6 +96,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||
permissionRepository: ref.read(permissionRepositoryProvider),
|
||||
syncMigrationRepository: ref.read(syncMigrationRepositoryProvider),
|
||||
api: ref.read(apiServiceProvider),
|
||||
appMetadataRepository: ref.read(appMetadataRepositoryProvider),
|
||||
cancellation: _cancellationToken,
|
||||
);
|
||||
_hashService = HashService(
|
||||
|
|
|
|||
|
|
@ -5,9 +5,8 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/app_metadata.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
|
||||
|
|
@ -28,6 +27,7 @@ class LocalSyncService {
|
|||
final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository;
|
||||
final AssetMediaRepository _assetMediaRepository;
|
||||
final IPermissionRepository _permissionRepository;
|
||||
final AppMetadataRepository _appMetadataRepository;
|
||||
final Completer<void>? _cancellation;
|
||||
final Logger _log = Logger("DeviceSyncService");
|
||||
|
||||
|
|
@ -38,6 +38,7 @@ class LocalSyncService {
|
|||
required this._trashedLocalAssetRepository,
|
||||
required this._assetMediaRepository,
|
||||
required this._permissionRepository,
|
||||
required this._appMetadataRepository,
|
||||
this._cancellation,
|
||||
}) {
|
||||
unawaited(_cancellation?.future.then((_) => _nativeSyncApi.cancelSync().onError(_log.warning)));
|
||||
|
|
@ -48,7 +49,7 @@ class LocalSyncService {
|
|||
Future<void> sync({bool full = false}) async {
|
||||
final Stopwatch stopwatch = Stopwatch()..start();
|
||||
try {
|
||||
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
||||
if (CurrentPlatform.isAndroid && await _appMetadataRepository.get(.manageLocalMediaAndroid)) {
|
||||
final hasPermission = await _permissionRepository.hasManageMediaPermission();
|
||||
if (hasPermission) {
|
||||
await _syncTrashedAssets();
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/models/sync_event.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/app_metadata.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/sync_migration.repository.dart';
|
||||
|
|
@ -39,6 +37,7 @@ class SyncStreamService {
|
|||
final IPermissionRepository _permissionRepository;
|
||||
final SyncMigrationRepository _syncMigrationRepository;
|
||||
final ApiService _api;
|
||||
final AppMetadataRepository _appMetadataRepository;
|
||||
final Completer<void>? _cancellation;
|
||||
|
||||
SyncStreamService({
|
||||
|
|
@ -50,10 +49,12 @@ class SyncStreamService {
|
|||
required this._permissionRepository,
|
||||
required this._syncMigrationRepository,
|
||||
required this._api,
|
||||
required this._appMetadataRepository,
|
||||
this._cancellation,
|
||||
});
|
||||
|
||||
bool get isCancelled => _cancellation?.isCompleted ?? false;
|
||||
bool _manageLocalMediaAndroid = false;
|
||||
|
||||
Future<bool> sync() async {
|
||||
_logger.info("Remote sync request for user");
|
||||
|
|
@ -65,16 +66,17 @@ class SyncStreamService {
|
|||
|
||||
final serverSemVer = SemVer(major: serverVersion.major, minor: serverVersion.minor, patch: serverVersion.patch_);
|
||||
|
||||
final value = Store.get(StoreKey.syncMigrationStatus, "[]");
|
||||
final migrations = (jsonDecode(value) as List).cast<String>();
|
||||
final migrations = (await _appMetadataRepository.get(.syncMigrationStatus)).toList();
|
||||
int previousLength = migrations.length;
|
||||
await _runPreSyncTasks(migrations, serverSemVer);
|
||||
|
||||
if (migrations.length != previousLength) {
|
||||
_logger.info("Updated pre-sync migration status: $migrations");
|
||||
await Store.put(StoreKey.syncMigrationStatus, jsonEncode(migrations));
|
||||
await _appMetadataRepository.set(.syncMigrationStatus, migrations);
|
||||
}
|
||||
|
||||
_manageLocalMediaAndroid = CurrentPlatform.isAndroid && await _appMetadataRepository.get(.manageLocalMediaAndroid);
|
||||
|
||||
// Start the sync stream and handle events
|
||||
bool shouldReset = false;
|
||||
await _syncApiRepository.streamChanges(
|
||||
|
|
@ -97,7 +99,7 @@ class SyncStreamService {
|
|||
|
||||
if (migrations.length != previousLength) {
|
||||
_logger.info("Updated pre-sync migration status: $migrations");
|
||||
await Store.put(StoreKey.syncMigrationStatus, jsonEncode(migrations));
|
||||
await _appMetadataRepository.set(.syncMigrationStatus, migrations);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -113,10 +115,10 @@ class SyncStreamService {
|
|||
if (!migrations.contains(SyncMigrationTask.v20260128_ResetExifV1.name)) {
|
||||
_logger.info("Running pre-sync task: v20260128_ResetExifV1");
|
||||
await _syncApiRepository.deleteSyncAck([
|
||||
SyncEntityType.assetExifV1,
|
||||
SyncEntityType.partnerAssetExifV1,
|
||||
SyncEntityType.albumAssetExifCreateV1,
|
||||
SyncEntityType.albumAssetExifUpdateV1,
|
||||
.assetExifV1,
|
||||
.partnerAssetExifV1,
|
||||
.albumAssetExifCreateV1,
|
||||
.albumAssetExifUpdateV1,
|
||||
]);
|
||||
migrations.add(SyncMigrationTask.v20260128_ResetExifV1.name);
|
||||
}
|
||||
|
|
@ -124,12 +126,7 @@ class SyncStreamService {
|
|||
if (!migrations.contains(SyncMigrationTask.v20260128_ResetAssetV1.name) &&
|
||||
semVer >= const SemVer(major: 2, minor: 5, patch: 0)) {
|
||||
_logger.info("Running pre-sync task: v20260128_ResetAssetV1");
|
||||
await _syncApiRepository.deleteSyncAck([
|
||||
SyncEntityType.assetV1,
|
||||
SyncEntityType.partnerAssetV1,
|
||||
SyncEntityType.albumAssetCreateV1,
|
||||
SyncEntityType.albumAssetUpdateV1,
|
||||
]);
|
||||
await _syncApiRepository.deleteSyncAck([.assetV1, .partnerAssetV1, .albumAssetCreateV1, .albumAssetUpdateV1]);
|
||||
|
||||
migrations.add(SyncMigrationTask.v20260128_ResetAssetV1.name);
|
||||
|
||||
|
|
@ -141,7 +138,7 @@ class SyncStreamService {
|
|||
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]);
|
||||
await _syncApiRepository.deleteSyncAck([.assetV1, .assetV2]);
|
||||
migrations.add(SyncMigrationTask.v20260597_ResetAssetV1AssetV2.name);
|
||||
}
|
||||
}
|
||||
|
|
@ -204,20 +201,20 @@ class SyncStreamService {
|
|||
case SyncEntityType.assetV1:
|
||||
final remoteSyncAssets = data.cast<SyncAssetV1>();
|
||||
await _syncStreamRepository.updateAssetsV1(remoteSyncAssets);
|
||||
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
||||
if (_manageLocalMediaAndroid) {
|
||||
await _syncAssetTrashStatus(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.id).toList());
|
||||
}
|
||||
return;
|
||||
case SyncEntityType.assetV2:
|
||||
final remoteSyncAssets = data.cast<SyncAssetV2>();
|
||||
await _syncStreamRepository.updateAssetsV2(remoteSyncAssets);
|
||||
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
||||
if (_manageLocalMediaAndroid) {
|
||||
await _syncAssetTrashStatus(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.id).toList());
|
||||
}
|
||||
return;
|
||||
case SyncEntityType.assetDeleteV1:
|
||||
final remoteSyncAssets = data.cast<SyncAssetDeleteV1>();
|
||||
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
||||
if (_manageLocalMediaAndroid) {
|
||||
await _syncAssetDeletion(remoteSyncAssets.map((e) => e.assetId).toList());
|
||||
}
|
||||
return _syncStreamRepository.deleteAssetsV1(remoteSyncAssets);
|
||||
|
|
|
|||
18
mobile/lib/infrastructure/entities/app_metadata.entity.dart
Normal file
18
mobile/lib/infrastructure/entities/app_metadata.entity.dart
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||
|
||||
class AppMetadataEntity extends Table with DriftDefaultsMixin {
|
||||
const AppMetadataEntity();
|
||||
|
||||
TextColumn get key => text()();
|
||||
|
||||
TextColumn get value => text().nullable()();
|
||||
|
||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {key};
|
||||
|
||||
@override
|
||||
String get tableName => "app_metadata";
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:immich_mobile/domain/models/app_metadata_key.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/app_metadata.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
|
||||
class AppMetadataRepository {
|
||||
final Drift _db;
|
||||
|
||||
const AppMetadataRepository(this._db);
|
||||
|
||||
Future<T> get<T>(AppMetadataKey<T> key) async {
|
||||
final row = await (_db.select(_db.appMetadataEntity)..where((row) => row.key.equals(key.name))).getSingleOrNull();
|
||||
final value = row?.value;
|
||||
return value == null ? key.defaultValue : key.decode(value);
|
||||
}
|
||||
|
||||
Future<void> set<T, U extends T>(AppMetadataKey<T> key, U value) async {
|
||||
await _db
|
||||
.into(_db.appMetadataEntity)
|
||||
.insertOnConflictUpdate(
|
||||
AppMetadataEntityCompanion.insert(
|
||||
key: key.name,
|
||||
value: Value(key.encode(value)),
|
||||
updatedAt: Value(DateTime.now()),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import 'package:drift/drift.dart';
|
|||
import 'package:drift/src/runtime/executor/stream_queries.dart' show StreamQueryStore;
|
||||
import 'package:drift_sqlite_async/drift_sqlite_async.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/app_metadata.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/asset_ocr.entity.dart';
|
||||
|
|
@ -69,6 +70,7 @@ import 'package:sqlite_async/sqlite_async.dart';
|
|||
SettingsEntity,
|
||||
AssetOcrEntity,
|
||||
SessionEntity,
|
||||
AppMetadataEntity,
|
||||
],
|
||||
include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'},
|
||||
)
|
||||
|
|
@ -122,7 +124,7 @@ class Drift extends $Drift {
|
|||
}
|
||||
|
||||
@override
|
||||
int get schemaVersion => 32;
|
||||
int get schemaVersion => 33;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
|
|
@ -323,6 +325,14 @@ class Drift extends $Drift {
|
|||
from31To32: (m, v32) async {
|
||||
await m.createTable(v32.session);
|
||||
},
|
||||
from32To33: (m, v33) async {
|
||||
await m.createTable(v33.appMetadata);
|
||||
await customStatement(
|
||||
"INSERT INTO app_metadata (key, value) "
|
||||
"SELECT 'version', CAST(int_value AS TEXT) FROM store_entity "
|
||||
"WHERE id = 0 AND int_value IS NOT NULL",
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -17103,6 +17103,615 @@ final class Schema32 extends i0.VersionedSchema {
|
|||
);
|
||||
}
|
||||
|
||||
final class Schema33 extends i0.VersionedSchema {
|
||||
Schema33({required super.database}) : super(version: 33);
|
||||
@override
|
||||
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||
userEntity,
|
||||
remoteAssetEntity,
|
||||
stackEntity,
|
||||
localAssetEntity,
|
||||
remoteAlbumEntity,
|
||||
localAlbumEntity,
|
||||
localAlbumAssetEntity,
|
||||
idxLocalAlbumAssetAlbumAsset,
|
||||
idxLocalAssetChecksum,
|
||||
idxLocalAssetCloudId,
|
||||
idxLocalAssetCreatedAt,
|
||||
idxStackPrimaryAssetId,
|
||||
uQRemoteAssetsOwnerChecksum,
|
||||
uQRemoteAssetsOwnerLibraryChecksum,
|
||||
idxRemoteAssetChecksum,
|
||||
idxRemoteAssetStackId,
|
||||
idxRemoteAssetOwnerVisibilityDeletedCreated,
|
||||
idxRemoteAssetUploaded,
|
||||
authUserEntity,
|
||||
userMetadataEntity,
|
||||
partnerEntity,
|
||||
remoteExifEntity,
|
||||
remoteAlbumAssetEntity,
|
||||
remoteAlbumUserEntity,
|
||||
remoteAssetCloudIdEntity,
|
||||
memoryEntity,
|
||||
memoryAssetEntity,
|
||||
personEntity,
|
||||
assetFaceEntity,
|
||||
storeEntity,
|
||||
trashedLocalAssetEntity,
|
||||
assetEditEntity,
|
||||
settings,
|
||||
assetOcrEntity,
|
||||
session,
|
||||
appMetadata,
|
||||
idxPartnerSharedWithId,
|
||||
idxLatLng,
|
||||
idxRemoteExifCity,
|
||||
idxRemoteAlbumAssetAlbumAsset,
|
||||
idxRemoteAssetCloudId,
|
||||
idxPersonOwnerId,
|
||||
idxAssetFacePersonId,
|
||||
idxAssetFaceAssetId,
|
||||
idxAssetFaceVisiblePerson,
|
||||
idxTrashedLocalAssetChecksum,
|
||||
idxTrashedLocalAssetAlbum,
|
||||
idxAssetEditAssetId,
|
||||
idxAssetOcrAssetId,
|
||||
];
|
||||
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 idxLocalAssetCreatedAt = i1.Index(
|
||||
'idx_local_asset_created_at',
|
||||
'CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)',
|
||||
);
|
||||
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 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 idxRemoteAssetOwnerVisibilityDeletedCreated = i1.Index(
|
||||
'idx_remote_asset_owner_visibility_deleted_created',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)',
|
||||
);
|
||||
final i1.Index idxRemoteAssetUploaded = i1.Index(
|
||||
'idx_remote_asset_uploaded',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_uploaded ON remote_asset_entity (uploaded_at)',
|
||||
);
|
||||
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 settings = Shape49(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'settings',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY("key")'],
|
||||
columns: [_column_210, _column_224, _column_115],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape51 assetOcrEntity = Shape51(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'asset_ocr_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_107,
|
||||
_column_159,
|
||||
_column_213,
|
||||
_column_214,
|
||||
_column_215,
|
||||
_column_216,
|
||||
_column_217,
|
||||
_column_218,
|
||||
_column_219,
|
||||
_column_220,
|
||||
_column_221,
|
||||
_column_222,
|
||||
_column_223,
|
||||
_column_201,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape49 session = Shape49(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'session',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY("key")'],
|
||||
columns: [_column_210, _column_224, _column_115],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape49 appMetadata = Shape49(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'app_metadata',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY("key")'],
|
||||
columns: [_column_210, _column_224, _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 idxRemoteExifCity = i1.Index(
|
||||
'idx_remote_exif_city',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL',
|
||||
);
|
||||
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 idxAssetFaceVisiblePerson = i1.Index(
|
||||
'idx_asset_face_visible_person',
|
||||
'CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL',
|
||||
);
|
||||
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)',
|
||||
);
|
||||
final i1.Index idxAssetOcrAssetId = i1.Index(
|
||||
'idx_asset_ocr_asset_id',
|
||||
'CREATE INDEX IF NOT EXISTS idx_asset_ocr_asset_id ON asset_ocr_entity (asset_id)',
|
||||
);
|
||||
}
|
||||
|
||||
i0.MigrationStepWithVersion migrationSteps({
|
||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||
|
|
@ -17135,6 +17744,7 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||
required Future<void> Function(i1.Migrator m, Schema30 schema) from29To30,
|
||||
required Future<void> Function(i1.Migrator m, Schema31 schema) from30To31,
|
||||
required Future<void> Function(i1.Migrator m, Schema32 schema) from31To32,
|
||||
required Future<void> Function(i1.Migrator m, Schema33 schema) from32To33,
|
||||
}) {
|
||||
return (currentVersion, database) async {
|
||||
switch (currentVersion) {
|
||||
|
|
@ -17293,6 +17903,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||
final migrator = i1.Migrator(database, schema);
|
||||
await from31To32(migrator, schema);
|
||||
return 32;
|
||||
case 32:
|
||||
final schema = Schema33(database: database);
|
||||
final migrator = i1.Migrator(database, schema);
|
||||
await from32To33(migrator, schema);
|
||||
return 33;
|
||||
default:
|
||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||
}
|
||||
|
|
@ -17331,6 +17946,7 @@ i1.OnUpgrade stepByStep({
|
|||
required Future<void> Function(i1.Migrator m, Schema30 schema) from29To30,
|
||||
required Future<void> Function(i1.Migrator m, Schema31 schema) from30To31,
|
||||
required Future<void> Function(i1.Migrator m, Schema32 schema) from31To32,
|
||||
required Future<void> Function(i1.Migrator m, Schema33 schema) from32To33,
|
||||
}) => i0.VersionedSchema.stepByStepHelper(
|
||||
step: migrationSteps(
|
||||
from1To2: from1To2,
|
||||
|
|
@ -17364,5 +17980,6 @@ i1.OnUpgrade stepByStep({
|
|||
from29To30: from29To30,
|
||||
from30To31: from30To31,
|
||||
from31To32: from31To32,
|
||||
from32To33: from32To33,
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
|||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||
import 'package:immich_mobile/generated/translations.g.dart';
|
||||
import 'package:immich_mobile/presentation/actions/action.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/app_metadata.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/store.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/toast.provider.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
|
|
@ -209,9 +209,9 @@ Future<int> _cleanupLocalAssets(
|
|||
final requiresPrompt =
|
||||
requestCustomPrompt &&
|
||||
CurrentPlatform.isAndroid &&
|
||||
ref.read(storeServiceProvider).get(.manageLocalMediaAndroid, false);
|
||||
await ref.read(appMetadataRepositoryProvider).get(.manageLocalMediaAndroid);
|
||||
|
||||
if (requiresPrompt) {
|
||||
if (requiresPrompt && context.mounted) {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (_) => ConfirmDialog(
|
||||
|
|
|
|||
|
|
@ -81,9 +81,6 @@ class MapStateNotifier extends Notifier<MapState> {
|
|||
}
|
||||
|
||||
void switchTheme(ThemeMode mode) {
|
||||
// TODO: Remove this line when map theme provider is removed
|
||||
// Until then, keep both in sync as MapThemeOverride uses map state provider
|
||||
// ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapThemeMode, mode.index);
|
||||
ref.read(mapStateNotifierProvider.notifier).switchTheme(mode);
|
||||
state = state.copyWith(themeMode: mode);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
|
||||
final appSettingsServiceProvider = Provider((_) => const AppSettingsService());
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/app_metadata.repository.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
|
||||
final appMetadataRepositoryProvider = Provider<AppMetadataRepository>(
|
||||
(ref) => AppMetadataRepository(ref.watch(driftProvider)),
|
||||
);
|
||||
|
|
@ -4,6 +4,7 @@ import 'package:immich_mobile/infrastructure/repositories/local_asset.repository
|
|||
import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/remote_exif.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/app_metadata.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/repositories/asset_api.repository.dart';
|
||||
|
|
@ -31,6 +32,7 @@ final assetServiceProvider = Provider(
|
|||
apiRepository: ref.watch(assetApiRepositoryProvider),
|
||||
mediaRepository: ref.watch(assetMediaRepositoryProvider),
|
||||
trashedLocalRepository: ref.watch(trashedLocalAssetRepository),
|
||||
appMetadataRepository: ref.read(appMetadataRepositoryProvider),
|
||||
),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:immich_mobile/infrastructure/repositories/sync_migration.reposit
|
|||
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
|
||||
import 'package:immich_mobile/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/app_metadata.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
|
|
@ -26,6 +27,7 @@ final syncStreamServiceProvider = Provider(
|
|||
permissionRepository: ref.watch(permissionRepositoryProvider),
|
||||
syncMigrationRepository: ref.watch(syncMigrationRepositoryProvider),
|
||||
api: ref.watch(apiServiceProvider),
|
||||
appMetadataRepository: ref.watch(appMetadataRepositoryProvider),
|
||||
cancellation: ref.watch(cancellationProvider),
|
||||
),
|
||||
);
|
||||
|
|
@ -42,6 +44,7 @@ final localSyncServiceProvider = Provider(
|
|||
assetMediaRepository: ref.watch(assetMediaRepositoryProvider),
|
||||
permissionRepository: ref.watch(permissionRepositoryProvider),
|
||||
nativeSyncApi: ref.watch(nativeSyncApiProvider),
|
||||
appMetadataRepository: ref.watch(appMetadataRepositoryProvider),
|
||||
cancellation: ref.watch(cancellationProvider),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
|
||||
enum AppSettingsEnum<T> {
|
||||
manageLocalMediaAndroid<bool>(StoreKey.manageLocalMediaAndroid, null, false);
|
||||
|
||||
const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue);
|
||||
|
||||
final StoreKey<T> storeKey;
|
||||
final String? hiveKey;
|
||||
final T defaultValue;
|
||||
}
|
||||
|
||||
class AppSettingsService {
|
||||
const AppSettingsService();
|
||||
T getSetting<T>(AppSettingsEnum<T> setting) {
|
||||
return Store.get(setting.storeKey, setting.defaultValue);
|
||||
}
|
||||
|
||||
Future<void> setSetting<T>(AppSettingsEnum<T> setting, T value) {
|
||||
return Store.put(setting.storeKey, value);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
|
||||
ValueNotifier<T> useAppSettingsState<T>(AppSettingsEnum<T> key) {
|
||||
final notifier = useState<T>(Store.get(key.storeKey, key.defaultValue));
|
||||
|
||||
// Listen to changes to the notifier and update app settings
|
||||
useValueChanged(notifier.value, (_, __) => unawaited(Store.put(key.storeKey, notifier.value)));
|
||||
|
||||
return notifier;
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import 'package:drift/drift.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/constants/colors.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/domain/models/app_metadata_key.dart';
|
||||
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||
import 'package:immich_mobile/domain/models/session.model.dart';
|
||||
|
|
@ -14,8 +15,10 @@ import 'package:immich_mobile/domain/models/store.model.dart';
|
|||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||
import 'package:immich_mobile/domain/services/feature_message.service.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/app_metadata.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/session.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/app_metadata.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||
|
|
@ -23,11 +26,13 @@ import 'package:immich_mobile/infrastructure/repositories/settings.repository.da
|
|||
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||
|
||||
const int targetVersion = 28;
|
||||
|
||||
Future<void> migrateDatabaseIfNeeded(Drift drift) async {
|
||||
final int? storedVersion = Store.tryGet(StoreKey.version);
|
||||
final version = storedVersion ?? targetVersion;
|
||||
final isFreshInstall = await drift.appMetadataEntity
|
||||
.count(where: (row) => row.key.equals(AppMetadataKey.version.name))
|
||||
.map((count) => count == 0)
|
||||
.getSingle();
|
||||
final metadata = AppMetadataRepository(drift);
|
||||
final version = await metadata.get(.version);
|
||||
|
||||
if (version < 25) {
|
||||
await _migrateTo25();
|
||||
|
|
@ -37,9 +42,6 @@ Future<void> migrateDatabaseIfNeeded(Drift drift) async {
|
|||
await _migrateTo26(drift);
|
||||
}
|
||||
|
||||
if (storedVersion == null) {
|
||||
await FeatureMessageService(SettingsRepository.instance).markSeen();
|
||||
}
|
||||
if (version < 27) {
|
||||
await _migrateTo27(drift);
|
||||
}
|
||||
|
|
@ -48,26 +50,34 @@ Future<void> migrateDatabaseIfNeeded(Drift drift) async {
|
|||
await _migrateTo28(drift);
|
||||
}
|
||||
|
||||
await Store.put(StoreKey.version, targetVersion);
|
||||
if (version < 29) {
|
||||
await _migrateTo29(drift);
|
||||
}
|
||||
|
||||
if (isFreshInstall) {
|
||||
await FeatureMessageService(SettingsRepository.instance).markSeen();
|
||||
}
|
||||
|
||||
await metadata.set(.version, kCurrentVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
Future<void> _migrateTo25() async {
|
||||
final accessToken = Store.tryGet(StoreKey.legacyAccessToken);
|
||||
final accessToken = Store.tryGet(.legacyAccessToken);
|
||||
if (accessToken == null || accessToken.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final urls = <String>[];
|
||||
final serverEndpoint = Store.tryGet(StoreKey.legacyServerEndpoint);
|
||||
final serverEndpoint = Store.tryGet(.legacyServerEndpoint);
|
||||
if (serverEndpoint != null && serverEndpoint.isNotEmpty) {
|
||||
urls.add(serverEndpoint);
|
||||
}
|
||||
final localEndpoint = Store.tryGet(StoreKey.legacyLocalEndpoint);
|
||||
final localEndpoint = Store.tryGet(.legacyLocalEndpoint);
|
||||
if (localEndpoint != null && localEndpoint.isNotEmpty) {
|
||||
urls.add(localEndpoint);
|
||||
}
|
||||
final externalJson = Store.tryGet(StoreKey.legacyExternalEndpointList);
|
||||
final externalJson = Store.tryGet(.legacyExternalEndpointList);
|
||||
if (externalJson != null) {
|
||||
final List<dynamic> list = jsonDecode(externalJson);
|
||||
for (final entry in list) {
|
||||
|
|
@ -81,7 +91,7 @@ Future<void> _migrateTo25() async {
|
|||
return;
|
||||
}
|
||||
|
||||
final customHeadersStr = Store.get(StoreKey.legacyCustomHeaders, "");
|
||||
final customHeadersStr = Store.get(.legacyCustomHeaders, "");
|
||||
final headers = customHeadersStr.isEmpty
|
||||
? const <String, String>{}
|
||||
: (jsonDecode(customHeadersStr) as Map).cast<String, String>();
|
||||
|
|
@ -91,75 +101,67 @@ Future<void> _migrateTo25() async {
|
|||
|
||||
Future<void> _migrateTo26(Drift drift) async {
|
||||
final migrator = _StoreMigrator.settings(drift);
|
||||
await migrator.migrateEnumIndex(StoreKey.legacyLogLevel, SettingsKey.logLevel, LogLevel.values);
|
||||
await migrator.migrateEnumIndex(.legacyLogLevel, .logLevel, LogLevel.values);
|
||||
// Theme
|
||||
await migrator.migrateEnumName(StoreKey.legacyThemeMode, SettingsKey.themeMode, ThemeMode.values);
|
||||
await migrator.migrateEnumName(StoreKey.legacyPrimaryColor, SettingsKey.themePrimaryColor, ImmichColorPreset.values);
|
||||
await migrator.migrateBool(StoreKey.legacyDynamicTheme, SettingsKey.themeDynamic);
|
||||
await migrator.migrateBool(StoreKey.legacyColorfulInterface, SettingsKey.themeColorfulInterface);
|
||||
await migrator.migrateEnumName(.legacyThemeMode, .themeMode, ThemeMode.values);
|
||||
await migrator.migrateEnumName(.legacyPrimaryColor, .themePrimaryColor, ImmichColorPreset.values);
|
||||
await migrator.migrateBool(.legacyDynamicTheme, .themeDynamic);
|
||||
await migrator.migrateBool(.legacyColorfulInterface, .themeColorfulInterface);
|
||||
// Cleanup
|
||||
final cleanupKeepAlbumIds = await migrator.readLegacyStoreString(StoreKey.legacyCleanupKeepAlbumIds.id);
|
||||
final cleanupKeepAlbumIds = await migrator.readLegacyStoreString(.legacyCleanupKeepAlbumIds);
|
||||
if (cleanupKeepAlbumIds != null) {
|
||||
final ids = cleanupKeepAlbumIds.split(',').where((id) => id.isNotEmpty).toList();
|
||||
migrator.stage(StoreKey.legacyCleanupKeepAlbumIds, SettingsKey.cleanupKeepAlbumIds, ids);
|
||||
migrator.stage(.legacyCleanupKeepAlbumIds, .cleanupKeepAlbumIds, ids);
|
||||
}
|
||||
await migrator.migrateBool(StoreKey.legacyCleanupKeepFavorites, SettingsKey.cleanupKeepFavorites);
|
||||
await migrator.migrateEnumIndex(
|
||||
StoreKey.legacyCleanupKeepMediaType,
|
||||
SettingsKey.cleanupKeepMediaType,
|
||||
AssetKeepType.values,
|
||||
);
|
||||
await migrator.migrateInt(StoreKey.legacyCleanupCutoffDaysAgo, SettingsKey.cleanupCutoffDaysAgo);
|
||||
await migrator.migrateBool(StoreKey.legacyCleanupDefaultsInitialized, SettingsKey.cleanupDefaultsInitialized);
|
||||
await migrator.migrateBool(.legacyCleanupKeepFavorites, .cleanupKeepFavorites);
|
||||
await migrator.migrateEnumIndex(.legacyCleanupKeepMediaType, .cleanupKeepMediaType, AssetKeepType.values);
|
||||
await migrator.migrateInt(.legacyCleanupCutoffDaysAgo, .cleanupCutoffDaysAgo);
|
||||
await migrator.migrateBool(.legacyCleanupDefaultsInitialized, .cleanupDefaultsInitialized);
|
||||
// Map
|
||||
await migrator.migrateBool(StoreKey.legacyMapShowFavoriteOnly, SettingsKey.mapShowFavoriteOnly);
|
||||
await migrator.migrateInt(StoreKey.legacyMapRelativeDate, SettingsKey.mapRelativeDate);
|
||||
await migrator.migrateBool(StoreKey.legacyMapIncludeArchived, SettingsKey.mapIncludeArchived);
|
||||
await migrator.migrateEnumIndex(StoreKey.legacyMapThemeMode, SettingsKey.mapThemeMode, ThemeMode.values);
|
||||
await migrator.migrateBool(StoreKey.legacyMapwithPartners, SettingsKey.mapWithPartners);
|
||||
await migrator.migrateBool(.legacyMapShowFavoriteOnly, .mapShowFavoriteOnly);
|
||||
await migrator.migrateInt(.legacyMapRelativeDate, .mapRelativeDate);
|
||||
await migrator.migrateBool(.legacyMapIncludeArchived, .mapIncludeArchived);
|
||||
await migrator.migrateEnumIndex(.legacyMapThemeMode, .mapThemeMode, ThemeMode.values);
|
||||
await migrator.migrateBool(.legacyMapwithPartners, .mapWithPartners);
|
||||
// Timeline
|
||||
await migrator.migrateInt(StoreKey.legacyTilesPerRow, SettingsKey.timelineTilesPerRow);
|
||||
await migrator.migrateEnumIndex(
|
||||
StoreKey.legacyGroupAssetsBy,
|
||||
SettingsKey.timelineGroupAssetsBy,
|
||||
GroupAssetsBy.values,
|
||||
);
|
||||
await migrator.migrateBool(StoreKey.legacyStorageIndicator, SettingsKey.timelineStorageIndicator);
|
||||
await migrator.migrateInt(.legacyTilesPerRow, .timelineTilesPerRow);
|
||||
await migrator.migrateEnumIndex(.legacyGroupAssetsBy, .timelineGroupAssetsBy, GroupAssetsBy.values);
|
||||
await migrator.migrateBool(.legacyStorageIndicator, .timelineStorageIndicator);
|
||||
// Image
|
||||
await migrator.migrateBool(StoreKey.legacyPreferRemoteImage, SettingsKey.imagePreferRemote);
|
||||
await migrator.migrateBool(StoreKey.legacyLoadOriginal, SettingsKey.imageLoadOriginal);
|
||||
await migrator.migrateBool(.legacyPreferRemoteImage, .imagePreferRemote);
|
||||
await migrator.migrateBool(.legacyLoadOriginal, .imageLoadOriginal);
|
||||
// Viewer
|
||||
await migrator.migrateBool(StoreKey.legacyLoopVideo, SettingsKey.viewerLoopVideo);
|
||||
await migrator.migrateBool(StoreKey.legacyLoadOriginalVideo, SettingsKey.viewerLoadOriginalVideo);
|
||||
await migrator.migrateBool(StoreKey.legacyAutoPlayVideo, SettingsKey.viewerAutoPlayVideo);
|
||||
await migrator.migrateBool(StoreKey.legacyTapToNavigate, SettingsKey.viewerTapToNavigate);
|
||||
await migrator.migrateBool(.legacyLoopVideo, .viewerLoopVideo);
|
||||
await migrator.migrateBool(.legacyLoadOriginalVideo, .viewerLoadOriginalVideo);
|
||||
await migrator.migrateBool(.legacyAutoPlayVideo, .viewerAutoPlayVideo);
|
||||
await migrator.migrateBool(.legacyTapToNavigate, .viewerTapToNavigate);
|
||||
// Network
|
||||
await migrator.migrateBool(StoreKey.legacyAutoEndpointSwitching, SettingsKey.networkAutoEndpointSwitching);
|
||||
final preferredWifiName = await migrator.readLegacyStoreString(StoreKey.legacyPreferredWifiName.id);
|
||||
migrator.stage(StoreKey.legacyPreferredWifiName, SettingsKey.networkPreferredWifiName, preferredWifiName);
|
||||
final localEndpoint = await migrator.readLegacyStoreString(StoreKey.legacyLocalEndpoint.id);
|
||||
migrator.stage(StoreKey.legacyLocalEndpoint, SettingsKey.networkLocalEndpoint, localEndpoint);
|
||||
await migrator.migrateBool(.legacyAutoEndpointSwitching, .networkAutoEndpointSwitching);
|
||||
final preferredWifiName = await migrator.readLegacyStoreString(.legacyPreferredWifiName);
|
||||
migrator.stage(.legacyPreferredWifiName, .networkPreferredWifiName, preferredWifiName);
|
||||
final localEndpoint = await migrator.readLegacyStoreString(.legacyLocalEndpoint);
|
||||
migrator.stage(.legacyLocalEndpoint, .networkLocalEndpoint, localEndpoint);
|
||||
await _migrateExternalEndpointList(migrator);
|
||||
await _migrateCustomHeaders(migrator);
|
||||
// Album
|
||||
await _migrateAlbumSortMode(migrator);
|
||||
await migrator.migrateBool(StoreKey.legacySelectedAlbumSortReverse, SettingsKey.albumIsReverse);
|
||||
await migrator.migrateBool(StoreKey.legacyAlbumGridView, SettingsKey.albumIsGrid);
|
||||
await migrator.migrateBool(.legacySelectedAlbumSortReverse, .albumIsReverse);
|
||||
await migrator.migrateBool(.legacyAlbumGridView, .albumIsGrid);
|
||||
// Backup
|
||||
await migrator.migrateBool(StoreKey.legacyEnableBackup, SettingsKey.backupEnabled);
|
||||
await migrator.migrateBool(StoreKey.legacyUseWifiForUploadVideos, SettingsKey.backupUseCellularForVideos);
|
||||
await migrator.migrateBool(StoreKey.legacyUseWifiForUploadPhotos, SettingsKey.backupUseCellularForPhotos);
|
||||
await migrator.migrateBool(StoreKey.legacyBackupRequireCharging, SettingsKey.backupRequireCharging);
|
||||
await migrator.migrateInt(StoreKey.legacyBackupTriggerDelay, SettingsKey.backupTriggerDelay);
|
||||
await migrator.migrateBool(StoreKey.legacySyncAlbums, SettingsKey.backupSyncAlbums);
|
||||
await migrator.migrateBool(.legacyEnableBackup, .backupEnabled);
|
||||
await migrator.migrateBool(.legacyUseWifiForUploadVideos, .backupUseCellularForVideos);
|
||||
await migrator.migrateBool(.legacyUseWifiForUploadPhotos, .backupUseCellularForPhotos);
|
||||
await migrator.migrateBool(.legacyBackupRequireCharging, .backupRequireCharging);
|
||||
await migrator.migrateInt(.legacyBackupTriggerDelay, .backupTriggerDelay);
|
||||
await migrator.migrateBool(.legacySyncAlbums, .backupSyncAlbums);
|
||||
await migrator.complete();
|
||||
}
|
||||
|
||||
Future<void> _migrateTo27(Drift drift) async {
|
||||
final migrator = _StoreMigrator.session(drift);
|
||||
await migrator.migrateString(StoreKey.legacyServerUrl, SessionKey.serverUrl);
|
||||
await migrator.migrateString(StoreKey.legacyAccessToken, SessionKey.accessToken);
|
||||
await migrator.migrateString(StoreKey.legacyServerEndpoint, SessionKey.serverEndpoint);
|
||||
await migrator.migrateString(.legacyServerUrl, .serverUrl);
|
||||
await migrator.migrateString(.legacyAccessToken, .accessToken);
|
||||
await migrator.migrateString(.legacyServerEndpoint, .serverEndpoint);
|
||||
await migrator.complete();
|
||||
|
||||
await SessionRepository.instance.refresh();
|
||||
|
|
@ -167,26 +169,40 @@ Future<void> _migrateTo27(Drift drift) async {
|
|||
|
||||
Future<void> _migrateTo28(Drift drift) async {
|
||||
final migrator = _StoreMigrator.settings(drift);
|
||||
await migrator.migrateBool(StoreKey.legacyAdvancedTroubleshooting, SettingsKey.advancedTroubleshooting);
|
||||
await migrator.migrateBool(StoreKey.legacyEnableHapticFeedback, SettingsKey.advancedEnableHapticFeedback);
|
||||
await migrator.migrateBool(StoreKey.legacyReadonlyModeEnabled, SettingsKey.advancedReadonlyModeEnabled);
|
||||
await migrator.migrateBool(.legacyAdvancedTroubleshooting, .advancedTroubleshooting);
|
||||
await migrator.migrateBool(.legacyEnableHapticFeedback, .advancedEnableHapticFeedback);
|
||||
await migrator.migrateBool(.legacyReadonlyModeEnabled, .advancedReadonlyModeEnabled);
|
||||
await migrator.complete();
|
||||
|
||||
await SettingsRepository.instance.refresh();
|
||||
}
|
||||
|
||||
Future<void> _migrateTo29(Drift drift) async {
|
||||
final migrator = _StoreMigrator.appMetadata(drift);
|
||||
|
||||
final rawStatus = await migrator.readLegacyStoreString(.legacySyncMigrationStatus);
|
||||
if (rawStatus != null) {
|
||||
final decoded = jsonDecode(rawStatus);
|
||||
final migrations = decoded is List ? decoded.whereType<String>().toList() : <String>[];
|
||||
migrator.stage(.legacySyncMigrationStatus, .syncMigrationStatus, migrations);
|
||||
}
|
||||
|
||||
await migrator.migrateBool(.legacyManageLocalMediaAndroid, .manageLocalMediaAndroid);
|
||||
await migrator.complete();
|
||||
}
|
||||
|
||||
Future<void> _migrateAlbumSortMode(_StoreMigrator<SettingsKey> migrator) async {
|
||||
final raw = await migrator.readLegacyStoreInt(StoreKey.legacySelectedAlbumSortOrder.id);
|
||||
final raw = await migrator.readLegacyStoreInt(.legacySelectedAlbumSortOrder);
|
||||
final mode = AlbumSortMode.values.firstWhereOrNull((e) => raw != null && e.storeIndex == raw);
|
||||
if (mode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
migrator.stage(StoreKey.legacySelectedAlbumSortOrder, SettingsKey.albumSortMode, mode);
|
||||
migrator.stage(.legacySelectedAlbumSortOrder, .albumSortMode, mode);
|
||||
}
|
||||
|
||||
Future<void> _migrateExternalEndpointList(_StoreMigrator<SettingsKey> migrator) async {
|
||||
final raw = await migrator.readLegacyStoreString(StoreKey.legacyExternalEndpointList.id);
|
||||
final raw = await migrator.readLegacyStoreString(.legacyExternalEndpointList);
|
||||
if (raw == null) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -210,7 +226,7 @@ Future<void> _migrateExternalEndpointList(_StoreMigrator<SettingsKey> migrator)
|
|||
}
|
||||
|
||||
Future<void> _migrateCustomHeaders(_StoreMigrator<SettingsKey> migrator) async {
|
||||
final raw = await migrator.readLegacyStoreString(StoreKey.legacyCustomHeaders.id);
|
||||
final raw = await migrator.readLegacyStoreString(.legacyCustomHeaders);
|
||||
if (raw == null) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -257,6 +273,17 @@ class _StoreMigrator<K extends Enum> {
|
|||
),
|
||||
);
|
||||
|
||||
static _StoreMigrator<AppMetadataKey> appMetadata(Drift db) => _StoreMigrator<AppMetadataKey>._(
|
||||
db,
|
||||
encode: (key, value) => key.encode(value),
|
||||
readDefault: (_) => null,
|
||||
insertRow: (batch, name, value) => batch.insert(
|
||||
db.appMetadataEntity,
|
||||
AppMetadataEntityCompanion(key: Value(name), value: Value(value)),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
),
|
||||
);
|
||||
|
||||
final Drift _db;
|
||||
final String Function(K key, Object value) encode;
|
||||
final Object? Function(K key) readDefault;
|
||||
|
|
@ -265,7 +292,7 @@ class _StoreMigrator<K extends Enum> {
|
|||
final List<int> _migratedStoreIds = [];
|
||||
|
||||
Future<void> migrateEnumIndex<T extends Enum>(StoreKey<int> legacyKey, K newKey, List<T> values) async {
|
||||
final index = await readLegacyStoreInt(legacyKey.id);
|
||||
final index = await readLegacyStoreInt(legacyKey);
|
||||
if (index == null) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -280,7 +307,7 @@ class _StoreMigrator<K extends Enum> {
|
|||
}
|
||||
|
||||
Future<void> migrateEnumName<T extends Enum>(StoreKey<String> legacyKey, K newKey, List<T> values) async {
|
||||
final name = await readLegacyStoreString(legacyKey.id);
|
||||
final name = await readLegacyStoreString(legacyKey);
|
||||
if (name == null) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -295,7 +322,7 @@ class _StoreMigrator<K extends Enum> {
|
|||
}
|
||||
|
||||
Future<void> migrateBool(StoreKey<bool> legacyKey, K newKey) async {
|
||||
final intValue = await readLegacyStoreInt(legacyKey.id);
|
||||
final intValue = await readLegacyStoreInt(legacyKey);
|
||||
if (intValue == null) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -305,7 +332,7 @@ class _StoreMigrator<K extends Enum> {
|
|||
}
|
||||
|
||||
Future<void> migrateInt(StoreKey<int> legacyKey, K newKey) async {
|
||||
final intValue = await readLegacyStoreInt(legacyKey.id);
|
||||
final intValue = await readLegacyStoreInt(legacyKey);
|
||||
if (intValue == null) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -315,7 +342,7 @@ class _StoreMigrator<K extends Enum> {
|
|||
}
|
||||
|
||||
Future<void> migrateString(StoreKey<String> legacyKey, K newKey) async {
|
||||
final value = await readLegacyStoreString(legacyKey.id);
|
||||
final value = await readLegacyStoreString(legacyKey);
|
||||
if (value == null || value.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -325,7 +352,7 @@ class _StoreMigrator<K extends Enum> {
|
|||
}
|
||||
|
||||
Future<void> migrateNullableString(StoreKey<String> legacyKey, K newKey) async {
|
||||
_cache[newKey] = await readLegacyStoreString(legacyKey.id);
|
||||
_cache[newKey] = await readLegacyStoreString(legacyKey);
|
||||
_migratedStoreIds.add(legacyKey.id);
|
||||
}
|
||||
|
||||
|
|
@ -348,13 +375,13 @@ class _StoreMigrator<K extends Enum> {
|
|||
await deleteLegacyStoreRows(_migratedStoreIds);
|
||||
}
|
||||
|
||||
Future<String?> readLegacyStoreString(int id) async {
|
||||
final row = await (_db.storeEntity.select()..where((t) => t.id.equals(id))).getSingleOrNull();
|
||||
Future<String?> readLegacyStoreString(StoreKey key) async {
|
||||
final row = await (_db.storeEntity.select()..where((t) => t.id.equals(key.id))).getSingleOrNull();
|
||||
return row?.stringValue;
|
||||
}
|
||||
|
||||
Future<int?> readLegacyStoreInt(int id) async {
|
||||
final row = await (_db.storeEntity.select()..where((t) => t.id.equals(id))).getSingleOrNull();
|
||||
Future<int?> readLegacyStoreInt(StoreKey key) async {
|
||||
final row = await (_db.storeEntity.select()..where((t) => t.id.equals(key.id))).getSingleOrNull();
|
||||
return row?.intValue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,8 +11,7 @@ import 'package:flutter/services.dart';
|
|||
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/domain/models/app_metadata_key.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||
|
|
@ -20,6 +19,7 @@ import 'package:immich_mobile/providers/auth.provider.dart';
|
|||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||
import 'package:immich_mobile/providers/feature_message.provider.dart';
|
||||
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/app_metadata.provider.dart';
|
||||
import 'package:immich_mobile/providers/oauth.provider.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/providers/view_intent/view_intent_handler.provider.dart';
|
||||
|
|
@ -247,7 +247,8 @@ class LoginForm extends HookConsumerWidget {
|
|||
}
|
||||
}
|
||||
|
||||
bool isSyncRemoteDeletionsMode() => Platform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false);
|
||||
Future<bool> isSyncRemoteDeletionsMode() async =>
|
||||
Platform.isAndroid && await ref.read(appMetadataRepositoryProvider).get(AppMetadataKey.manageLocalMediaAndroid);
|
||||
|
||||
Future<void> login() async {
|
||||
TextInput.finishAutofillContext();
|
||||
|
|
@ -266,7 +267,7 @@ class LoginForm extends HookConsumerWidget {
|
|||
unawaited(context.pushRoute(const ChangePasswordRoute()));
|
||||
} else {
|
||||
await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission();
|
||||
if (isSyncRemoteDeletionsMode()) {
|
||||
if (await isSyncRemoteDeletionsMode()) {
|
||||
await getManageMediaPermission();
|
||||
}
|
||||
unawaited(handleSyncFlow());
|
||||
|
|
@ -371,7 +372,7 @@ class LoginForm extends HookConsumerWidget {
|
|||
|
||||
if (isSuccess && context.mounted) {
|
||||
await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission();
|
||||
if (isSyncRemoteDeletionsMode()) {
|
||||
if (await isSyncRemoteDeletionsMode()) {
|
||||
await getManageMediaPermission();
|
||||
}
|
||||
unawaited(handleSyncFlow());
|
||||
|
|
|
|||
|
|
@ -6,15 +6,15 @@ import 'package:easy_localization/easy_localization.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/app_metadata_key.dart';
|
||||
import 'package:immich_mobile/domain/services/log.service.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/app_metadata.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||
import 'package:immich_mobile/repositories/permission.repository.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/utils/bytes_units.dart';
|
||||
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
||||
import 'package:immich_mobile/widgets/settings/custom_proxy_headers_settings/custom_proxy_headers_settings.dart';
|
||||
import 'package:immich_mobile/widgets/settings/settings_action_tile.dart';
|
||||
import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart';
|
||||
|
|
@ -33,7 +33,7 @@ class AdvancedSettings extends HookConsumerWidget {
|
|||
advancedTroubleshooting.value,
|
||||
(_, __) => unawaited(ref.read(settingsProvider).write(.advancedTroubleshooting, advancedTroubleshooting.value)),
|
||||
);
|
||||
final manageLocalMediaAndroid = useAppSettingsState(AppSettingsEnum.manageLocalMediaAndroid);
|
||||
final manageLocalMediaAndroid = useState(false);
|
||||
final isManageMediaSupported = useState(false);
|
||||
final manageMediaAndroidPermission = useState(false);
|
||||
final levelId = useState<int>(ref.watch(appConfigProvider).logLevel.index);
|
||||
|
|
@ -64,7 +64,17 @@ class AdvancedSettings extends HookConsumerWidget {
|
|||
useEffect(() {
|
||||
unawaited(() async {
|
||||
isManageMediaSupported.value = await checkAndroidVersion();
|
||||
if (isManageMediaSupported.value && context.mounted) {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isManageMediaSupported.value) {
|
||||
manageLocalMediaAndroid.value = await ref
|
||||
.read(appMetadataRepositoryProvider)
|
||||
.get(AppMetadataKey.manageLocalMediaAndroid);
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
manageMediaAndroidPermission.value = await ref.read(permissionRepositoryProvider).hasManageMediaPermission();
|
||||
}
|
||||
}());
|
||||
|
|
@ -91,6 +101,12 @@ class AdvancedSettings extends HookConsumerWidget {
|
|||
final result = await ref.read(permissionRepositoryProvider).requestManageMediaPermission();
|
||||
manageLocalMediaAndroid.value = result;
|
||||
manageMediaAndroidPermission.value = result;
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
await ref.read(appMetadataRepositoryProvider).set(.manageLocalMediaAndroid, result);
|
||||
} else {
|
||||
await ref.read(appMetadataRepositoryProvider).set(.manageLocalMediaAndroid, false);
|
||||
}
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
||||
|
|
@ -20,7 +19,6 @@ class GroupSettings extends HookConsumerWidget {
|
|||
|
||||
Future<void> updateAppSettings(GroupAssetsBy groupBy) async {
|
||||
await ref.read(settingsProvider).write(.timelineGroupAssetsBy, groupBy);
|
||||
ref.invalidate(appSettingsServiceProvider);
|
||||
ref.invalidate(timelineServiceProvider);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
||||
import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart';
|
||||
|
|
@ -34,9 +33,6 @@ class LayoutSettings extends HookConsumerWidget {
|
|||
maxValue: 6,
|
||||
minValue: 2,
|
||||
noDivisons: 4,
|
||||
onChangeEnd: (value) {
|
||||
ref.invalidate(appSettingsServiceProvider);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import 'package:easy_localization/easy_localization.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_group_settings.dart';
|
||||
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_layout_settings.dart';
|
||||
|
|
@ -24,7 +23,6 @@ class AssetListSettings extends HookConsumerWidget {
|
|||
title: 'theme_setting_asset_list_storage_indicator_title'.tr(),
|
||||
onChanged: (value) {
|
||||
unawaited(ref.read(settingsProvider).write(.timelineStorageIndicator, value));
|
||||
ref.invalidate(appSettingsServiceProvider);
|
||||
ref.invalidate(settingsProvider);
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
||||
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
|
||||
|
|
@ -31,7 +30,6 @@ class ImageViewerQualitySetting extends HookConsumerWidget {
|
|||
valueNotifier: isOriginal,
|
||||
title: "setting_image_viewer_original_title".t(context: context),
|
||||
subtitle: "setting_image_viewer_original_subtitle".t(context: context),
|
||||
onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,13 +3,14 @@ import 'dart:io';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/app_metadata_key.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/generated/translations.g.dart';
|
||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/app_metadata.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
|
||||
|
|
@ -17,7 +18,6 @@ import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
|
|||
import 'package:immich_mobile/providers/infrastructure/trash_sync.provider.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/providers/sync_status.provider.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/widgets/settings/beta_sync_settings/entity_count_tile.dart';
|
||||
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
||||
import 'package:immich_mobile/widgets/settings/setting_list_tile.dart';
|
||||
|
|
@ -234,7 +234,7 @@ class _SyncStatsCounts extends ConsumerWidget {
|
|||
final localAlbumService = ref.watch(localAlbumServiceProvider);
|
||||
final remoteAlbumService = ref.watch(remoteAlbumServiceProvider);
|
||||
final memoryService = ref.watch(driftMemoryServiceProvider);
|
||||
final appSettingsService = ref.watch(appSettingsServiceProvider);
|
||||
final appMetadataRepository = ref.watch(appMetadataRepositoryProvider);
|
||||
|
||||
Future<List<dynamic>> loadCounts() async {
|
||||
final assetCounts = assetService.getAssetCounts();
|
||||
|
|
@ -242,8 +242,16 @@ class _SyncStatsCounts extends ConsumerWidget {
|
|||
final remoteAlbumCounts = remoteAlbumService.getCount();
|
||||
final memoryCount = memoryService.getCount();
|
||||
final getLocalHashedCount = assetService.getLocalHashedCount();
|
||||
final manageLocalMediaAndroid = appMetadataRepository.get(AppMetadataKey.manageLocalMediaAndroid);
|
||||
|
||||
return await Future.wait([assetCounts, localAlbumCounts, remoteAlbumCounts, memoryCount, getLocalHashedCount]);
|
||||
return await Future.wait([
|
||||
assetCounts,
|
||||
localAlbumCounts,
|
||||
remoteAlbumCounts,
|
||||
memoryCount,
|
||||
getLocalHashedCount,
|
||||
manageLocalMediaAndroid,
|
||||
]);
|
||||
}
|
||||
|
||||
return FutureBuilder(
|
||||
|
|
@ -269,14 +277,15 @@ class _SyncStatsCounts extends ConsumerWidget {
|
|||
);
|
||||
}
|
||||
|
||||
final assetCounts = snapshot.data![0]! as (int, int);
|
||||
final assetCounts = snapshot.data![0] as (int, int);
|
||||
final localAssetCount = assetCounts.$1;
|
||||
final remoteAssetCount = assetCounts.$2;
|
||||
|
||||
final localAlbumCount = snapshot.data![1]! as int;
|
||||
final remoteAlbumCount = snapshot.data![2]! as int;
|
||||
final memoryCount = snapshot.data![3]! as int;
|
||||
final localHashedCount = snapshot.data![4]! as int;
|
||||
final localAlbumCount = snapshot.data![1] as int;
|
||||
final remoteAlbumCount = snapshot.data![2] as int;
|
||||
final memoryCount = snapshot.data![3] as int;
|
||||
final localHashedCount = snapshot.data![4] as int;
|
||||
final manageLocalMediaAndroid = snapshot.data![5] as bool;
|
||||
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
|
|
@ -369,8 +378,7 @@ class _SyncStatsCounts extends ConsumerWidget {
|
|||
),
|
||||
),
|
||||
// To be removed once the experimental feature is stable
|
||||
if (CurrentPlatform.isAndroid &&
|
||||
appSettingsService.getSetting<bool>(AppSettingsEnum.manageLocalMediaAndroid)) ...[
|
||||
if (CurrentPlatform.isAndroid && manageLocalMediaAndroid) ...[
|
||||
SettingGroupTitle(title: "trash".t(context: context)),
|
||||
Consumer(
|
||||
builder: (context, ref, _) {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import 'package:drift/drift.dart' as drift;
|
|||
import 'package:drift/native.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/domain/models/app_metadata_key.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/services/local_sync.service.dart';
|
||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
|
|
@ -29,6 +29,7 @@ void main() {
|
|||
late AssetMediaRepository mockAssetMediaRepository;
|
||||
late MockPermissionRepository mockPermissionRepository;
|
||||
late MockNativeSyncApi mockNativeSyncApi;
|
||||
late MockAppMetadataRepository mockAppMetadataRepository;
|
||||
late Drift db;
|
||||
|
||||
setUpAll(() async {
|
||||
|
|
@ -52,6 +53,7 @@ void main() {
|
|||
mockAssetMediaRepository = MockAssetMediaRepository();
|
||||
mockPermissionRepository = MockPermissionRepository();
|
||||
mockNativeSyncApi = MockNativeSyncApi();
|
||||
mockAppMetadataRepository = MockAppMetadataRepository();
|
||||
|
||||
when(() => mockNativeSyncApi.shouldFullSync()).thenAnswer((_) async => false);
|
||||
when(() => mockNativeSyncApi.getMediaChanges()).thenAnswer(
|
||||
|
|
@ -75,15 +77,16 @@ void main() {
|
|||
assetMediaRepository: mockAssetMediaRepository,
|
||||
permissionRepository: mockPermissionRepository,
|
||||
nativeSyncApi: mockNativeSyncApi,
|
||||
appMetadataRepository: mockAppMetadataRepository,
|
||||
);
|
||||
|
||||
await Store.put(StoreKey.manageLocalMediaAndroid, false);
|
||||
when(() => mockAppMetadataRepository.get(AppMetadataKey.manageLocalMediaAndroid)).thenAnswer((_) async => false);
|
||||
when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => false);
|
||||
});
|
||||
|
||||
group('LocalSyncService - syncTrashedAssets gating', () {
|
||||
test('invokes syncTrashedAssets when Android flag enabled and permission granted', () async {
|
||||
await Store.put(StoreKey.manageLocalMediaAndroid, true);
|
||||
when(() => mockAppMetadataRepository.get(AppMetadataKey.manageLocalMediaAndroid)).thenAnswer((_) async => true);
|
||||
when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true);
|
||||
|
||||
await sut.sync();
|
||||
|
|
@ -93,7 +96,7 @@ void main() {
|
|||
});
|
||||
|
||||
test('skips syncTrashedAssets when store flag disabled', () async {
|
||||
await Store.put(StoreKey.manageLocalMediaAndroid, false);
|
||||
when(() => mockAppMetadataRepository.get(AppMetadataKey.manageLocalMediaAndroid)).thenAnswer((_) async => false);
|
||||
when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true);
|
||||
|
||||
await sut.sync();
|
||||
|
|
@ -102,7 +105,7 @@ void main() {
|
|||
});
|
||||
|
||||
test('skips syncTrashedAssets when MANAGE_MEDIA permission absent', () async {
|
||||
await Store.put(StoreKey.manageLocalMediaAndroid, true);
|
||||
when(() => mockAppMetadataRepository.get(AppMetadataKey.manageLocalMediaAndroid)).thenAnswer((_) async => true);
|
||||
when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => false);
|
||||
|
||||
await sut.sync();
|
||||
|
|
@ -114,7 +117,7 @@ void main() {
|
|||
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
||||
addTearDown(() => debugDefaultTargetPlatformOverride = TargetPlatform.android);
|
||||
|
||||
await Store.put(StoreKey.manageLocalMediaAndroid, true);
|
||||
when(() => mockAppMetadataRepository.get(AppMetadataKey.manageLocalMediaAndroid)).thenAnswer((_) async => true);
|
||||
when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true);
|
||||
|
||||
await sut.sync();
|
||||
|
|
|
|||
|
|
@ -22,14 +22,14 @@ void main() {
|
|||
mockDriftStoreRepo = MockDriftStoreRepository();
|
||||
// For generics, we need to provide fallback to each concrete type to avoid runtime errors
|
||||
registerFallbackValue(StoreKey.legacyAccessToken);
|
||||
registerFallbackValue(StoreKey.version);
|
||||
registerFallbackValue(StoreKey.legacyVersion);
|
||||
registerFallbackValue(StoreKey.legacyAdvancedTroubleshooting);
|
||||
|
||||
when(() => mockDriftStoreRepo.getAll()).thenAnswer(
|
||||
(_) async => [
|
||||
const StoreDto(StoreKey.legacyAccessToken, _kAccessToken),
|
||||
const StoreDto(StoreKey.legacyAdvancedTroubleshooting, _kAdvancedTroubleshooting),
|
||||
const StoreDto(StoreKey.version, _kVersion),
|
||||
const StoreDto(StoreKey.legacyVersion, _kVersion),
|
||||
],
|
||||
);
|
||||
when(() => mockDriftStoreRepo.watchAll()).thenAnswer((_) => controller.stream);
|
||||
|
|
@ -47,7 +47,7 @@ void main() {
|
|||
verify(() => mockDriftStoreRepo.getAll()).called(1);
|
||||
expect(sut.tryGet(StoreKey.legacyAccessToken), _kAccessToken);
|
||||
expect(sut.tryGet(StoreKey.legacyAdvancedTroubleshooting), _kAdvancedTroubleshooting);
|
||||
expect(sut.tryGet(StoreKey.version), _kVersion);
|
||||
expect(sut.tryGet(StoreKey.legacyVersion), _kVersion);
|
||||
// Other keys should be null
|
||||
expect(sut.tryGet(StoreKey.deviceId), isNull);
|
||||
});
|
||||
|
|
@ -148,7 +148,7 @@ void main() {
|
|||
verify(() => mockDriftStoreRepo.deleteAll()).called(1);
|
||||
expect(sut.tryGet(StoreKey.legacyAccessToken), isNull);
|
||||
expect(sut.tryGet(StoreKey.legacyAdvancedTroubleshooting), isNull);
|
||||
expect(sut.tryGet(StoreKey.version), isNull);
|
||||
expect(sut.tryGet(StoreKey.legacyVersion), isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import 'package:drift/drift.dart' as drift;
|
|||
import 'package:drift/native.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/domain/models/app_metadata_key.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/models/sync_event.model.dart';
|
||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||
import 'package:immich_mobile/domain/services/sync_stream.service.dart';
|
||||
|
|
@ -50,6 +50,7 @@ void main() {
|
|||
late Future<void> Function(List<SyncEvent>, Function(), Function()) handleEventsCallback;
|
||||
late _MockAbortCallbackWrapper mockAbortCallbackWrapper;
|
||||
late _MockAbortCallbackWrapper mockResetCallbackWrapper;
|
||||
late MockAppMetadataRepository mockAppMetadataRepository;
|
||||
late Drift db;
|
||||
late bool hasManageMediaPermission;
|
||||
|
||||
|
|
@ -58,6 +59,8 @@ void main() {
|
|||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
registerFallbackValue(LocalAssetStub.image1);
|
||||
registerFallbackValue(const SemVer(major: 2, minor: 5, patch: 0));
|
||||
registerFallbackValue(AppMetadataKey.syncMigrationStatus);
|
||||
registerFallbackValue(const <String>[]);
|
||||
|
||||
db = Drift(drift.DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
|
||||
await StoreService.init(storeRepository: DriftStoreRepository(db));
|
||||
|
|
@ -83,6 +86,7 @@ void main() {
|
|||
mockApi = MockApiService();
|
||||
mockServerApi = MockServerApi();
|
||||
mockSyncMigrationRepo = MockSyncMigrationRepository();
|
||||
mockAppMetadataRepository = MockAppMetadataRepository();
|
||||
|
||||
when(() => mockAbortCallbackWrapper()).thenReturn(false);
|
||||
|
||||
|
|
@ -158,6 +162,7 @@ void main() {
|
|||
permissionRepository: mockPermissionRepo,
|
||||
api: mockApi,
|
||||
syncMigrationRepository: mockSyncMigrationRepo,
|
||||
appMetadataRepository: mockAppMetadataRepository,
|
||||
);
|
||||
|
||||
when(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).thenAnswer((_) async => {});
|
||||
|
|
@ -171,7 +176,11 @@ void main() {
|
|||
return ids;
|
||||
});
|
||||
when(() => mockAssetMediaRepo.restoreAssetsFromTrash(any())).thenAnswer((_) async => []);
|
||||
await Store.put(StoreKey.manageLocalMediaAndroid, false);
|
||||
when(() => mockAppMetadataRepository.get(AppMetadataKey.manageLocalMediaAndroid)).thenAnswer((_) async => false);
|
||||
when(
|
||||
() => mockAppMetadataRepository.get(AppMetadataKey.syncMigrationStatus),
|
||||
).thenAnswer((_) async => const <String>[]);
|
||||
when(() => mockAppMetadataRepository.set<List<String>, List<String>>(any(), any())).thenAnswer((_) async {});
|
||||
});
|
||||
|
||||
Future<void> simulateEvents(List<SyncEvent> events) async {
|
||||
|
|
@ -242,6 +251,7 @@ void main() {
|
|||
cancellation: cancellation,
|
||||
api: mockApi,
|
||||
syncMigrationRepository: mockSyncMigrationRepo,
|
||||
appMetadataRepository: mockAppMetadataRepository,
|
||||
);
|
||||
await sut.sync();
|
||||
|
||||
|
|
@ -282,6 +292,7 @@ void main() {
|
|||
cancellation: cancellation,
|
||||
api: mockApi,
|
||||
syncMigrationRepository: mockSyncMigrationRepo,
|
||||
appMetadataRepository: mockAppMetadataRepository,
|
||||
);
|
||||
|
||||
await sut.sync();
|
||||
|
|
@ -393,12 +404,12 @@ void main() {
|
|||
|
||||
group("SyncStreamService - remote trash & restore", () {
|
||||
setUp(() async {
|
||||
await Store.put(StoreKey.manageLocalMediaAndroid, true);
|
||||
when(() => mockAppMetadataRepository.get(AppMetadataKey.manageLocalMediaAndroid)).thenAnswer((_) async => true);
|
||||
hasManageMediaPermission = true;
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await Store.put(StoreKey.manageLocalMediaAndroid, false);
|
||||
when(() => mockAppMetadataRepository.get(AppMetadataKey.manageLocalMediaAndroid)).thenAnswer((_) async => false);
|
||||
hasManageMediaPermission = false;
|
||||
});
|
||||
|
||||
|
|
@ -551,7 +562,9 @@ void main() {
|
|||
|
||||
group('SyncStreamService - Sync Migration', () {
|
||||
test('ensure that <2.5.0 migrations run', () async {
|
||||
await Store.put(StoreKey.syncMigrationStatus, "[]");
|
||||
when(
|
||||
() => mockAppMetadataRepository.get(AppMetadataKey.syncMigrationStatus),
|
||||
).thenAnswer((_) async => const <String>[]);
|
||||
when(
|
||||
() => mockServerApi.getServerVersion(),
|
||||
).thenAnswer((_) async => ServerVersionResponseDto(major: 2, minor: 4, patch_: 1, prerelease: null));
|
||||
|
|
@ -579,7 +592,9 @@ void main() {
|
|||
);
|
||||
});
|
||||
test('ensure that >=2.5.0 migrations run', () async {
|
||||
await Store.put(StoreKey.syncMigrationStatus, "[]");
|
||||
when(
|
||||
() => mockAppMetadataRepository.get(AppMetadataKey.syncMigrationStatus),
|
||||
).thenAnswer((_) async => const <String>[]);
|
||||
when(
|
||||
() => mockServerApi.getServerVersion(),
|
||||
).thenAnswer((_) async => ServerVersionResponseDto(major: 2, minor: 5, patch_: 0, prerelease: null));
|
||||
|
|
@ -605,10 +620,9 @@ void main() {
|
|||
});
|
||||
|
||||
test('ensure that migrations do not re-run', () async {
|
||||
await Store.put(
|
||||
StoreKey.syncMigrationStatus,
|
||||
'["${SyncMigrationTask.v20260128_CopyExifWidthHeightToAsset.name}"]',
|
||||
);
|
||||
when(
|
||||
() => mockAppMetadataRepository.get(AppMetadataKey.syncMigrationStatus),
|
||||
).thenAnswer((_) async => [SyncMigrationTask.v20260128_CopyExifWidthHeightToAsset.name]);
|
||||
|
||||
when(
|
||||
() => mockServerApi.getServerVersion(),
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
// dart format width=80
|
||||
// ignore_for_file: unused_local_variable, unused_import
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift_dev/api/migrations_native.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/domain/models/app_metadata_key.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
|
||||
import 'generated/schema.dart';
|
||||
import 'generated/schema_v1.dart' as v1;
|
||||
import 'generated/schema_v2.dart' as v2;
|
||||
import 'generated/schema_v32.dart' as v32;
|
||||
|
||||
void main() {
|
||||
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
|
||||
|
|
@ -35,4 +33,39 @@ void main() {
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
group('data migrations', () {
|
||||
test('v32->v33 backfills the migration', () async {
|
||||
const version = 28;
|
||||
final schema = await verifier.schemaAt(32);
|
||||
|
||||
final oldDb = v32.DatabaseAtV32(schema.newConnection());
|
||||
await oldDb
|
||||
.into(oldDb.storeEntity)
|
||||
.insert(v32.StoreEntityCompanion.insert(id: 0, intValue: const Value(version)));
|
||||
await oldDb.close();
|
||||
|
||||
final db = Drift(schema.newConnection());
|
||||
await verifier.migrateAndValidate(db, 33);
|
||||
|
||||
final cursor = await (db.appMetadataEntity.select()..where((tbl) => tbl.key.equals(AppMetadataKey.version.name)))
|
||||
.map((row) => row.value)
|
||||
.getSingleOrNull();
|
||||
expect(cursor, version.toString());
|
||||
|
||||
await db.close();
|
||||
});
|
||||
|
||||
test('v32->v33 writes no row when the legacy store has none', () async {
|
||||
final schema = await verifier.schemaAt(32);
|
||||
|
||||
final db = Drift(schema.newConnection());
|
||||
await verifier.migrateAndValidate(db, 33);
|
||||
|
||||
final rows = await db.appMetadataEntity.select().get();
|
||||
expect(rows, isEmpty);
|
||||
|
||||
await db.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ Future<void> _populateStore(Drift db) async {
|
|||
batch.insert(
|
||||
db.storeEntity,
|
||||
StoreEntityCompanion(
|
||||
id: Value(StoreKey.version.id),
|
||||
id: Value(StoreKey.legacyVersion.id),
|
||||
intValue: const Value(_kTestVersion),
|
||||
stringValue: const Value(null),
|
||||
),
|
||||
|
|
@ -56,10 +56,10 @@ void main() {
|
|||
|
||||
group('Store Repository converters:', () {
|
||||
test('converts int', () async {
|
||||
int? version = await sut.tryGet(StoreKey.version);
|
||||
int? version = await sut.tryGet(StoreKey.legacyVersion);
|
||||
expect(version, isNull);
|
||||
await sut.upsert(StoreKey.version, _kTestVersion);
|
||||
version = await sut.tryGet(StoreKey.version);
|
||||
await sut.upsert(StoreKey.legacyVersion, _kTestVersion);
|
||||
version = await sut.tryGet(StoreKey.legacyVersion);
|
||||
expect(version, _kTestVersion);
|
||||
});
|
||||
|
||||
|
|
@ -107,10 +107,10 @@ void main() {
|
|||
});
|
||||
|
||||
test('upsert()', () async {
|
||||
int? version = await sut.tryGet(StoreKey.version);
|
||||
int? version = await sut.tryGet(StoreKey.legacyVersion);
|
||||
expect(version, _kTestVersion);
|
||||
await sut.upsert(StoreKey.version, _kTestVersion + 10);
|
||||
version = await sut.tryGet(StoreKey.version);
|
||||
await sut.upsert(StoreKey.legacyVersion, _kTestVersion + 10);
|
||||
version = await sut.tryGet(StoreKey.legacyVersion);
|
||||
expect(version, _kTestVersion + 10);
|
||||
});
|
||||
});
|
||||
|
|
@ -121,10 +121,10 @@ void main() {
|
|||
});
|
||||
|
||||
test('watch()', () async {
|
||||
final stream = sut.watch(StoreKey.version);
|
||||
final stream = sut.watch(StoreKey.legacyVersion);
|
||||
unawaited(expectLater(stream, emitsInOrder([_kTestVersion, _kTestVersion + 10])));
|
||||
await pumpEventQueue();
|
||||
await sut.upsert(StoreKey.version, _kTestVersion + 10);
|
||||
await sut.upsert(StoreKey.legacyVersion, _kTestVersion + 10);
|
||||
});
|
||||
|
||||
test('watchAll()', () async {
|
||||
|
|
@ -134,19 +134,19 @@ void main() {
|
|||
stream,
|
||||
emitsInOrder([
|
||||
[
|
||||
const StoreDto<Object>(StoreKey.version, _kTestVersion),
|
||||
const StoreDto<Object>(StoreKey.legacyVersion, _kTestVersion),
|
||||
const StoreDto<Object>(StoreKey.legacyAccessToken, _kTestAccessToken),
|
||||
const StoreDto<Object>(StoreKey.legacyAdvancedTroubleshooting, _kTestAdvancedTroubleshooting),
|
||||
],
|
||||
[
|
||||
const StoreDto<Object>(StoreKey.version, _kTestVersion + 10),
|
||||
const StoreDto<Object>(StoreKey.legacyVersion, _kTestVersion + 10),
|
||||
const StoreDto<Object>(StoreKey.legacyAccessToken, _kTestAccessToken),
|
||||
const StoreDto<Object>(StoreKey.legacyAdvancedTroubleshooting, _kTestAdvancedTroubleshooting),
|
||||
],
|
||||
]),
|
||||
),
|
||||
);
|
||||
await sut.upsert(StoreKey.version, _kTestVersion + 10);
|
||||
await sut.upsert(StoreKey.legacyVersion, _kTestVersion + 10);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:immich_mobile/infrastructure/repositories/app_metadata.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||
|
|
@ -22,6 +23,8 @@ class MockDriftStoreRepository extends Mock implements DriftStoreRepository {}
|
|||
|
||||
class MockSettingsRepository extends Mock implements SettingsRepository {}
|
||||
|
||||
class MockAppMetadataRepository extends Mock implements AppMetadataRepository {}
|
||||
|
||||
class MockLogRepository extends Mock implements LogRepository {}
|
||||
|
||||
class MockSyncStreamRepository extends Mock implements SyncStreamRepository {}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/domain/models/app_metadata_key.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/app_metadata.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/app_metadata.repository.dart';
|
||||
|
||||
import '../repository_context.dart';
|
||||
|
||||
void main() {
|
||||
late MediumRepositoryContext ctx;
|
||||
late AppMetadataRepository sut;
|
||||
|
||||
setUpAll(() {
|
||||
ctx = MediumRepositoryContext();
|
||||
sut = AppMetadataRepository(ctx.db);
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await ctx.dispose();
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await ctx.db.delete(ctx.db.appMetadataEntity).go();
|
||||
});
|
||||
|
||||
group('get', () {
|
||||
test('a stored NULL value column resolves to the key default', () async {
|
||||
await ctx.db
|
||||
.into(ctx.db.appMetadataEntity)
|
||||
.insert(
|
||||
AppMetadataEntityCompanion.insert(
|
||||
key: AppMetadataKey.manageLocalMediaAndroid.name,
|
||||
value: const .new(null),
|
||||
updatedAt: .new(DateTime.now()),
|
||||
),
|
||||
);
|
||||
|
||||
expect(await sut.get(.manageLocalMediaAndroid), false);
|
||||
});
|
||||
});
|
||||
|
||||
group('defaults', () {
|
||||
test('falls back to the key default when the value is absent', () async {
|
||||
expect(await sut.get(.version), kCurrentVersion);
|
||||
expect(await sut.get(.syncMigrationStatus), const <String>[]);
|
||||
expect(await sut.get(.manageLocalMediaAndroid), false);
|
||||
});
|
||||
|
||||
test('a stored value takes precedence over the default', () async {
|
||||
await sut.set(.version, 5);
|
||||
await sut.set(.syncMigrationStatus, const ['task']);
|
||||
await sut.set(.manageLocalMediaAndroid, true);
|
||||
|
||||
expect(await sut.get(.version), 5);
|
||||
expect(await sut.get(.syncMigrationStatus), const ['task']);
|
||||
expect(await sut.get(.manageLocalMediaAndroid), true);
|
||||
});
|
||||
});
|
||||
|
||||
group('set', () {
|
||||
test('round-trips int, List and bool values to their typed form', () async {
|
||||
await sut.set(.version, 42);
|
||||
await sut.set(.syncMigrationStatus, const ['task']);
|
||||
await sut.set(.manageLocalMediaAndroid, true);
|
||||
|
||||
expect(await sut.get(.version), 42);
|
||||
expect(await sut.get(.syncMigrationStatus), const ['task']);
|
||||
expect(await sut.get(.manageLocalMediaAndroid), true);
|
||||
});
|
||||
|
||||
test('overwrites the existing value and keeps a single row per key', () async {
|
||||
await sut.set(.version, 1);
|
||||
await sut.set(.version, 2);
|
||||
|
||||
expect(await sut.get(.version), 2);
|
||||
expect(await ctx.db.select(ctx.db.appMetadataEntity).get(), hasLength(1));
|
||||
});
|
||||
});
|
||||
|
||||
group('cache-less reads', () {
|
||||
test('observes a value mutated directly in the DB', () async {
|
||||
await sut.set(.version, 10);
|
||||
|
||||
await (ctx.db.update(ctx.db.appMetadataEntity)..where((r) => r.key.equals(AppMetadataKey.version.name))).write(
|
||||
AppMetadataEntityCompanion(value: .new(AppMetadataKey.version.encode(99))),
|
||||
);
|
||||
|
||||
expect(await sut.get(.version), 99);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ import 'package:immich_mobile/domain/services/user.service.dart';
|
|||
import 'package:immich_mobile/domain/utils/background_sync.dart';
|
||||
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/services/auth.service.dart';
|
||||
import 'package:immich_mobile/services/background_upload.service.dart';
|
||||
import 'package:immich_mobile/services/cleanup.service.dart';
|
||||
|
|
@ -24,14 +23,10 @@ class MockApiService extends Mock implements ApiService {}
|
|||
|
||||
class MockNetworkService extends Mock implements NetworkService {}
|
||||
|
||||
class MockAppSettingService extends Mock implements AppSettingsService {}
|
||||
|
||||
class MockStoreService extends Mock implements StoreService {}
|
||||
|
||||
class MockNativeSyncApi extends Mock implements NativeSyncApi {}
|
||||
|
||||
class MockAppSettingsService extends Mock implements AppSettingsService {}
|
||||
|
||||
class MockPartnerService extends Mock implements PartnerService {}
|
||||
|
||||
class MockAssetService extends Mock implements AssetService {}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ class RepositoryMocks {
|
|||
final localAsset = LocalAssetRepositoryStub(MockDriftLocalAssetRepository());
|
||||
final remoteAsset = RemoteAssetRepositoryStub(MockRemoteAssetRepository());
|
||||
final remoteExif = RemoteExifRepositoryStub(MockRemoteExifRepository());
|
||||
final metadata = MockAppMetadataRepository();
|
||||
final trashedAsset = MockTrashedLocalAssetRepository();
|
||||
final remoteAlbum = MockRemoteAlbumRepository();
|
||||
final albumApi = MockDriftAlbumApiRepository();
|
||||
|
|
@ -63,6 +64,7 @@ class RepositoryMocks {
|
|||
_stubAssetApiRepository();
|
||||
_stubAssetMediaRepository();
|
||||
_stubDownloadRepository();
|
||||
_stubAppMetadataRepository();
|
||||
}
|
||||
|
||||
void _stubRemoteAssetRepository() {
|
||||
|
|
@ -101,6 +103,10 @@ class RepositoryMocks {
|
|||
void _stubDownloadRepository() {
|
||||
when(download.downloadAllAssets).thenAnswer((_) async => const []);
|
||||
}
|
||||
|
||||
void _stubAppMetadataRepository() {
|
||||
when(() => metadata.get(.manageLocalMediaAndroid)).thenAnswer((_) async => false);
|
||||
}
|
||||
}
|
||||
|
||||
class ServiceMocks {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||
import 'package:immich_mobile/generated/translations.g.dart';
|
||||
import 'package:immich_mobile/presentation/actions/action.widget.dart';
|
||||
import 'package:immich_mobile/presentation/actions/delete.action.dart';
|
||||
|
|
@ -31,7 +29,6 @@ void main() {
|
|||
|
||||
tearDown(() async {
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
await StoreService.I.put(StoreKey.manageLocalMediaAndroid, false);
|
||||
await context.dispose();
|
||||
});
|
||||
|
||||
|
|
@ -190,7 +187,7 @@ void main() {
|
|||
|
||||
testWidgets('local only delete on Android with MANAGE_MEDIA shows the prompt', (tester) async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
await StoreService.I.put(StoreKey.manageLocalMediaAndroid, true);
|
||||
when(() => context.repository.metadata.get(.manageLocalMediaAndroid)).thenAnswer((_) async => true);
|
||||
final asset = LocalAssetFactory.create();
|
||||
|
||||
await pumpDelete(tester, {asset});
|
||||
|
|
@ -207,7 +204,7 @@ void main() {
|
|||
|
||||
testWidgets('local only delete on Android with MANAGE_MEDIA deletes nothing when cancelled', (tester) async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
await StoreService.I.put(StoreKey.manageLocalMediaAndroid, true);
|
||||
when(() => context.repository.metadata.get(.manageLocalMediaAndroid)).thenAnswer((_) async => true);
|
||||
final asset = LocalAssetFactory.create();
|
||||
|
||||
await pumpDelete(tester, {asset});
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'
|
|||
import 'package:immich_mobile/presentation/actions/action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/action.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/app_metadata.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
||||
import 'package:immich_mobile/providers/routes.provider.dart';
|
||||
|
|
@ -60,6 +61,7 @@ class PresentationContext {
|
|||
inLockedViewProvider.overrideWithValue(false),
|
||||
remoteAssetRepositoryProvider.overrideWithValue(repository.remoteAsset.repo),
|
||||
assetMediaRepositoryProvider.overrideWithValue(repository.assetMedia.api),
|
||||
appMetadataRepositoryProvider.overrideWithValue(repository.metadata),
|
||||
];
|
||||
|
||||
List<Override> selected(Set<BaseAsset> assets) => [
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ void main() {
|
|||
apiRepository: apiRepository,
|
||||
mediaRepository: mocks.assetMedia.api,
|
||||
trashedLocalRepository: mocks.trashedAsset,
|
||||
appMetadataRepository: mocks.metadata,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue