mirror of
https://github.com/immich-app/immich
synced 2026-08-15 13:03:57 +00:00
Merge d71ca6281b into f9c05af45f
This commit is contained in:
commit
249ea89ba5
38 changed files with 4743 additions and 141 deletions
3683
mobile/drift_schemas/main/drift_schema_v32.json
generated
Normal file
3683
mobile/drift_schemas/main/drift_schema_v32.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -40,7 +40,6 @@ void main() {
|
|||
tearDown(() async {
|
||||
await workerManagerPatch.dispose();
|
||||
await server.close();
|
||||
await Store.delete(StoreKey.serverEndpoint);
|
||||
await Store.delete(StoreKey.syncMigrationStatus);
|
||||
});
|
||||
|
||||
|
|
@ -119,7 +118,9 @@ void main() {
|
|||
final releaseTxn = Completer<void>();
|
||||
final txnHeld = Completer<void>();
|
||||
final txn = drift.transaction(() async {
|
||||
await drift.into(drift.userEntity).insert(
|
||||
await drift
|
||||
.into(drift.userEntity)
|
||||
.insert(
|
||||
UserEntityCompanion.insert(
|
||||
id: 'holder',
|
||||
name: 'holder',
|
||||
|
|
|
|||
44
mobile/lib/domain/models/session.model.dart
Normal file
44
mobile/lib/domain/models/session.model.dart
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:immich_mobile/domain/models/value_codec.dart';
|
||||
|
||||
part 'session.model.freezed.dart';
|
||||
|
||||
enum SessionKey<T> {
|
||||
serverUrl<String?>(),
|
||||
accessToken<String?>(),
|
||||
serverEndpoint<String?>();
|
||||
|
||||
ValueCodec<T> get _codec => ValueCodec.forType(T);
|
||||
|
||||
String encode(T value) => _codec.encode(value);
|
||||
|
||||
T decode(String raw) => _codec.decode(raw);
|
||||
}
|
||||
|
||||
const defaultSession = Session();
|
||||
|
||||
@freezed
|
||||
abstract class Session with _$Session {
|
||||
const Session._();
|
||||
|
||||
const factory Session({String? serverUrl, String? accessToken, String? serverEndpoint}) = _Session;
|
||||
|
||||
factory Session.fromEntries(Map<SessionKey, Object?> overrides) =>
|
||||
overrides.entries.fold(const Session(), (session, entry) => session.write(entry.key, entry.value));
|
||||
|
||||
T read<T>(SessionKey<T> key) =>
|
||||
(switch (key) {
|
||||
.serverUrl => serverUrl,
|
||||
.accessToken => accessToken,
|
||||
.serverEndpoint => serverEndpoint,
|
||||
})
|
||||
as T;
|
||||
|
||||
Session write<T, U extends T>(SessionKey<T> key, U value) {
|
||||
return switch (key) {
|
||||
.serverUrl => copyWith(serverUrl: value as String?),
|
||||
.accessToken => copyWith(accessToken: value as String?),
|
||||
.serverEndpoint => copyWith(serverEndpoint: value as String?),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -6,9 +6,6 @@ enum StoreKey<T> {
|
|||
version<int>._(0),
|
||||
currentUser<UserDto>._(2),
|
||||
deviceId<String>._(4),
|
||||
serverUrl<String>._(10),
|
||||
accessToken<String>._(11),
|
||||
serverEndpoint<String>._(12),
|
||||
advancedTroubleshooting<bool>._(114),
|
||||
enableHapticFeedback<bool>._(126),
|
||||
|
||||
|
|
@ -19,6 +16,9 @@ enum StoreKey<T> {
|
|||
syncMigrationStatus<String>._(1013),
|
||||
|
||||
// Legacy keys that have been migrated to the new metadata store
|
||||
legacyServerUrl<String>._(10),
|
||||
legacyAccessToken<String>._(11),
|
||||
legacyServerEndpoint<String>._(12),
|
||||
legacyBackupRequireCharging<bool>._(7),
|
||||
legacyBackupTriggerDelay<int>._(8),
|
||||
legacySyncAlbums<bool>._(131),
|
||||
|
|
|
|||
18
mobile/lib/infrastructure/entities/session.entity.dart
Normal file
18
mobile/lib/infrastructure/entities/session.entity.dart
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||
|
||||
class SessionEntity extends Table with DriftDefaultsMixin {
|
||||
const SessionEntity();
|
||||
|
||||
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 => "session";
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ abstract class CachedKeyValueRepository<K extends Enum, S> {
|
|||
|
||||
Future<void> refresh() async => _snapshot = _build(await selectable().get());
|
||||
|
||||
@protected
|
||||
Stream<S> watchSnapshot() => selectable().watch().map((rows) => _snapshot = _build(rows));
|
||||
|
||||
S _build(List<({String key, String? value})> rows) => buildSnapshot(
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.d
|
|||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_asset_cloud_id.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/session.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/settings.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/stack.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
||||
|
|
@ -67,6 +68,7 @@ import 'package:sqlite_async/sqlite_async.dart';
|
|||
AssetEditEntity,
|
||||
SettingsEntity,
|
||||
AssetOcrEntity,
|
||||
SessionEntity,
|
||||
],
|
||||
include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'},
|
||||
)
|
||||
|
|
@ -120,7 +122,7 @@ class Drift extends $Drift {
|
|||
}
|
||||
|
||||
@override
|
||||
int get schemaVersion => 31;
|
||||
int get schemaVersion => 32;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
|
|
@ -318,6 +320,9 @@ class Drift extends $Drift {
|
|||
from30To31: (m, v31) async {
|
||||
await m.createIndex(v31.idxRemoteAssetUploaded);
|
||||
},
|
||||
from31To32: (m, v32) async {
|
||||
await m.createTable(v32.session);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -16506,6 +16506,603 @@ final class Schema31 extends i0.VersionedSchema {
|
|||
);
|
||||
}
|
||||
|
||||
final class Schema32 extends i0.VersionedSchema {
|
||||
Schema32({required super.database}) : super(version: 32);
|
||||
@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,
|
||||
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,
|
||||
);
|
||||
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,
|
||||
|
|
@ -16537,6 +17134,7 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||
required Future<void> Function(i1.Migrator m, Schema29 schema) from28To29,
|
||||
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,
|
||||
}) {
|
||||
return (currentVersion, database) async {
|
||||
switch (currentVersion) {
|
||||
|
|
@ -16690,6 +17288,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||
final migrator = i1.Migrator(database, schema);
|
||||
await from30To31(migrator, schema);
|
||||
return 31;
|
||||
case 31:
|
||||
final schema = Schema32(database: database);
|
||||
final migrator = i1.Migrator(database, schema);
|
||||
await from31To32(migrator, schema);
|
||||
return 32;
|
||||
default:
|
||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||
}
|
||||
|
|
@ -16727,6 +17330,7 @@ i1.OnUpgrade stepByStep({
|
|||
required Future<void> Function(i1.Migrator m, Schema29 schema) from28To29,
|
||||
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,
|
||||
}) => i0.VersionedSchema.stepByStepHelper(
|
||||
step: migrationSteps(
|
||||
from1To2: from1To2,
|
||||
|
|
@ -16759,5 +17363,6 @@ i1.OnUpgrade stepByStep({
|
|||
from28To29: from28To29,
|
||||
from29To30: from29To30,
|
||||
from30To31: from30To31,
|
||||
from31To32: from31To32,
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/domain/models/session.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/session.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/cached_key_value_repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
|
||||
class SessionRepository extends CachedKeyValueRepository<SessionKey, Session> {
|
||||
final Drift _db;
|
||||
|
||||
SessionRepository._(this._db) : super(const .new());
|
||||
|
||||
static SessionRepository? _instance;
|
||||
|
||||
static SessionRepository get instance {
|
||||
final instance = _instance;
|
||||
if (instance == null) {
|
||||
throw StateError('SessionRepository not initialized. Call ensureInitialized() first');
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
static Future<SessionRepository> ensureInitialized(Drift db) async {
|
||||
if (_instance == null) {
|
||||
final instance = SessionRepository._(db);
|
||||
await instance.refresh();
|
||||
_instance = instance;
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
@override
|
||||
List<SessionKey> get keys => SessionKey.values;
|
||||
|
||||
@override
|
||||
Object decodeValue(SessionKey key, String raw) => key.decode(raw);
|
||||
|
||||
@override
|
||||
Session buildSnapshot(Map<SessionKey, Object?> overrides) => Session.fromEntries(overrides);
|
||||
|
||||
@override
|
||||
@protected
|
||||
Selectable<({String key, String? value})> selectable() =>
|
||||
_db.select(_db.sessionEntity).map((row) => (key: row.key, value: row.value));
|
||||
|
||||
Session get session => snapshot;
|
||||
|
||||
Future<void> clear(Iterable<SessionKey> keys) async {
|
||||
if (keys.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final names = keys.map((key) => key.name).toList();
|
||||
await (_db.delete(_db.sessionEntity)..where((row) => row.key.isIn(names))).go();
|
||||
|
||||
var session = snapshot;
|
||||
for (final key in keys) {
|
||||
session = session.write(key, defaultSession.read(key));
|
||||
}
|
||||
snapshot = session;
|
||||
}
|
||||
|
||||
Future<void> write<T, U extends T>(SessionKey<T> key, U value) async {
|
||||
if (value == snapshot.read(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String? resolvedValue;
|
||||
if (value != null) {
|
||||
resolvedValue = key.encode(value);
|
||||
}
|
||||
|
||||
await _db
|
||||
.into(_db.sessionEntity)
|
||||
.insertOnConflictUpdate(
|
||||
SessionEntityCompanion.insert(key: key.name, value: .new(resolvedValue), updatedAt: .new(DateTime.now())),
|
||||
);
|
||||
snapshot = snapshot.write(key, value);
|
||||
}
|
||||
|
||||
Stream<Session> watch() => watchSnapshot();
|
||||
}
|
||||
|
|
@ -49,6 +49,7 @@ class SettingsRepository extends CachedKeyValueRepository<SettingsKey, AppConfig
|
|||
AppConfig buildSnapshot(Map<SettingsKey, Object?> overrides) => AppConfig.fromEntries(overrides);
|
||||
|
||||
@override
|
||||
@protected
|
||||
Selectable<({String key, String? value})> selectable() =>
|
||||
_db.select(_db.settingsEntity).map((row) => (key: row.key, value: row.value));
|
||||
|
||||
|
|
@ -91,5 +92,5 @@ class SettingsRepository extends CachedKeyValueRepository<SettingsKey, AppConfig
|
|||
snapshot = snapshot.write(key, value);
|
||||
}
|
||||
|
||||
Stream<AppConfig> watchConfig() => watchSnapshot();
|
||||
Stream<AppConfig> watch() => watchSnapshot();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:flutter/services.dart';
|
|||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/locales.dart';
|
||||
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
||||
import 'package:immich_mobile/domain/models/session.model.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
||||
|
|
@ -16,6 +17,7 @@ import 'package:immich_mobile/infrastructure/repositories/settings.repository.da
|
|||
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/session.provider.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/providers/view_intent/view_intent_handler.provider.dart';
|
||||
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||
|
|
@ -302,10 +304,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
|||
}
|
||||
|
||||
Future<void> resumeSession() async {
|
||||
final serverUrl = Store.tryGet(StoreKey.serverUrl);
|
||||
final endpoint = Store.tryGet(StoreKey.serverEndpoint);
|
||||
final accessToken = Store.tryGet(StoreKey.accessToken);
|
||||
|
||||
final Session(:serverUrl, serverEndpoint: endpoint, :accessToken) = ref.read(sessionProvider);
|
||||
if (accessToken != null && serverUrl != null && endpoint != null) {
|
||||
final infoProvider = ref.read(serverInfoProvider.notifier);
|
||||
final wsProvider = ref.read(websocketProvider.notifier);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
||||
import 'package:immich_mobile/generated/translations.g.dart';
|
||||
import 'package:immich_mobile/presentation/actions/action.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/store.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/session.provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class OpenInBrowserAction extends ActionBuilder {
|
||||
|
|
@ -17,7 +17,7 @@ class OpenInBrowserAction extends ActionBuilder {
|
|||
.new(icon: Icons.open_in_browser, label: context.t.open_in_browser, onAction: () => _open(ref));
|
||||
|
||||
Future<void> _open(WidgetRef ref) async {
|
||||
final serverEndpoint = ref.read(storeServiceProvider).get(.serverEndpoint).replaceFirst('/api', '');
|
||||
final serverEndpoint = ref.read(sessionProvider).serverEndpoint!.replaceFirst('/api', '');
|
||||
final url = Uri.parse('$serverEndpoint${webPathFor(origin)}/photos/$remoteId');
|
||||
|
||||
if (await canLaunchUrl(url)) {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ import 'dart:io';
|
|||
import 'package:flutter/material.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/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
||||
|
|
@ -13,6 +11,7 @@ import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.pro
|
|||
import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart';
|
||||
import 'package:immich_mobile/providers/cast.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/session.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
|
@ -148,12 +147,11 @@ class _NativeVideoViewerState extends ConsumerState<NativeVideoViewer> with Widg
|
|||
}
|
||||
|
||||
final remoteAsset = videoAsset as RemoteAsset;
|
||||
|
||||
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
||||
if (!context.mounted) {
|
||||
if (!mounted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final serverEndpoint = ref.read(sessionProvider).serverEndpoint!;
|
||||
final isOriginalVideo = ref.read(appConfigProvider).viewer.loadOriginalVideo;
|
||||
final String postfixUrl = isOriginalVideo ? 'original' : 'video/playback';
|
||||
final String assetId = remoteAsset.livePhotoVideoId ?? remoteAsset.id;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/session.provider.dart';
|
||||
|
||||
class PartnerUserAvatar extends StatelessWidget {
|
||||
class PartnerUserAvatar extends ConsumerWidget {
|
||||
const PartnerUserAvatar({super.key, required this.userId, required this.name});
|
||||
|
||||
final String userId;
|
||||
final String name;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final url = "${Store.get(StoreKey.serverEndpoint)}/users/$userId/profile-image";
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final url = "${ref.watch(sessionProvider.select((s) => s.serverEndpoint))!}/users/$userId/profile-image";
|
||||
final nameFirstLetter = name.isNotEmpty ? name[0] : "";
|
||||
return CircleAvatar(
|
||||
radius: 16,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'dart:convert';
|
|||
import 'package:flutter_udid/flutter_udid.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/constants.dart';
|
||||
import 'package:immich_mobile/domain/models/session.model.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/domain/services/user.service.dart';
|
||||
|
|
@ -10,6 +11,7 @@ import 'package:immich_mobile/entities/store.entity.dart';
|
|||
import 'package:immich_mobile/models/auth/auth_state.model.dart';
|
||||
import 'package:immich_mobile/models/auth/login_response.model.dart';
|
||||
import 'package:immich_mobile/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/session.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
|
|
@ -125,10 +127,10 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
|||
}
|
||||
|
||||
Future<bool> saveAuthInfo({required String accessToken}) async {
|
||||
await Store.put(StoreKey.accessToken, accessToken);
|
||||
await _ref.read(sessionRepository).write(SessionKey.accessToken, accessToken);
|
||||
await _apiService.updateHeaders();
|
||||
|
||||
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
||||
final serverEndpoint = _ref.read(sessionProvider).serverEndpoint!;
|
||||
final headerMap = _ref.read(appConfigProvider).network.customHeaders;
|
||||
final customHeaders = headerMap.isEmpty ? null : jsonEncode(headerMap);
|
||||
await _widgetService.writeCredentials(serverEndpoint, accessToken, customHeaders);
|
||||
|
|
@ -194,9 +196,9 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
|||
return _ref.read(appConfigProvider).network.localEndpoint;
|
||||
}
|
||||
|
||||
/// Returns the current server endpoint (with /api) URL from the store
|
||||
/// Returns the current server endpoint (with /api) URL from the session
|
||||
String? getServerEndpoint() {
|
||||
return Store.tryGet(StoreKey.serverEndpoint);
|
||||
return _ref.read(sessionProvider).serverEndpoint;
|
||||
}
|
||||
|
||||
Future<String?> setOpenApiServiceEndpoint() {
|
||||
|
|
|
|||
12
mobile/lib/providers/infrastructure/session.provider.dart
Normal file
12
mobile/lib/providers/infrastructure/session.provider.dart
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/session.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||
|
||||
final sessionRepository = Provider.autoDispose<SessionRepository>((_) => SessionRepository.instance);
|
||||
|
||||
final sessionProvider = Provider.autoDispose<Session>((ref) {
|
||||
final repo = ref.watch(sessionRepository);
|
||||
final subscription = repo.watch().listen((event) => ref.state = event);
|
||||
ref.onDispose(subscription.cancel);
|
||||
return repo.session;
|
||||
});
|
||||
|
|
@ -6,7 +6,7 @@ final settingsProvider = Provider.autoDispose<SettingsRepository>((_) => Setting
|
|||
|
||||
final appConfigProvider = Provider.autoDispose<AppConfig>((ref) {
|
||||
final repo = ref.watch(settingsProvider);
|
||||
final subscription = repo.watchConfig().listen((event) => ref.state = event);
|
||||
final subscription = repo.watch().listen((event) => ref.state = event);
|
||||
ref.onDispose(subscription.cancel);
|
||||
return repo.appConfig;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,12 +3,11 @@ import 'dart:async';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.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/infrastructure/repositories/network.repository.dart';
|
||||
import 'package:immich_mobile/models/server_info/server_version.model.dart';
|
||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/session.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/utils/debounce.dart';
|
||||
|
|
@ -53,7 +52,7 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
|||
|
||||
if (authenticationState.isAuthenticated) {
|
||||
try {
|
||||
final endpoint = Uri.parse(Store.get(StoreKey.serverEndpoint));
|
||||
final endpoint = Uri.parse(_ref.read(sessionProvider).serverEndpoint!);
|
||||
dPrint(() => "Attempting to connect to websocket");
|
||||
// Configure socket transports must be specified
|
||||
final Socket socket = io(
|
||||
|
|
|
|||
|
|
@ -6,9 +6,8 @@ import 'package:background_downloader/background_downloader.dart';
|
|||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:immich_mobile/constants/constants.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||
import 'package:immich_mobile/utils/debug_print.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
|
|
@ -96,7 +95,7 @@ class UploadRepository {
|
|||
void Function(int bytes, int totalBytes)? onProgress,
|
||||
required String logContext,
|
||||
}) async {
|
||||
final String savedEndpoint = Store.get(StoreKey.serverEndpoint);
|
||||
final String savedEndpoint = SessionRepository.instance.session.serverEndpoint!;
|
||||
final baseRequest = ProgressMultipartRequest(
|
||||
'POST',
|
||||
Uri.parse('$savedEndpoint/assets'),
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ import 'dart:async';
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/services/auth.service.dart';
|
||||
|
|
@ -23,10 +21,8 @@ class AuthGuard extends AutoRouteGuard {
|
|||
// guards, so we keep this function fully sync and validate the token in
|
||||
// the background — otherwise a slow validateAccessToken() request would
|
||||
// block the route transition for as long as the OS-level HTTP timeout.
|
||||
try {
|
||||
Store.get(StoreKey.accessToken);
|
||||
} on StoreKeyNotFoundException catch (_) {
|
||||
_log.warning('No access token in the store.');
|
||||
if (SessionRepository.instance.session.accessToken == null) {
|
||||
_log.warning('No access token in the session.');
|
||||
resolver.next(false);
|
||||
unawaited(router.replaceAll([const LoginRoute()]));
|
||||
return;
|
||||
|
|
@ -40,7 +36,7 @@ class AuthGuard extends AutoRouteGuard {
|
|||
if (_validateInFlight) {
|
||||
return;
|
||||
}
|
||||
final token = Store.tryGet(StoreKey.accessToken);
|
||||
final token = SessionRepository.instance.session.accessToken;
|
||||
if (token == null) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -50,7 +46,7 @@ class AuthGuard extends AutoRouteGuard {
|
|||
if (res == null || res.authStatus != true) {
|
||||
// Token may have changed during validation (user logged out + logged in
|
||||
// again); only act if it still applies to the current session.
|
||||
if (Store.tryGet(StoreKey.accessToken) != token) {
|
||||
if (SessionRepository.instance.session.accessToken != token) {
|
||||
return;
|
||||
}
|
||||
_log.fine('User token is invalid. Redirecting to login');
|
||||
|
|
@ -61,7 +57,7 @@ class AuthGuard extends AutoRouteGuard {
|
|||
if (e.code != HttpStatus.unauthorized) {
|
||||
return;
|
||||
}
|
||||
if (Store.tryGet(StoreKey.accessToken) != token) {
|
||||
if (SessionRepository.instance.session.accessToken != token) {
|
||||
return;
|
||||
}
|
||||
_log.warning("Unauthorized access token.");
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ import 'dart:convert';
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/domain/models/session.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||
import 'package:immich_mobile/utils/debug_print.dart';
|
||||
import 'package:immich_mobile/utils/url_helper.dart';
|
||||
|
|
@ -41,7 +41,7 @@ class ApiService {
|
|||
// The below line ensures that the api clients are initialized when the service is instantiated
|
||||
// This is required to avoid late initialization errors when the clients are access before the endpoint is resolved
|
||||
setEndpoint('');
|
||||
final endpoint = Store.tryGet(StoreKey.serverEndpoint);
|
||||
final endpoint = SessionRepository.instance.session.serverEndpoint;
|
||||
if (endpoint != null && endpoint.isNotEmpty) {
|
||||
setEndpoint(endpoint);
|
||||
}
|
||||
|
|
@ -84,7 +84,7 @@ class ApiService {
|
|||
setEndpoint(endpoint);
|
||||
|
||||
// Save in local database for next startup
|
||||
await Store.put(StoreKey.serverEndpoint, endpoint);
|
||||
await SessionRepository.instance.write(SessionKey.serverEndpoint, endpoint);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
|
|
@ -173,7 +173,7 @@ class ApiService {
|
|||
|
||||
static List<String> getServerUrls() {
|
||||
final urls = <String>[];
|
||||
final serverEndpoint = Store.tryGet(StoreKey.serverEndpoint);
|
||||
final serverEndpoint = SessionRepository.instance.session.serverEndpoint;
|
||||
if (serverEndpoint != null && serverEndpoint.isNotEmpty) {
|
||||
urls.add(serverEndpoint);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/session.model.dart';
|
||||
import 'package:immich_mobile/domain/models/settings_key.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/repositories/network.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
||||
import 'package:immich_mobile/models/auth/login_response.model.dart';
|
||||
|
|
@ -55,7 +57,7 @@ class AuthService {
|
|||
Future<String> validateServerUrl(String url) async {
|
||||
final validUrl = await _apiService.resolveAndSetEndpoint(url);
|
||||
await _apiService.setDeviceInfoHeader();
|
||||
await Store.put(StoreKey.serverUrl, validUrl);
|
||||
await SessionRepository.instance.write(SessionKey.serverUrl, validUrl);
|
||||
|
||||
return validUrl;
|
||||
}
|
||||
|
|
@ -119,7 +121,7 @@ class AuthService {
|
|||
await Future.wait([
|
||||
_authRepository.clearLocalData(),
|
||||
Store.delete(StoreKey.currentUser),
|
||||
Store.delete(StoreKey.accessToken),
|
||||
SessionRepository.instance.clear([SessionKey.accessToken]),
|
||||
SettingsRepository.instance.clear(const [
|
||||
.networkAutoEndpointSwitching,
|
||||
.networkPreferredWifiName,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import 'package:immich_mobile/entities/store.entity.dart';
|
|||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
|
|
@ -364,7 +365,7 @@ class BackgroundUploadService {
|
|||
String? latitude,
|
||||
String? longitude,
|
||||
}) async {
|
||||
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
||||
final serverEndpoint = SessionRepository.instance.session.serverEndpoint!;
|
||||
final url = Uri.parse('$serverEndpoint/assets').toString();
|
||||
final headers = ApiService.getRequestHeaders();
|
||||
final deviceId = Store.get(StoreKey.deviceId);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
|||
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||
import 'package:immich_mobile/utils/debug_print.dart';
|
||||
|
|
@ -54,6 +55,7 @@ abstract final class Bootstrap {
|
|||
final DriftStoreRepository storeRepo = DriftStoreRepository(drift);
|
||||
|
||||
await StoreService.init(storeRepository: storeRepo, listenUpdates: listenStoreUpdates);
|
||||
await SessionRepository.ensureInitialized(drift);
|
||||
|
||||
final settingsRepo = await SettingsRepository.ensureInitialized(drift);
|
||||
final logDb = await _initLogger(settingsRepository: settingsRepo, shouldBufferLogs: shouldBufferLogs);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
String getOriginalUrlForRemoteId(final String id, {bool edited = true}) {
|
||||
return '${Store.get(StoreKey.serverEndpoint)}/assets/$id/original?edited=$edited';
|
||||
return '${SessionRepository.instance.session.serverEndpoint!}/assets/$id/original?edited=$edited';
|
||||
}
|
||||
|
||||
String getThumbnailUrlForRemoteId(
|
||||
|
|
@ -12,15 +11,15 @@ String getThumbnailUrlForRemoteId(
|
|||
bool edited = true,
|
||||
String? thumbhash,
|
||||
}) {
|
||||
final url = '${Store.get(StoreKey.serverEndpoint)}/assets/$id/thumbnail?size=$type&edited=$edited';
|
||||
final url = '${SessionRepository.instance.session.serverEndpoint!}/assets/$id/thumbnail?size=$type&edited=$edited';
|
||||
return thumbhash != null ? '$url&c=${Uri.encodeComponent(thumbhash)}' : url;
|
||||
}
|
||||
|
||||
String getPlaybackUrlForRemoteId(final String id) {
|
||||
return '${Store.get(StoreKey.serverEndpoint)}/assets/$id/video/playback?';
|
||||
return '${SessionRepository.instance.session.serverEndpoint!}/assets/$id/video/playback?';
|
||||
}
|
||||
|
||||
String getFaceThumbnailUrl(final String personId, {DateTime? updatedAt}) {
|
||||
final url = '${Store.get(StoreKey.serverEndpoint)}/people/$personId/thumbnail';
|
||||
final url = '${SessionRepository.instance.session.serverEndpoint!}/people/$personId/thumbnail';
|
||||
return updatedAt != null ? '$url?c=${updatedAt.millisecondsSinceEpoch}' : url;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,19 +8,22 @@ import 'package:immich_mobile/constants/colors.dart';
|
|||
import 'package:immich_mobile/constants/enums.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';
|
||||
import 'package:immich_mobile/domain/models/settings_key.dart';
|
||||
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/session.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.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';
|
||||
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||
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 = 26;
|
||||
const int targetVersion = 27;
|
||||
|
||||
Future<void> migrateDatabaseIfNeeded(Drift drift) async {
|
||||
final int? storedVersion = Store.tryGet(StoreKey.version);
|
||||
|
|
@ -37,19 +40,22 @@ Future<void> migrateDatabaseIfNeeded(Drift drift) async {
|
|||
if (storedVersion == null) {
|
||||
await FeatureMessageService(SettingsRepository.instance).markSeen();
|
||||
}
|
||||
if (version < 27) {
|
||||
await _migrateTo27(drift);
|
||||
}
|
||||
|
||||
await Store.put(StoreKey.version, targetVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
Future<void> _migrateTo25() async {
|
||||
final accessToken = Store.tryGet(StoreKey.accessToken);
|
||||
final accessToken = Store.tryGet(StoreKey.legacyAccessToken);
|
||||
if (accessToken == null || accessToken.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final urls = <String>[];
|
||||
final serverEndpoint = Store.tryGet(StoreKey.serverEndpoint);
|
||||
final serverEndpoint = Store.tryGet(StoreKey.legacyServerEndpoint);
|
||||
if (serverEndpoint != null && serverEndpoint.isNotEmpty) {
|
||||
urls.add(serverEndpoint);
|
||||
}
|
||||
|
|
@ -80,7 +86,7 @@ Future<void> _migrateTo25() async {
|
|||
}
|
||||
|
||||
Future<void> _migrateTo26(Drift drift) async {
|
||||
final migrator = _StoreMigrator(drift);
|
||||
final migrator = _StoreMigrator.settings(drift);
|
||||
await migrator.migrateEnumIndex(StoreKey.legacyLogLevel, SettingsKey.logLevel, LogLevel.values);
|
||||
// Theme
|
||||
await migrator.migrateEnumName(StoreKey.legacyThemeMode, SettingsKey.themeMode, ThemeMode.values);
|
||||
|
|
@ -145,7 +151,17 @@ Future<void> _migrateTo26(Drift drift) async {
|
|||
await migrator.complete();
|
||||
}
|
||||
|
||||
Future<void> _migrateAlbumSortMode(_StoreMigrator migrator) async {
|
||||
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.complete();
|
||||
|
||||
await SessionRepository.instance.refresh();
|
||||
}
|
||||
|
||||
Future<void> _migrateAlbumSortMode(_StoreMigrator<SettingsKey> migrator) async {
|
||||
final raw = await migrator.readLegacyStoreInt(StoreKey.legacySelectedAlbumSortOrder.id);
|
||||
final mode = AlbumSortMode.values.firstWhereOrNull((e) => raw != null && e.storeIndex == raw);
|
||||
if (mode == null) {
|
||||
|
|
@ -155,7 +171,7 @@ Future<void> _migrateAlbumSortMode(_StoreMigrator migrator) async {
|
|||
migrator.stage(StoreKey.legacySelectedAlbumSortOrder, SettingsKey.albumSortMode, mode);
|
||||
}
|
||||
|
||||
Future<void> _migrateExternalEndpointList(_StoreMigrator migrator) async {
|
||||
Future<void> _migrateExternalEndpointList(_StoreMigrator<SettingsKey> migrator) async {
|
||||
final raw = await migrator.readLegacyStoreString(StoreKey.legacyExternalEndpointList.id);
|
||||
if (raw == null) {
|
||||
return;
|
||||
|
|
@ -179,7 +195,7 @@ Future<void> _migrateExternalEndpointList(_StoreMigrator migrator) async {
|
|||
migrator.stage(StoreKey.legacyExternalEndpointList, SettingsKey.networkExternalEndpointList, urls);
|
||||
}
|
||||
|
||||
Future<void> _migrateCustomHeaders(_StoreMigrator migrator) async {
|
||||
Future<void> _migrateCustomHeaders(_StoreMigrator<SettingsKey> migrator) async {
|
||||
final raw = await migrator.readLegacyStoreString(StoreKey.legacyCustomHeaders.id);
|
||||
if (raw == null) {
|
||||
return;
|
||||
|
|
@ -202,14 +218,39 @@ Future<void> _migrateCustomHeaders(_StoreMigrator migrator) async {
|
|||
migrator.stage(StoreKey.legacyCustomHeaders, SettingsKey.networkCustomHeaders, headers);
|
||||
}
|
||||
|
||||
class _StoreMigrator {
|
||||
class _StoreMigrator<K extends Enum> {
|
||||
_StoreMigrator._(this._db, {required this.encode, required this.readDefault, required this.insertRow});
|
||||
|
||||
static _StoreMigrator<SettingsKey> settings(Drift db) => _StoreMigrator<SettingsKey>._(
|
||||
db,
|
||||
encode: (key, value) => key.encode(value),
|
||||
readDefault: (key) => defaultConfig.read(key),
|
||||
insertRow: (batch, name, value) => batch.insert(
|
||||
db.settingsEntity,
|
||||
SettingsEntityCompanion(key: Value(name), value: Value(value)),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
),
|
||||
);
|
||||
|
||||
static _StoreMigrator<SessionKey> session(Drift db) => _StoreMigrator<SessionKey>._(
|
||||
db,
|
||||
encode: (key, value) => key.encode(value),
|
||||
readDefault: (key) => defaultSession.read(key),
|
||||
insertRow: (batch, name, value) => batch.insert(
|
||||
db.sessionEntity,
|
||||
SessionEntityCompanion(key: Value(name), value: Value(value)),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
),
|
||||
);
|
||||
|
||||
final Drift _db;
|
||||
final Map<SettingsKey, Object?> _cache = {};
|
||||
final String Function(K key, Object value) encode;
|
||||
final Object? Function(K key) readDefault;
|
||||
final void Function(Batch batch, String name, String? value) insertRow;
|
||||
final Map<K, Object?> _cache = {};
|
||||
final List<int> _migratedStoreIds = [];
|
||||
|
||||
_StoreMigrator(this._db);
|
||||
|
||||
Future<void> migrateEnumIndex<T extends Enum>(StoreKey<int> legacyKey, SettingsKey<T> newKey, List<T> values) async {
|
||||
Future<void> migrateEnumIndex<T extends Enum>(StoreKey<int> legacyKey, K newKey, List<T> values) async {
|
||||
final index = await readLegacyStoreInt(legacyKey.id);
|
||||
if (index == null) {
|
||||
return;
|
||||
|
|
@ -224,11 +265,7 @@ class _StoreMigrator {
|
|||
_migratedStoreIds.add(legacyKey.id);
|
||||
}
|
||||
|
||||
Future<void> migrateEnumName<T extends Enum>(
|
||||
StoreKey<String> legacyKey,
|
||||
SettingsKey<T> newKey,
|
||||
List<T> values,
|
||||
) async {
|
||||
Future<void> migrateEnumName<T extends Enum>(StoreKey<String> legacyKey, K newKey, List<T> values) async {
|
||||
final name = await readLegacyStoreString(legacyKey.id);
|
||||
if (name == null) {
|
||||
return;
|
||||
|
|
@ -243,18 +280,17 @@ class _StoreMigrator {
|
|||
_migratedStoreIds.add(legacyKey.id);
|
||||
}
|
||||
|
||||
Future<void> migrateBool(StoreKey<bool> legacyKey, SettingsKey<bool> newKey) async {
|
||||
Future<void> migrateBool(StoreKey<bool> legacyKey, K newKey) async {
|
||||
final intValue = await readLegacyStoreInt(legacyKey.id);
|
||||
if (intValue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final boolValue = intValue != 0;
|
||||
_cache[newKey] = boolValue;
|
||||
_cache[newKey] = intValue != 0;
|
||||
_migratedStoreIds.add(legacyKey.id);
|
||||
}
|
||||
|
||||
Future<void> migrateInt(StoreKey<int> legacyKey, SettingsKey<int> newKey) async {
|
||||
Future<void> migrateInt(StoreKey<int> legacyKey, K newKey) async {
|
||||
final intValue = await readLegacyStoreInt(legacyKey.id);
|
||||
if (intValue == null) {
|
||||
return;
|
||||
|
|
@ -264,9 +300,9 @@ class _StoreMigrator {
|
|||
_migratedStoreIds.add(legacyKey.id);
|
||||
}
|
||||
|
||||
Future<void> migrateString(StoreKey<String> legacyKey, SettingsKey<String> newKey) async {
|
||||
Future<void> migrateString(StoreKey<String> legacyKey, K newKey) async {
|
||||
final value = await readLegacyStoreString(legacyKey.id);
|
||||
if (value == null) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -274,7 +310,12 @@ class _StoreMigrator {
|
|||
_migratedStoreIds.add(legacyKey.id);
|
||||
}
|
||||
|
||||
void stage<T, U extends T>(StoreKey legacyKey, SettingsKey<T> newKey, U value) {
|
||||
Future<void> migrateNullableString(StoreKey<String> legacyKey, K newKey) async {
|
||||
_cache[newKey] = await readLegacyStoreString(legacyKey.id);
|
||||
_migratedStoreIds.add(legacyKey.id);
|
||||
}
|
||||
|
||||
void stage(StoreKey legacyKey, K newKey, Object? value) {
|
||||
_cache[newKey] = value;
|
||||
_migratedStoreIds.add(legacyKey.id);
|
||||
}
|
||||
|
|
@ -282,20 +323,12 @@ class _StoreMigrator {
|
|||
Future<void> complete() async {
|
||||
await _db.batch((batch) {
|
||||
for (final entry in _cache.entries) {
|
||||
if (entry.value == defaultConfig.read(entry.key)) {
|
||||
if (entry.value == readDefault(entry.key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String? resolvedValue;
|
||||
if (entry.value != null) {
|
||||
resolvedValue = entry.key.encode(entry.value);
|
||||
}
|
||||
|
||||
batch.insert(
|
||||
_db.settingsEntity,
|
||||
SettingsEntityCompanion(key: Value(entry.key.name), value: Value(resolvedValue)),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
final value = entry.value;
|
||||
insertRow(batch, entry.key.name, value == null ? null : encode(entry.key, value));
|
||||
}
|
||||
});
|
||||
await deleteLegacyStoreRows(_migratedStoreIds);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||
import 'package:punycode/punycode.dart';
|
||||
|
||||
/// Normalizes a server URL, guaranteeing that it has a schema and no trailing slashes
|
||||
|
|
@ -30,7 +29,7 @@ bool normalizeAndValidateServerUrl(String? url) {
|
|||
}
|
||||
|
||||
String? getServerUrl() {
|
||||
final serverUrl = punycodeDecodeUrl(Store.tryGet(StoreKey.serverEndpoint));
|
||||
final serverUrl = punycodeDecodeUrl(SessionRepository.instance.session.serverEndpoint);
|
||||
final serverUri = serverUrl != null ? Uri.tryParse(serverUrl) : null;
|
||||
if (serverUri == null) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
|
||||
Widget userAvatar(BuildContext context, UserDto u, {double? radius}) {
|
||||
final url = "${Store.get(StoreKey.serverEndpoint)}/users/${u.id}/profile-image";
|
||||
final url = "${SessionRepository.instance.session.serverEndpoint!}/users/${u.id}/profile-image";
|
||||
final nameFirstLetter = u.name.isNotEmpty ? u.name[0] : "";
|
||||
return CircleAvatar(
|
||||
radius: radius,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/session.provider.dart';
|
||||
|
||||
class UserCircleAvatar extends StatelessWidget {
|
||||
class UserCircleAvatar extends ConsumerWidget {
|
||||
final UserDto user;
|
||||
final double size;
|
||||
final bool hasBorder;
|
||||
|
|
@ -13,10 +13,10 @@ class UserCircleAvatar extends StatelessWidget {
|
|||
const UserCircleAvatar({super.key, this.size = 44, this.hasBorder = false, this.opacity = 1, required this.user});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final userAvatarColor = user.avatarColor.toColor().withValues(alpha: opacity);
|
||||
final profileImageUrl =
|
||||
'${Store.get(StoreKey.serverEndpoint)}/users/${user.id}/profile-image?d=${user.profileChangedAt.millisecondsSinceEpoch}';
|
||||
'${ref.watch(sessionProvider.select((s) => s.serverEndpoint))!}/users/${user.id}/profile-image?d=${user.profileChangedAt.millisecondsSinceEpoch}';
|
||||
|
||||
final textColor = (user.avatarColor.toColor().computeLuminance() > 0.5 ? Colors.black : Colors.white).withValues(
|
||||
alpha: opacity,
|
||||
|
|
|
|||
|
|
@ -21,13 +21,13 @@ void main() {
|
|||
controller = StreamController<List<StoreDto<Object>>>.broadcast();
|
||||
mockDriftStoreRepo = MockDriftStoreRepository();
|
||||
// For generics, we need to provide fallback to each concrete type to avoid runtime errors
|
||||
registerFallbackValue(StoreKey.accessToken);
|
||||
registerFallbackValue(StoreKey.legacyAccessToken);
|
||||
registerFallbackValue(StoreKey.version);
|
||||
registerFallbackValue(StoreKey.advancedTroubleshooting);
|
||||
|
||||
when(() => mockDriftStoreRepo.getAll()).thenAnswer(
|
||||
(_) async => [
|
||||
const StoreDto(StoreKey.accessToken, _kAccessToken),
|
||||
const StoreDto(StoreKey.legacyAccessToken, _kAccessToken),
|
||||
const StoreDto(StoreKey.advancedTroubleshooting, _kAdvancedTroubleshooting),
|
||||
const StoreDto(StoreKey.version, _kVersion),
|
||||
],
|
||||
|
|
@ -45,7 +45,7 @@ void main() {
|
|||
group("Store Service Init:", () {
|
||||
test('Populates the internal cache on init', () {
|
||||
verify(() => mockDriftStoreRepo.getAll()).called(1);
|
||||
expect(sut.tryGet(StoreKey.accessToken), _kAccessToken);
|
||||
expect(sut.tryGet(StoreKey.legacyAccessToken), _kAccessToken);
|
||||
expect(sut.tryGet(StoreKey.advancedTroubleshooting), _kAdvancedTroubleshooting);
|
||||
expect(sut.tryGet(StoreKey.version), _kVersion);
|
||||
// Other keys should be null
|
||||
|
|
@ -53,19 +53,19 @@ void main() {
|
|||
});
|
||||
|
||||
test('Listens to stream of store updates', () async {
|
||||
final event = StoreDto(StoreKey.accessToken, _kAccessToken.toUpperCase());
|
||||
final event = StoreDto(StoreKey.legacyAccessToken, _kAccessToken.toUpperCase());
|
||||
controller.add([event]);
|
||||
|
||||
await pumpEventQueue();
|
||||
|
||||
verify(() => mockDriftStoreRepo.watchAll()).called(1);
|
||||
expect(sut.tryGet(StoreKey.accessToken), _kAccessToken.toUpperCase());
|
||||
expect(sut.tryGet(StoreKey.legacyAccessToken), _kAccessToken.toUpperCase());
|
||||
});
|
||||
});
|
||||
|
||||
group('Store Service get:', () {
|
||||
test('Returns the stored value for the given key', () {
|
||||
expect(sut.get(StoreKey.accessToken), _kAccessToken);
|
||||
expect(sut.get(StoreKey.legacyAccessToken), _kAccessToken);
|
||||
});
|
||||
|
||||
test('Throws StoreKeyNotFoundException for nonexistent keys', () {
|
||||
|
|
@ -83,15 +83,15 @@ void main() {
|
|||
});
|
||||
|
||||
test('Skip insert when value is not modified', () async {
|
||||
await sut.put(StoreKey.accessToken, _kAccessToken);
|
||||
verifyNever(() => mockDriftStoreRepo.upsert<String>(StoreKey.accessToken, any()));
|
||||
await sut.put(StoreKey.legacyAccessToken, _kAccessToken);
|
||||
verifyNever(() => mockDriftStoreRepo.upsert<String>(StoreKey.legacyAccessToken, any()));
|
||||
});
|
||||
|
||||
test('Insert value when modified', () async {
|
||||
final newAccessToken = _kAccessToken.toUpperCase();
|
||||
await sut.put(StoreKey.accessToken, newAccessToken);
|
||||
verify(() => mockDriftStoreRepo.upsert<String>(StoreKey.accessToken, newAccessToken)).called(1);
|
||||
expect(sut.tryGet(StoreKey.accessToken), newAccessToken);
|
||||
await sut.put(StoreKey.legacyAccessToken, newAccessToken);
|
||||
verify(() => mockDriftStoreRepo.upsert<String>(StoreKey.legacyAccessToken, newAccessToken)).called(1);
|
||||
expect(sut.tryGet(StoreKey.legacyAccessToken), newAccessToken);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -108,7 +108,7 @@ void main() {
|
|||
});
|
||||
|
||||
test('Watches a specific key for changes', () async {
|
||||
final stream = sut.watch(StoreKey.accessToken);
|
||||
final stream = sut.watch(StoreKey.legacyAccessToken);
|
||||
final events = <String?>[_kAccessToken, _kAccessToken.toUpperCase(), null, _kAccessToken.toLowerCase()];
|
||||
|
||||
unawaited(expectLater(stream, emitsInOrder(events)));
|
||||
|
|
@ -118,7 +118,7 @@ void main() {
|
|||
}
|
||||
|
||||
await pumpEventQueue();
|
||||
verify(() => mockDriftStoreRepo.watch<String>(StoreKey.accessToken)).called(1);
|
||||
verify(() => mockDriftStoreRepo.watch<String>(StoreKey.legacyAccessToken)).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -128,13 +128,13 @@ void main() {
|
|||
});
|
||||
|
||||
test('Removes the value from the DB', () async {
|
||||
await sut.delete(StoreKey.accessToken);
|
||||
verify(() => mockDriftStoreRepo.delete<String>(StoreKey.accessToken)).called(1);
|
||||
await sut.delete(StoreKey.legacyAccessToken);
|
||||
verify(() => mockDriftStoreRepo.delete<String>(StoreKey.legacyAccessToken)).called(1);
|
||||
});
|
||||
|
||||
test('Removes the value from the cache', () async {
|
||||
await sut.delete(StoreKey.accessToken);
|
||||
expect(sut.tryGet(StoreKey.accessToken), isNull);
|
||||
await sut.delete(StoreKey.legacyAccessToken);
|
||||
expect(sut.tryGet(StoreKey.legacyAccessToken), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -146,7 +146,7 @@ void main() {
|
|||
test('Clears all values from the store', () async {
|
||||
await sut.clear();
|
||||
verify(() => mockDriftStoreRepo.deleteAll()).called(1);
|
||||
expect(sut.tryGet(StoreKey.accessToken), isNull);
|
||||
expect(sut.tryGet(StoreKey.legacyAccessToken), isNull);
|
||||
expect(sut.tryGet(StoreKey.advancedTroubleshooting), isNull);
|
||||
expect(sut.tryGet(StoreKey.version), isNull);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ Future<void> _populateStore(Drift db) async {
|
|||
batch.insert(
|
||||
db.storeEntity,
|
||||
StoreEntityCompanion(
|
||||
id: Value(StoreKey.accessToken.id),
|
||||
id: Value(StoreKey.legacyAccessToken.id),
|
||||
intValue: const Value(null),
|
||||
stringValue: const Value(_kTestAccessToken),
|
||||
),
|
||||
|
|
@ -68,10 +68,10 @@ void main() {
|
|||
});
|
||||
|
||||
test('converts string', () async {
|
||||
String? accessToken = await sut.tryGet(StoreKey.accessToken);
|
||||
String? accessToken = await sut.tryGet(StoreKey.legacyAccessToken);
|
||||
expect(accessToken, isNull);
|
||||
await sut.upsert(StoreKey.accessToken, _kTestAccessToken);
|
||||
accessToken = await sut.tryGet(StoreKey.accessToken);
|
||||
await sut.upsert(StoreKey.legacyAccessToken, _kTestAccessToken);
|
||||
accessToken = await sut.tryGet(StoreKey.legacyAccessToken);
|
||||
expect(accessToken, _kTestAccessToken);
|
||||
});
|
||||
|
||||
|
|
@ -147,12 +147,12 @@ void main() {
|
|||
emitsInOrder([
|
||||
[
|
||||
const StoreDto<Object>(StoreKey.version, _kTestVersion),
|
||||
const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken),
|
||||
const StoreDto<Object>(StoreKey.legacyAccessToken, _kTestAccessToken),
|
||||
const StoreDto<Object>(StoreKey.advancedTroubleshooting, _kTestAdvancedTroubleshooting),
|
||||
],
|
||||
[
|
||||
const StoreDto<Object>(StoreKey.version, _kTestVersion + 10),
|
||||
const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken),
|
||||
const StoreDto<Object>(StoreKey.legacyAccessToken, _kTestAccessToken),
|
||||
const StoreDto<Object>(StoreKey.advancedTroubleshooting, _kTestAdvancedTroubleshooting),
|
||||
],
|
||||
]),
|
||||
|
|
|
|||
118
mobile/test/medium/repositories/session_repository_test.dart
Normal file
118
mobile/test/medium/repositories/session_repository_test.dart
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/domain/models/session.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/session.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||
|
||||
import '../repository_context.dart';
|
||||
|
||||
void main() {
|
||||
late MediumRepositoryContext ctx;
|
||||
late SessionRepository sut;
|
||||
|
||||
setUpAll(() async {
|
||||
ctx = MediumRepositoryContext();
|
||||
sut = await SessionRepository.ensureInitialized(ctx.db);
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await ctx.dispose();
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
await ctx.db.delete(ctx.db.sessionEntity).go();
|
||||
await SessionRepository.instance.refresh();
|
||||
});
|
||||
|
||||
group('defaults', () {
|
||||
test('session returns null fields when DB is empty', () {
|
||||
expect(sut.session.serverUrl, isNull);
|
||||
expect(sut.session.accessToken, isNull);
|
||||
expect(sut.session.serverEndpoint, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('write', () {
|
||||
test('persists a value and reflects it in the composed view', () async {
|
||||
await sut.write(.serverEndpoint, 'https://demo.immich.app/api');
|
||||
expect(sut.session.serverEndpoint, 'https://demo.immich.app/api');
|
||||
});
|
||||
|
||||
test('persists across keys independently', () async {
|
||||
await sut.write(.serverUrl, 'https://demo.immich.app');
|
||||
await sut.write(.accessToken, 'token-123');
|
||||
expect(sut.session.serverUrl, 'https://demo.immich.app');
|
||||
expect(sut.session.accessToken, 'token-123');
|
||||
expect(sut.session.serverEndpoint, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('null values', () {
|
||||
test('a stored NULL value column decodes to null on refresh', () async {
|
||||
await ctx.db
|
||||
.into(ctx.db.sessionEntity)
|
||||
.insert(
|
||||
SessionEntityCompanion.insert(
|
||||
key: SessionKey.accessToken.name,
|
||||
value: const .new(null),
|
||||
updatedAt: .new(DateTime.now()),
|
||||
),
|
||||
);
|
||||
|
||||
await SessionRepository.instance.refresh();
|
||||
expect(sut.session.accessToken, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('sync', () {
|
||||
test('picks up rows that were inserted directly into the DB', () async {
|
||||
await ctx.db
|
||||
.into(ctx.db.sessionEntity)
|
||||
.insert(
|
||||
SessionEntityCompanion.insert(
|
||||
key: SessionKey.serverEndpoint.name,
|
||||
value: const .new('https://demo.immich.app/api'),
|
||||
updatedAt: .new(DateTime.now()),
|
||||
),
|
||||
);
|
||||
expect(sut.session.serverEndpoint, isNull);
|
||||
|
||||
await SessionRepository.instance.refresh();
|
||||
expect(sut.session.serverEndpoint, 'https://demo.immich.app/api');
|
||||
});
|
||||
|
||||
test('drops cached values for rows that were deleted out from under the repo', () async {
|
||||
await sut.write(.serverEndpoint, 'https://demo.immich.app/api');
|
||||
await ctx.db.delete(ctx.db.sessionEntity).go();
|
||||
expect(sut.session.serverEndpoint, 'https://demo.immich.app/api');
|
||||
|
||||
await SessionRepository.instance.refresh();
|
||||
expect(sut.session.serverEndpoint, isNull);
|
||||
});
|
||||
|
||||
test('skips rows whose key is unknown to SessionKey', () async {
|
||||
await ctx.db
|
||||
.into(ctx.db.sessionEntity)
|
||||
.insert(
|
||||
SessionEntityCompanion.insert(
|
||||
key: 'session.unknown.future-key',
|
||||
value: const .new('unknown'),
|
||||
updatedAt: .new(DateTime.now()),
|
||||
),
|
||||
);
|
||||
|
||||
await SessionRepository.instance.refresh();
|
||||
expect(sut.session.serverEndpoint, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('watch', () {
|
||||
test('watchSession emits the new value after a write', () async {
|
||||
final expectation = expectLater(
|
||||
sut.watch().map((s) => s.serverEndpoint),
|
||||
emitsThrough('https://demo.immich.app/api/watch'),
|
||||
);
|
||||
await sut.write(SessionKey.serverEndpoint, 'https://demo.immich.app/api/watch');
|
||||
await expectation;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -142,13 +142,13 @@ void main() {
|
|||
|
||||
group('watch', () {
|
||||
test('watchAppConfig emits the new value after a write', () async {
|
||||
final expectation = expectLater(sut.watchConfig().map((c) => c.theme.mode), emitsThrough(ThemeMode.dark));
|
||||
final expectation = expectLater(sut.watch().map((c) => c.theme.mode), emitsThrough(ThemeMode.dark));
|
||||
await sut.write(SettingsKey.themeMode, ThemeMode.dark);
|
||||
await expectation;
|
||||
});
|
||||
|
||||
test('watchConfig emits the new value after a write', () async {
|
||||
final expectation = expectLater(sut.watchConfig().map((c) => c.logLevel), emitsThrough(LogLevel.warning));
|
||||
final expectation = expectLater(sut.watch().map((c) => c.logLevel), emitsThrough(LogLevel.warning));
|
||||
await sut.write(SettingsKey.logLevel, LogLevel.warning);
|
||||
await expectation;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:flutter/widgets.dart';
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
||||
import 'package:immich_mobile/services/auth.service.dart';
|
||||
|
|
@ -37,6 +38,7 @@ void main() {
|
|||
WidgetsFlutterBinding.ensureInitialized();
|
||||
db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
|
||||
await StoreService.init(storeRepository: DriftStoreRepository(db));
|
||||
await SessionRepository.ensureInitialized(db);
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/session.model.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||
import 'package:immich_mobile/services/background_upload.service.dart';
|
||||
|
|
@ -39,8 +41,8 @@ void main() {
|
|||
db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
|
||||
await StoreService.init(storeRepository: DriftStoreRepository(db));
|
||||
await SettingsRepository.ensureInitialized(db);
|
||||
|
||||
await Store.put(StoreKey.serverEndpoint, 'http://test-server.com');
|
||||
await SessionRepository.ensureInitialized(db);
|
||||
await SessionRepository.instance.write(SessionKey.serverEndpoint, 'https://demo.immich.app');
|
||||
await Store.put(StoreKey.deviceId, 'test-device-id');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import 'package:immich_mobile/domain/models/store.model.dart';
|
|||
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||
import 'package:immich_mobile/repositories/upload.repository.dart';
|
||||
|
|
@ -38,8 +39,8 @@ void main() {
|
|||
db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
|
||||
await StoreService.init(storeRepository: DriftStoreRepository(db));
|
||||
await SettingsRepository.ensureInitialized(db);
|
||||
|
||||
await Store.put(StoreKey.serverEndpoint, 'http://demo.immich.app');
|
||||
await SessionRepository.ensureInitialized(db);
|
||||
await SessionRepository.instance.write(.serverEndpoint, 'http://demo.immich.app');
|
||||
await Store.put(StoreKey.deviceId, 'device-id');
|
||||
|
||||
registerFallbackValue(File('file'));
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ import 'package:flutter_test/flutter_test.dart';
|
|||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/locales.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/user.model.dart';
|
||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||
import 'package:immich_mobile/presentation/actions/action.dart';
|
||||
|
|
@ -72,7 +72,8 @@ class PresentationContext {
|
|||
if (_db == null) {
|
||||
final db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
|
||||
await StoreService.init(storeRepository: DriftStoreRepository(db), listenUpdates: false);
|
||||
await StoreService.I.put(StoreKey.serverEndpoint, serverEndpoint);
|
||||
await SessionRepository.ensureInitialized(db);
|
||||
await SessionRepository.instance.write(.serverEndpoint, serverEndpoint);
|
||||
_db = db;
|
||||
}
|
||||
await SettingsRepository.ensureInitialized(_db!);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||
|
||||
void main() {
|
||||
|
|
@ -12,8 +10,8 @@ void main() {
|
|||
|
||||
setUpAll(() async {
|
||||
final db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
|
||||
await StoreService.init(storeRepository: DriftStoreRepository(db), listenUpdates: false);
|
||||
await StoreService.I.put(StoreKey.serverEndpoint, endpoint);
|
||||
await SessionRepository.ensureInitialized(db);
|
||||
await SessionRepository.instance.write(.serverEndpoint, endpoint);
|
||||
});
|
||||
|
||||
group('getFaceThumbnailUrl', () {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue