chore(mobile): use Drift @DriftAccessor() and collapse some providers

This commit is contained in:
Adam Gastineau 2026-08-10 12:24:17 -07:00
parent 35c2a90fca
commit bc09be718a
92 changed files with 439 additions and 402 deletions

View file

@ -18,8 +18,7 @@ targets:
generate_for: &drift_generate_for
- lib/infrastructure/entities/*.dart
- lib/infrastructure/entities/*.drift
- lib/infrastructure/repositories/db.repository.dart
- lib/infrastructure/repositories/logger_db.repository.dart
- lib/infrastructure/repositories/*.repository.dart
drift_dev:modular:
enabled: true
options: *drift_options

View file

@ -17,10 +17,10 @@ import 'package:maplibre_gl/maplibre_gl.dart';
class AssetService {
final RemoteAssetRepository _remoteRepository;
final RemoteExifRepository _exifRepository;
final DriftLocalAssetRepository _localRepository;
final LocalAssetRepository _localRepository;
final AssetApiRepository _apiRepository;
final AssetMediaRepository _mediaRepository;
final DriftTrashedLocalAssetRepository _trashedLocalRepository;
final TrashedLocalAssetRepository _trashedLocalRepository;
const AssetService({
required this._remoteRepository,
@ -136,13 +136,13 @@ class AssetService {
location: location,
dateTimeOriginal: dateTime,
);
await _remoteRepository.update(
await _remoteRepository.updateAssets(
remoteIds,
isFavorite: isFavorite,
visibility: visibility,
createdAt: parsedDateTime,
);
await _exifRepository.update(
await _exifRepository.updateExif(
remoteIds,
location: location,
dateTimeOriginal: parsedDateTime,
@ -165,7 +165,7 @@ class AssetService {
}
await _apiRepository.delete(remoteIds, true);
await _remoteRepository.delete(remoteIds);
await _remoteRepository.deleteAssets(remoteIds);
}
Future<void> applyEdits(String remoteId, List<AssetEdit> edits) async {
@ -189,7 +189,7 @@ class AssetService {
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
await _trashedLocalRepository.applyTrashedAssets(deletedIds);
} else {
await _localRepository.delete(deletedIds);
await _localRepository.deleteAssets(deletedIds);
}
return deletedIds.length;
}

View file

@ -19,8 +19,6 @@ import 'package:immich_mobile/platform/background_worker_api.g.dart';
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/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
import 'package:immich_mobile/providers/infrastructure/sync.provider.dart';
@ -76,31 +74,32 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
: _backgroundHostApi = BackgroundWorkerBgHostApi() {
final ref = ProviderContainer(overrides: [driftProvider.overrideWith(driftOverride(_drift))]);
_ref = ref;
final db = ref.read(driftProvider);
_localSyncService = LocalSyncService(
localAlbumRepository: ref.read(localAlbumRepository),
localAssetRepository: ref.read(localAssetRepository),
localAlbumRepository: db.localAlbumRepository,
localAssetRepository: db.localAssetRepository,
nativeSyncApi: ref.read(nativeSyncApiProvider),
trashedLocalAssetRepository: ref.read(trashedLocalAssetRepository),
trashedLocalAssetRepository: db.trashedLocalAssetRepository,
assetMediaRepository: ref.read(assetMediaRepositoryProvider),
permissionRepository: ref.read(permissionRepositoryProvider),
cancellation: _cancellationToken,
);
_remoteSyncService = SyncStreamService(
syncApiRepository: ref.read(syncApiRepositoryProvider),
syncStreamRepository: ref.read(syncStreamRepositoryProvider),
localAssetRepository: ref.read(localAssetRepository),
trashedLocalAssetRepository: ref.read(trashedLocalAssetRepository),
syncStreamRepository: db.syncStreamRepository,
localAssetRepository: db.localAssetRepository,
trashedLocalAssetRepository: db.trashedLocalAssetRepository,
assetMediaRepository: ref.read(assetMediaRepositoryProvider),
permissionRepository: ref.read(permissionRepositoryProvider),
syncMigrationRepository: ref.read(syncMigrationRepositoryProvider),
syncMigrationRepository: db.syncMigrationRepository,
api: ref.read(apiServiceProvider),
cancellation: _cancellationToken,
);
_hashService = HashService(
localAlbumRepository: ref.read(localAlbumRepository),
localAssetRepository: ref.read(localAssetRepository),
localAlbumRepository: db.localAlbumRepository,
localAssetRepository: db.localAssetRepository,
nativeSyncApi: ref.read(nativeSyncApiProvider),
trashedLocalAssetRepository: ref.read(trashedLocalAssetRepository),
trashedLocalAssetRepository: db.trashedLocalAssetRepository,
cancellation: _cancellationToken,
);
BackgroundWorkerFlutterApi.setUp(this);

View file

@ -15,9 +15,9 @@ const String _kHashCancelledCode = "HASH_CANCELLED";
class HashService {
final int _batchSize;
final DriftLocalAlbumRepository _localAlbumRepository;
final DriftLocalAssetRepository _localAssetRepository;
final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository;
final LocalAlbumRepository _localAlbumRepository;
final LocalAssetRepository _localAssetRepository;
final TrashedLocalAssetRepository _trashedLocalAssetRepository;
final NativeSyncApi _nativeSyncApi;
final Completer<void>? _cancellation;
final _log = Logger('HashService');

View file

@ -3,7 +3,7 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
class LocalAlbumService {
final DriftLocalAlbumRepository _repository;
final LocalAlbumRepository _repository;
const LocalAlbumService(this._repository);

View file

@ -21,11 +21,11 @@ import 'package:logging/logging.dart';
const String _kSyncCancelledCode = "SYNC_CANCELLED";
class LocalSyncService {
final DriftLocalAlbumRepository _localAlbumRepository;
final LocalAlbumRepository _localAlbumRepository;
// ignore: unused_field
final DriftLocalAssetRepository _localAssetRepository;
final LocalAssetRepository _localAssetRepository;
final NativeSyncApi _nativeSyncApi;
final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository;
final TrashedLocalAssetRepository _trashedLocalAssetRepository;
final AssetMediaRepository _assetMediaRepository;
final DevicePermissionRepository _permissionRepository;
final Completer<void>? _cancellation;
@ -186,7 +186,7 @@ class LocalSyncService {
_log.fine("Removing device album ${a.name}");
try {
// Asset deletion is handled in the repository
await _localAlbumRepository.delete(a.id);
await _localAlbumRepository.deleteAlbum(a.id);
} catch (e, s) {
_log.warning("Error while removing device album", e, s);
}

View file

@ -8,7 +8,7 @@ typedef MapMarkerSource = Future<List<Marker>> Function(LatLngBounds? bounds);
typedef MapQuery = ({MapMarkerSource markerSource});
class MapFactory {
final DriftMapRepository _mapRepository;
final MapRepository _mapRepository;
const MapFactory({required this._mapRepository});

View file

@ -5,7 +5,7 @@ import 'package:logging/logging.dart';
class DriftMemoryService {
final log = Logger("DriftMemoryService");
final DriftMemoryRepository _repository;
final MemoryRepository _repository;
DriftMemoryService(this._repository);

View file

@ -27,7 +27,7 @@ class PartnerService {
Future<void> update({required String sharedById, required String sharedWithId, required bool inTimeline}) async {
await _partnerApiRepository.update(sharedById, inTimeline: inTimeline);
await _partnerRepository.update(sharedById: sharedById, sharedWithId: sharedWithId, inTimeline: inTimeline);
await _partnerRepository.updatePartner(sharedById: sharedById, sharedWithId: sharedWithId, inTimeline: inTimeline);
}
Future<void> create({required String sharedById, required String sharedWithId, bool inTimeline = false}) async {
@ -37,6 +37,6 @@ class PartnerService {
Future<void> delete({required String sharedById, required String sharedWithId}) async {
await _partnerApiRepository.delete(sharedWithId);
await _partnerRepository.delete(sharedById: sharedById, sharedWithId: sharedWithId);
await _partnerRepository.deletePartner(sharedById: sharedById, sharedWithId: sharedWithId);
}
}

View file

@ -5,7 +5,7 @@ import 'package:immich_mobile/infrastructure/repositories/people.repository.dart
import 'package:immich_mobile/repositories/person_api.repository.dart';
class DriftPeopleService {
final DriftPeopleRepository _repository;
final PeopleRepository _repository;
final PersonApiRepository _personApiRepository;
const DriftPeopleService(this._repository, this._personApiRepository);

View file

@ -25,7 +25,7 @@ class AlbumAssetCandidates {
class RemoteAlbumService {
static final _logger = Logger('RemoteAlbumService');
final DriftRemoteAlbumRepository _repository;
final RemoteAlbumRepository _repository;
final DriftAlbumApiRepository _albumApiRepository;
final ForegroundUploadService _uploadService;
@ -152,7 +152,7 @@ class RemoteAlbumService {
);
// Update the local database
await _repository.update(updatedAlbum);
await _repository.updateAlbum(updatedAlbum);
return updatedAlbum;
}

View file

@ -6,13 +6,13 @@ import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'
/// Provides access to a persistent key-value store with an in-memory cache.
/// Listens for repository changes to keep the cache updated.
class StoreService {
final DriftStoreRepository _storeRepository;
final StoreRepository _storeRepository;
/// In-memory cache. Keys are [StoreKey.id]
final Map<int, Object?> _cache = {};
StreamSubscription<List<StoreDto>>? _storeUpdateSubscription;
StoreService._({required DriftStoreRepository isarStoreRepository}) : _storeRepository = isarStoreRepository;
StoreService._({required StoreRepository isarStoreRepository}) : _storeRepository = isarStoreRepository;
// TODO: Temporary typedef to make minimal changes. Remove this and make the presentation layer access store through a provider
static StoreService? _instance;
@ -24,12 +24,12 @@ class StoreService {
}
// TODO: Replace the implementation with the one from create after removing the typedef
static Future<StoreService> init({required DriftStoreRepository storeRepository, bool listenUpdates = true}) async {
static Future<StoreService> init({required StoreRepository storeRepository, bool listenUpdates = true}) async {
_instance ??= await create(storeRepository: storeRepository, listenUpdates: listenUpdates);
return _instance!;
}
static Future<StoreService> create({required DriftStoreRepository storeRepository, bool listenUpdates = true}) async {
static Future<StoreService> create({required StoreRepository storeRepository, bool listenUpdates = true}) async {
final instance = StoreService._(isarStoreRepository: storeRepository);
await instance.populateCache();
if (listenUpdates) {
@ -90,7 +90,7 @@ class StoreService {
/// Removes the value for [key]
Future<void> delete<T>(StoreKey<T> key) async {
await _storeRepository.delete(key);
await _storeRepository.deleteValue(key);
_cache.remove(key.id);
}

View file

@ -6,24 +6,25 @@ import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/services/store.service.dart';
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/store.provider.dart';
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
import 'package:immich_mobile/utils/debug_print.dart';
import 'package:logging/logging.dart';
final syncLinkedAlbumServiceProvider = Provider(
(ref) => SyncLinkedAlbumService(
ref.watch(localAlbumRepository),
ref.watch(remoteAlbumRepository),
final syncLinkedAlbumServiceProvider = Provider((ref) {
final db = ref.watch(driftProvider);
return SyncLinkedAlbumService(
db.localAlbumRepository,
db.remoteAlbumRepository,
ref.watch(driftAlbumApiRepositoryProvider),
ref.watch(storeServiceProvider),
),
);
);
});
class SyncLinkedAlbumService {
final DriftLocalAlbumRepository _localAlbumRepository;
final DriftRemoteAlbumRepository _remoteAlbumRepository;
final LocalAlbumRepository _localAlbumRepository;
final RemoteAlbumRepository _remoteAlbumRepository;
final DriftAlbumApiRepository _albumApiRepository;
final StoreService _storeService;

View file

@ -33,8 +33,8 @@ class SyncStreamService {
final SyncApiRepository _syncApiRepository;
final SyncStreamRepository _syncStreamRepository;
final DriftLocalAssetRepository _localAssetRepository;
final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository;
final LocalAssetRepository _localAssetRepository;
final TrashedLocalAssetRepository _trashedLocalAssetRepository;
final AssetMediaRepository _assetMediaRepository;
final DevicePermissionRepository _permissionRepository;
final SyncMigrationRepository _syncMigrationRepository;

View file

@ -38,7 +38,7 @@ enum TimelineOrigin {
}
class TimelineFactory {
final DriftTimelineRepository _timelineRepository;
final TimelineRepository _timelineRepository;
final SettingsRepository _settingsRepository;
const TimelineFactory({required this._timelineRepository, required this._settingsRepository});

View file

@ -171,7 +171,7 @@ Future<void> _populateCloudIds(Drift drift) async {
);
}
}
await DriftLocalAlbumRepository(drift).updateCloudMapping(cloudMapping);
await LocalAlbumRepository(drift).updateCloudMapping(cloudMapping);
}
typedef _CloudIdMapping = ({String remoteAssetId, LocalAsset localAsset});

View file

@ -1,21 +1,18 @@
import 'dart:async';
import 'package:drift/drift.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/backup.repository.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
final backupRepositoryProvider = Provider<DriftBackupRepository>(
(ref) => DriftBackupRepository(ref.watch(driftProvider)),
);
@DriftAccessor()
class BackupRepository extends DatabaseAccessor<Drift> with $BackupRepositoryMixin {
BackupRepository(super.attachedDatabase);
class DriftBackupRepository extends DriftDatabaseRepository {
final Drift _db;
const DriftBackupRepository(this._db) : super(_db);
Drift get _db => attachedDatabase;
JoinedSelectStatement<$LocalAlbumAssetEntityTable, LocalAlbumAssetEntityData> _getExcludedSubquery() {
return _db.localAlbumAssetEntity.selectOnly()

View file

@ -32,8 +32,27 @@ import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.steps.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/map.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/memory.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/ocr.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/partner.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/people.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
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/stack.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/sync_migration.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/user_metadata.repository.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
@ -69,6 +88,28 @@ import 'package:sqlite_async/sqlite_async.dart';
AssetOcrEntity,
],
include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'},
daos: [
AuthUserRepository,
BackupRepository,
LocalAlbumRepository,
LocalAssetRepository,
MapRepository,
MemoryRepository,
OcrRepository,
PartnerRepository,
PeopleRepository,
RemoteAlbumRepository,
RemoteAssetRepository,
RemoteExifRepository,
StackRepository,
StoreRepository,
SyncMigrationRepository,
SyncStreamRepository,
TimelineRepository,
TrashedLocalAssetRepository,
UserMetadataRepository,
UserRepository,
],
)
class Drift extends $Drift {
final SqliteConnectionPool? _updatePool;
@ -344,13 +385,6 @@ class Drift extends $Drift {
);
}
class DriftDatabaseRepository {
final Drift _db;
const DriftDatabaseRepository(this._db);
Future<T> transaction<T>(Future<T> Function() callback) => _db.transaction(callback);
}
// ignore: invalid_use_of_internal_member
final class _DriftPoolStreamQueries extends StreamQueryStore {
_DriftPoolStreamQueries(this._pool);

View file

@ -10,13 +10,15 @@ import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.d
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.drift.dart';
enum SortLocalAlbumsBy { id, backupSelection, isIosSharedAlbum, name, assetCount, newestAsset }
class DriftLocalAlbumRepository extends DriftDatabaseRepository {
final Drift _db;
@DriftAccessor()
class LocalAlbumRepository extends DatabaseAccessor<Drift> with $LocalAlbumRepositoryMixin {
LocalAlbumRepository(super.attachedDatabase);
const DriftLocalAlbumRepository(this._db) : super(_db);
Drift get _db => attachedDatabase;
Future<List<LocalAlbum>> getAll({Set<SortLocalAlbumsBy> sortBy = const {}}) {
final assetCount = _db.localAlbumAssetEntity.assetId.count();
@ -57,7 +59,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
return query.map((row) => row.toDto()).get();
}
Future<void> delete(String albumId) => transaction(() async {
Future<void> deleteAlbum(String albumId) => transaction(() async {
// Remove all assets that are only in this particular album
// We cannot remove all assets in the album because they might be in other albums in iOS
// That is not the case on Android since asset <-> album has one:one mapping

View file

@ -10,6 +10,7 @@ import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.drift.dart';
class RemovalCandidatesResult {
final List<LocalAsset> assets;
@ -18,10 +19,12 @@ class RemovalCandidatesResult {
const RemovalCandidatesResult({required this.assets, required this.totalBytes});
}
class DriftLocalAssetRepository extends DriftDatabaseRepository {
final Drift _db;
@DriftAccessor()
class LocalAssetRepository extends DatabaseAccessor<Drift> with $LocalAssetRepositoryMixin {
const DriftLocalAssetRepository(this._db) : super(_db);
LocalAssetRepository(super.attachedDatabase);
Drift get _db => attachedDatabase;
SingleOrNullSelectable<LocalAsset?> _assetSelectable(String id) {
final query =
@ -72,7 +75,7 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
});
}
Future<void> delete(List<String> ids) {
Future<void> deleteAssets(List<String> ids) {
if (ids.isEmpty) {
return Future.value();
}

View file

@ -5,13 +5,16 @@ import 'package:immich_mobile/domain/services/map.service.dart';
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/map.repository.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
class DriftMapRepository extends DriftDatabaseRepository {
final Drift _db;
@DriftAccessor()
class MapRepository extends DatabaseAccessor<Drift> with $MapRepositoryMixin {
const DriftMapRepository(super._db) : _db = _db;
MapRepository(super.attachedDatabase);
Drift get _db => attachedDatabase;
MapQuery remote(List<String> ownerIds, TimelineMapOptions options) => _mapQueryBuilder(
assetFilter: (row) {

View file

@ -4,10 +4,13 @@ import 'package:immich_mobile/domain/models/memory.model.dart';
import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/memory.repository.drift.dart';
class DriftMemoryRepository extends DriftDatabaseRepository {
final Drift _db;
const DriftMemoryRepository(this._db) : super(_db);
@DriftAccessor()
class MemoryRepository extends DatabaseAccessor<Drift> with $MemoryRepositoryMixin {
MemoryRepository(super.attachedDatabase);
Drift get _db => attachedDatabase;
Future<List<DriftMemory>> getAll(String ownerId) async {
final now = DateTime.now();

View file

@ -2,10 +2,13 @@ import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/models/ocr.model.dart';
import 'package:immich_mobile/infrastructure/entities/asset_ocr.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/ocr.repository.drift.dart';
class OcrRepository extends DriftDatabaseRepository {
final Drift _db;
const OcrRepository(this._db) : super(_db);
@DriftAccessor()
class OcrRepository extends DatabaseAccessor<Drift> with $OcrRepositoryMixin {
OcrRepository(super.attachedDatabase);
Drift get _db => attachedDatabase;
Future<List<Ocr>> get(String assetId) async {
final query = _db.select(_db.assetOcrEntity)

View file

@ -4,10 +4,13 @@ import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart';
import 'package:immich_mobile/infrastructure/mapper.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/partner.repository.drift.dart';
class PartnerRepository {
final Drift _db;
const PartnerRepository(this._db);
@DriftAccessor()
class PartnerRepository extends DatabaseAccessor<Drift> with $PartnerRepositoryMixin {
PartnerRepository(super.attachedDatabase);
Drift get _db => attachedDatabase;
Future<Partner> get({required String sharedById, required String sharedWithId}) =>
(_db.select(_db.partnerEntity).join([
@ -46,11 +49,11 @@ class PartnerRepository {
),
);
Future<void> update({required String sharedById, required String sharedWithId, required bool inTimeline}) =>
Future<void> updatePartner({required String sharedById, required String sharedWithId, required bool inTimeline}) =>
(_db.partnerEntity.update()..where((t) => t.sharedById.equals(sharedById) & t.sharedWithId.equals(sharedWithId)))
.write(PartnerEntityCompanion(inTimeline: Value(inTimeline)));
Future<void> delete({required String sharedById, required String sharedWithId}) =>
Future<void> deletePartner({required String sharedById, required String sharedWithId}) =>
(_db.partnerEntity.delete()..where((t) => t.sharedById.equals(sharedById) & t.sharedWithId.equals(sharedWithId)))
.go();

View file

@ -3,10 +3,13 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/person.model.dart';
import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/people.repository.drift.dart';
class DriftPeopleRepository extends DriftDatabaseRepository {
final Drift _db;
const DriftPeopleRepository(this._db) : super(_db);
@DriftAccessor()
class PeopleRepository extends DatabaseAccessor<Drift> with $PeopleRepositoryMixin {
PeopleRepository(super.attachedDatabase);
Drift get _db => attachedDatabase;
Future<DriftPerson?> get(String personId) async {
final query = _db.select(_db.personEntity)..where((row) => row.id.equals(personId));

View file

@ -12,12 +12,15 @@ 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/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.drift.dart';
enum SortRemoteAlbumsBy { id, updatedAt }
class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
final Drift _db;
const DriftRemoteAlbumRepository(this._db) : super(_db);
@DriftAccessor()
class RemoteAlbumRepository extends DatabaseAccessor<Drift> with $RemoteAlbumRepositoryMixin {
RemoteAlbumRepository(super.attachedDatabase);
Drift get _db => attachedDatabase;
Future<List<RemoteAlbum>> getAll({Set<SortRemoteAlbumsBy> sortBy = const {SortRemoteAlbumsBy.updatedAt}}) {
// Count non-trashed assets via the joined asset table. Filtering trashed assets in the
@ -191,7 +194,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
});
}
Future<void> update(RemoteAlbum album) async {
Future<void> updateAlbum(RemoteAlbum album) async {
await _db.remoteAlbumEntity.update().replace(
RemoteAlbumEntityCompanion(
id: Value(album.id),

View file

@ -10,13 +10,16 @@ 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/stack.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.drift.dart';
import 'package:immich_mobile/utils/option.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
class RemoteAssetRepository extends DriftDatabaseRepository {
final Drift _db;
@DriftAccessor()
class RemoteAssetRepository extends DatabaseAccessor<Drift> with $RemoteAssetRepositoryMixin {
const RemoteAssetRepository(this._db) : super(_db);
RemoteAssetRepository(super.attachedDatabase);
Drift get _db => attachedDatabase;
/// For testing purposes
Future<List<RemoteAsset>> getSome(String userId) {
@ -163,7 +166,7 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
);
}
Future<void> delete(List<String> ids) {
Future<void> deleteAssets(List<String> ids) {
return _db.batch((batch) {
for (final id in ids) {
batch.deleteWhere(_db.remoteAssetEntity, (row) => row.id.equals(id));
@ -244,7 +247,7 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
return query.map((row) => row.toDto()!).get();
}
Future<void> update(
Future<void> updateAssets(
List<String> remoteIds, {
Option<bool> isFavorite = const .none(),
Option<AssetVisibility> visibility = const .none(),

View file

@ -1,14 +1,17 @@
import 'package:drift/drift.dart';
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/remote_exif.repository.drift.dart';
import 'package:immich_mobile/utils/option.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
class RemoteExifRepository extends DriftDatabaseRepository {
final Drift _db;
@DriftAccessor()
class RemoteExifRepository extends DatabaseAccessor<Drift> with $RemoteExifRepositoryMixin {
RemoteExifRepository(super.attachedDatabase);
const RemoteExifRepository(this._db) : super(_db);
Drift get _db => attachedDatabase;
Future<void> update(
Future<void> updateExif(
List<String> ids, {
Option<DateTime> dateTimeOriginal = const .none(),
Option<String> timeZone = const .none(),

View file

@ -2,10 +2,13 @@ import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/models/stack.model.dart';
import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/stack.repository.drift.dart';
class DriftStackRepository extends DriftDatabaseRepository {
final Drift _db;
const DriftStackRepository(this._db) : super(_db);
@DriftAccessor()
class StackRepository extends DatabaseAccessor<Drift> with $StackRepositoryMixin {
StackRepository(super.attachedDatabase);
Drift get _db => attachedDatabase;
Future<List<Stack>> getAll(String userId) {
final query = _db.stackEntity.select()..where((e) => e.ownerId.equals(userId));

View file

@ -3,13 +3,16 @@ import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/store.repository.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
class DriftStoreRepository extends DriftDatabaseRepository {
final Drift _db;
@DriftAccessor()
class StoreRepository extends DatabaseAccessor<Drift> with $StoreRepositoryMixin {
final validStoreKeys = StoreKey.values.map((e) => e.id).toSet();
DriftStoreRepository(super.db) : _db = db;
StoreRepository(super.attachedDatabase);
Drift get _db => attachedDatabase;
Future<bool> deleteAll() async {
await _db.storeEntity.deleteAll();
@ -27,7 +30,7 @@ class DriftStoreRepository extends DriftDatabaseRepository {
return query.asyncMap((entity) => _toUpdateEvent(entity)).watch();
}
Future<void> delete<T>(StoreKey<T> key) async {
Future<void> deleteValue<T>(StoreKey<T> key) async {
await _db.storeEntity.deleteWhere((entity) => entity.id.equals(key.id));
return;
}
@ -64,7 +67,7 @@ class DriftStoreRepository extends DriftDatabaseRepository {
const (bool) => entity.intValue == 1,
const (DateTime) => entity.intValue == null ? null : DateTime.fromMillisecondsSinceEpoch(entity.intValue!),
const (UserDto) =>
entity.stringValue == null ? null : await DriftAuthUserRepository(_db).get(entity.stringValue!),
entity.stringValue == null ? null : await AuthUserRepository(_db).get(entity.stringValue!),
_ => null,
}
as T?;
@ -75,7 +78,7 @@ class DriftStoreRepository extends DriftDatabaseRepository {
const (String) => (null, value as String),
const (bool) => ((value as bool) ? 1 : 0, null),
const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null),
const (UserDto) => (null, (await DriftAuthUserRepository(_db).upsert(value as UserDto)).id),
const (UserDto) => (null, (await AuthUserRepository(_db).upsert(value as UserDto)).id),
_ => throw UnsupportedError("Unsupported primitive type: ${key.type} for key: ${key.name}"),
};
return StoreEntityCompanion(id: Value(key.id), intValue: Value(intValue), stringValue: Value(strValue));

View file

@ -1,9 +1,13 @@
import 'package:drift/drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/sync_migration.repository.drift.dart';
class SyncMigrationRepository extends DriftDatabaseRepository {
final Drift _db;
@DriftAccessor()
class SyncMigrationRepository extends DatabaseAccessor<Drift> with $SyncMigrationRepositoryMixin {
const SyncMigrationRepository(super.db) : _db = db;
SyncMigrationRepository(super.attachedDatabase);
Drift get _db => attachedDatabase;
Future<void> v20260128CopyExifWidthHeightToAsset() async {
await _db.customStatement('''

View file

@ -29,16 +29,19 @@ import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.drift.dart';
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
import 'package:logging/logging.dart';
import 'package:openapi/api.dart' as api show AlbumUserRole, AssetEditAction, AssetVisibility, UserMetadataKey;
import 'package:openapi/api.dart' hide AlbumUserRole, AssetEditAction, AssetVisibility, UserMetadataKey;
class SyncStreamRepository extends DriftDatabaseRepository {
final Logger _logger = Logger('DriftSyncStreamRepository');
final Drift _db;
@DriftAccessor()
class SyncStreamRepository extends DatabaseAccessor<Drift> with $SyncStreamRepositoryMixin {
final Logger _logger = Logger('SyncStreamRepository');
SyncStreamRepository(super.db) : _db = db;
SyncStreamRepository(super.attachedDatabase);
Drift get _db => attachedDatabase;
Future<void> reset() async {
_logger.fine("SyncResetV1 received. Resetting remote entities");

View file

@ -12,6 +12,7 @@ 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/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/map.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.drift.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:stream_transform/stream_transform.dart';
@ -33,10 +34,12 @@ class TimelineMapOptions {
});
}
class DriftTimelineRepository extends DriftDatabaseRepository {
final Drift _db;
@DriftAccessor()
class TimelineRepository extends DatabaseAccessor<Drift> with $TimelineRepositoryMixin {
const DriftTimelineRepository(super._db) : _db = _db;
TimelineRepository(super.attachedDatabase);
Drift get _db => attachedDatabase;
Stream<List<String>> watchTimelineUserIds(String userId) {
final query = _db.partnerEntity.selectOnly()

View file

@ -8,13 +8,16 @@ import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.d
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.drift.dart';
typedef TrashedAsset = ({String albumId, LocalAsset asset});
class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
final Drift _db;
@DriftAccessor()
class TrashedLocalAssetRepository extends DatabaseAccessor<Drift> with $TrashedLocalAssetRepositoryMixin {
const DriftTrashedLocalAssetRepository(this._db) : super(_db);
TrashedLocalAssetRepository(super.attachedDatabase);
Drift get _db => attachedDatabase;
/// Matches remote_asset_entity rows owned by the current user. The asset is unique over (owner, checksum),
/// so partners can have a duplicate checksum

View file

@ -4,18 +4,23 @@ import 'package:immich_mobile/domain/models/user_metadata.model.dart';
import 'package:immich_mobile/infrastructure/entities/auth_user.entity.drift.dart';
import 'package:immich_mobile/infrastructure/mapper.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/user.repository.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/user_metadata.repository.dart';
class UserRepository {
final Drift _db;
const UserRepository(this._db);
@DriftAccessor()
class UserRepository extends DatabaseAccessor<Drift> with $UserRepositoryMixin {
UserRepository(super.attachedDatabase);
Drift get _db => attachedDatabase;
Stream<Iterable<User>> getAll() => _db.select(_db.userEntity).map(mapToUser).watch();
}
class DriftAuthUserRepository extends DriftDatabaseRepository {
final Drift _db;
const DriftAuthUserRepository(super.db) : _db = db;
@DriftAccessor()
class AuthUserRepository extends DatabaseAccessor<Drift> with $AuthUserRepositoryMixin {
AuthUserRepository(super.attachedDatabase);
Drift get _db => attachedDatabase;
Future<UserDto?> get(String id) async {
final user = await _db.managers.authUserEntity.filter((user) => user.id.equals(id)).getSingleOrNull();

View file

@ -3,9 +3,11 @@ import 'package:immich_mobile/domain/models/user_metadata.model.dart';
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
class DriftUserMetadataRepository extends DriftDatabaseRepository {
final Drift _db;
const DriftUserMetadataRepository(this._db) : super(_db);
@DriftAccessor()
class UserMetadataRepository extends DatabaseAccessor<Drift> {
UserMetadataRepository(super.attachedDatabase);
Drift get _db => attachedDatabase;
Future<List<UserMetadata>> getUserMetadata(String userId) {
final query = _db.userMetadataEntity.select()..where((e) => e.userId.equals(userId));

View file

@ -6,7 +6,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/utils/bytes_units.dart';
import 'package:path/path.dart' as path;
@ -530,7 +530,7 @@ class _CurrentUploadThumbnail extends ConsumerWidget {
Future<LocalAsset?> _getAsset(WidgetRef ref) async {
try {
return await ref.read(localAssetRepository).getById(taskId);
return await ref.read(driftProvider).localAssetRepository.getById(taskId);
} catch (e) {
return null;
}
@ -667,7 +667,7 @@ class FileDetailDialog extends ConsumerWidget {
Future<LocalAsset?> _getAssetDetails(WidgetRef ref, String localAssetId) async {
try {
return await ref.read(localAssetRepository).getById(localAssetId);
return await ref.read(driftProvider).localAssetRepository.getById(localAssetId);
} catch (e) {
return null;
}

View file

@ -12,6 +12,7 @@ import 'package:immich_mobile/presentation/actions/action.dart';
import 'package:immich_mobile/presentation/pages/edit/editor.provider.dart';
import 'package:immich_mobile/presentation/widgets/images/image_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/server_info.provider.dart';
import 'package:immich_mobile/providers/websocket.provider.dart';
import 'package:immich_mobile/routing/router.dart';
@ -50,7 +51,7 @@ class EditAssetAction extends AssetActionBuilder {
try {
// TODO(shenlong): Move all EXIF and Apply Edits logic onto the Route
final repository = ref.read(remoteAssetRepositoryProvider);
final repository = ref.read(driftProvider).remoteAssetRepository;
final (edits, exif) = await (repository.getAssetEdits(asset.id), repository.getExif(asset.id)).wait;
if (exif == null || !context.mounted) {
return;

View file

@ -7,6 +7,7 @@ import 'package:immich_mobile/generated/translations.g.dart';
import 'package:immich_mobile/presentation/actions/action.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset_viewer/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/toast.provider.dart';
import 'package:immich_mobile/utils/error_handler.dart';
import 'package:immich_mobile/utils/timezone.dart';
@ -46,7 +47,7 @@ class EditDateTimeAction extends AssetActionBuilder {
}
final (:assetIds, :origin) = state;
final remoteAssetRepository = ref.read(remoteAssetRepositoryProvider);
final remoteAssetRepository = ref.read(driftProvider).remoteAssetRepository;
final clearSelection = ref.read(clearSelectionProvider(source));
try {

View file

@ -7,6 +7,7 @@ import 'package:immich_mobile/generated/translations.g.dart';
import 'package:immich_mobile/presentation/actions/action.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset_viewer/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/toast.provider.dart';
import 'package:immich_mobile/utils/error_handler.dart';
import 'package:immich_mobile/widgets/common/location_picker.dart';
@ -46,7 +47,7 @@ class EditLocationAction extends AssetActionBuilder {
}
final (:assetIds, :origin) = state;
final remoteAssetRepository = ref.read(remoteAssetRepositoryProvider);
final remoteAssetRepository = ref.read(driftProvider).remoteAssetRepository;
final clearSelection = ref.read(clearSelectionProvider(source));
try {

View file

@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/routing/router.dart';
@ -60,7 +59,7 @@ class LocalMediaSummaryPage extends StatelessWidget {
body: Consumer(
builder: (ctx, ref, __) {
final db = ref.watch(driftProvider);
final albumsFuture = ref.watch(localAlbumRepository).getAll();
final albumsFuture = db.localAlbumRepository.getAll();
return CustomScrollView(
slivers: [
@ -140,7 +139,7 @@ class RemoteMediaSummaryPage extends StatelessWidget {
body: Consumer(
builder: (ctx, ref, __) {
final db = ref.watch(driftProvider);
final albumsFuture = ref.watch(remoteAlbumRepository).getAll();
final albumsFuture = db.remoteAlbumRepository.getAll();
return CustomScrollView(
slivers: [

View file

@ -4,10 +4,10 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/domain/services/local_album.service.dart';
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
final backupAlbumProvider = StateNotifierProvider<BackupAlbumNotifier, List<LocalAlbum>>(
(ref) => BackupAlbumNotifier(ref.watch(localAlbumServiceProvider)),
(ref) => BackupAlbumNotifier(LocalAlbumService(ref.watch(driftProvider).localAlbumRepository)),
);
class BackupAlbumNotifier extends StateNotifier<List<LocalAlbum>> {

View file

@ -6,7 +6,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/constants.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/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/services/background_upload.service.dart';
import 'package:immich_mobile/services/foreground_upload.service.dart';
@ -367,5 +367,8 @@ final driftCandidateBackupAlbumInfoProvider = FutureProvider.autoDispose.family<
ref,
assetId,
) {
return ref.read(localAssetRepository).getSourceAlbums(assetId, backupSelection: BackupSelection.selected);
return ref
.read(driftProvider)
.localAssetRepository
.getSourceAlbums(assetId, backupSelection: BackupSelection.selected);
});

View file

@ -5,41 +5,27 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/services/local_album.service.dart';
import 'package:immich_mobile/domain/services/remote_album.service.dart';
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/remote_album.provider.dart';
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
import 'package:immich_mobile/services/foreground_upload.service.dart';
final localAlbumRepository = Provider<DriftLocalAlbumRepository>(
(ref) => DriftLocalAlbumRepository(ref.watch(driftProvider)),
);
final localAlbumServiceProvider = Provider<LocalAlbumService>(
(ref) => LocalAlbumService(ref.watch(localAlbumRepository)),
);
final localAlbumProvider = FutureProvider<List<LocalAlbum>>(
(ref) => LocalAlbumService(ref.watch(localAlbumRepository))
(ref) => LocalAlbumService(ref.watch(driftProvider).localAlbumRepository)
.getAll(sortBy: {SortLocalAlbumsBy.newestAsset})
.then((albums) => albums.where((album) => album.assetCount > 0).toList()),
);
final localAlbumThumbnailProvider = FutureProvider.family<LocalAsset?, String>(
(ref, albumId) => LocalAlbumService(ref.watch(localAlbumRepository)).getThumbnail(albumId),
);
final remoteAlbumRepository = Provider<DriftRemoteAlbumRepository>(
(ref) => DriftRemoteAlbumRepository(ref.watch(driftProvider)),
(ref, albumId) => LocalAlbumService(ref.watch(driftProvider).localAlbumRepository).getThumbnail(albumId),
);
final remoteAlbumServiceProvider = Provider<RemoteAlbumService>(
(ref) => RemoteAlbumService(
ref.watch(remoteAlbumRepository),
ref.watch(driftProvider).remoteAlbumRepository,
ref.watch(driftAlbumApiRepositoryProvider),
ref.watch(foregroundUploadServiceProvider),
),
dependencies: [remoteAlbumRepository],
);
final remoteAlbumProvider = NotifierProvider<RemoteAlbumNotifier, RemoteAlbumState>(

View file

@ -1,38 +1,21 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/services/asset.service.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';
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.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';
import 'package:immich_mobile/repositories/asset_media.repository.dart';
final localAssetRepository = Provider<DriftLocalAssetRepository>(
(ref) => DriftLocalAssetRepository(ref.watch(driftProvider)),
);
final remoteAssetRepositoryProvider = Provider<RemoteAssetRepository>(
(ref) => RemoteAssetRepository(ref.watch(driftProvider)),
);
final remoteExifRepositoryProvider = Provider((ref) => RemoteExifRepository(ref.watch(driftProvider)));
final trashedLocalAssetRepository = Provider<DriftTrashedLocalAssetRepository>(
(ref) => DriftTrashedLocalAssetRepository(ref.watch(driftProvider)),
);
final assetServiceProvider = Provider(
(ref) => AssetService(
remoteRepository: ref.watch(remoteAssetRepositoryProvider),
exifRepository: ref.watch(remoteExifRepositoryProvider),
localRepository: ref.watch(localAssetRepository),
final assetServiceProvider = Provider((ref) {
final db = ref.watch(driftProvider);
return AssetService(
remoteRepository: db.remoteAssetRepository,
exifRepository: db.remoteExifRepository,
localRepository: db.localAssetRepository,
apiRepository: ref.watch(assetApiRepositoryProvider),
mediaRepository: ref.watch(assetMediaRepositoryProvider),
trashedLocalRepository: ref.watch(trashedLocalAssetRepository),
),
);
trashedLocalRepository: db.trashedLocalAssetRepository,
);
});
final placesProvider = FutureProvider<List<(String, String)>>((ref) {
final assetService = ref.watch(assetServiceProvider);

View file

@ -1,13 +1,10 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/services/map.service.dart';
import 'package:immich_mobile/infrastructure/repositories/map.repository.dart';
import 'package:immich_mobile/presentation/widgets/map/map.state.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
final mapRepositoryProvider = Provider<DriftMapRepository>((ref) => DriftMapRepository(ref.watch(driftProvider)));
final mapServiceProvider = Provider<MapService>(
(ref) {
final user = ref.watch(currentUserProvider);
@ -19,12 +16,11 @@ final mapServiceProvider = Provider<MapService>(
? ref.watch(timelineUsersProvider).valueOrNull ?? [user.id]
: [user.id];
final mapService = ref.watch(mapFactoryProvider).remote(users, ref.watch(mapStateProvider).toOptions());
final mapFactory = MapFactory(mapRepository: ref.watch(driftProvider).mapRepository);
final mapService = mapFactory.remote(users, ref.watch(mapStateProvider).toOptions());
return mapService;
},
// Empty dependencies to inform the framework that this provider
// might be used in a ProviderScope
dependencies: const [],
);
final mapFactoryProvider = Provider<MapFactory>((ref) => MapFactory(mapRepository: ref.watch(mapRepositoryProvider)));

View file

@ -3,18 +3,9 @@ import 'dart:async';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/memory.model.dart';
import 'package:immich_mobile/domain/services/memory.service.dart';
import 'package:immich_mobile/infrastructure/repositories/memory.repository.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
final driftMemoryRepositoryProvider = Provider<DriftMemoryRepository>(
(ref) => DriftMemoryRepository(ref.watch(driftProvider)),
);
final driftMemoryServiceProvider = Provider<DriftMemoryService>(
(ref) => DriftMemoryService(ref.watch(driftMemoryRepositoryProvider)),
);
final driftMemoryFutureProvider = FutureProvider.autoDispose<List<DriftMemory>>((ref) {
final (userId, enabled) = ref.watch(currentUserProvider.select((user) => (user?.id, user?.memoryEnabled ?? true)));
if (userId == null || !enabled) {
@ -26,6 +17,6 @@ final driftMemoryFutureProvider = FutureProvider.autoDispose<List<DriftMemory>>(
final timer = Timer(nextMidnight.difference(now) + const Duration(seconds: 5), ref.invalidateSelf);
ref.onDispose(timer.cancel);
final service = ref.watch(driftMemoryServiceProvider);
final service = DriftMemoryService(ref.watch(driftProvider).memoryRepository);
return service.getMemoryLane(userId);
});

View file

@ -1,14 +1,9 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/ocr.model.dart';
import 'package:immich_mobile/domain/services/ocr.service.dart';
import 'package:immich_mobile/infrastructure/repositories/ocr.repository.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
final ocrRepositoryProvider = Provider<OcrRepository>((ref) => OcrRepository(ref.watch(driftProvider)));
final ocrServiceProvider = Provider<OcrService>((ref) => OcrService(ref.watch(ocrRepositoryProvider)));
final ocrAssetProvider = FutureProvider.autoDispose.family<List<Ocr>?, String>((ref, assetId) async {
final service = ref.watch(ocrServiceProvider);
final service = OcrService(ref.watch(driftProvider).ocrRepository);
return service.get(assetId);
});

View file

@ -1,17 +1,12 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/person.model.dart';
import 'package:immich_mobile/domain/services/people.service.dart';
import 'package:immich_mobile/infrastructure/repositories/people.repository.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/user_metadata.provider.dart';
import 'package:immich_mobile/repositories/person_api.repository.dart';
final driftPeopleRepositoryProvider = Provider<DriftPeopleRepository>(
(ref) => DriftPeopleRepository(ref.watch(driftProvider)),
);
final driftPeopleServiceProvider = Provider<DriftPeopleService>(
(ref) => DriftPeopleService(ref.watch(driftPeopleRepositoryProvider), ref.watch(personApiRepositoryProvider)),
(ref) => DriftPeopleService(ref.watch(driftProvider).peopleRepository, ref.watch(personApiRepositoryProvider)),
);
final driftPeopleAssetProvider = FutureProvider.family<List<DriftPerson>, String>((ref, assetId) async {

View file

@ -1,5 +0,0 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/infrastructure/repositories/stack.repository.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
final driftStackProvider = Provider<DriftStackRepository>((ref) => DriftStackRepository(ref.watch(driftProvider)));

View file

@ -3,55 +3,50 @@ import 'package:immich_mobile/domain/services/hash.service.dart';
import 'package:immich_mobile/domain/services/local_sync.service.dart';
import 'package:immich_mobile/domain/services/sync_stream.service.dart';
import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/sync_migration.repository.dart';
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/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
import 'package:immich_mobile/repositories/asset_media.repository.dart';
import 'package:immich_mobile/repositories/permission.repository.dart';
final syncMigrationRepositoryProvider = Provider((ref) => SyncMigrationRepository(ref.watch(driftProvider)));
final syncStreamServiceProvider = Provider(
(ref) => SyncStreamService(
final syncStreamServiceProvider = Provider((ref) {
final db = ref.watch(driftProvider);
return SyncStreamService(
syncApiRepository: ref.watch(syncApiRepositoryProvider),
syncStreamRepository: ref.watch(syncStreamRepositoryProvider),
localAssetRepository: ref.watch(localAssetRepository),
trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository),
syncStreamRepository: db.syncStreamRepository,
localAssetRepository: db.localAssetRepository,
trashedLocalAssetRepository: db.trashedLocalAssetRepository,
assetMediaRepository: ref.watch(assetMediaRepositoryProvider),
permissionRepository: ref.watch(permissionRepositoryProvider),
syncMigrationRepository: ref.watch(syncMigrationRepositoryProvider),
syncMigrationRepository: db.syncMigrationRepository,
api: ref.watch(apiServiceProvider),
cancellation: ref.watch(cancellationProvider),
),
);
);
});
final syncApiRepositoryProvider = Provider((ref) => SyncApiRepository(ref.watch(apiServiceProvider)));
final syncStreamRepositoryProvider = Provider((ref) => SyncStreamRepository(ref.watch(driftProvider)));
final localSyncServiceProvider = Provider(
(ref) => LocalSyncService(
localAlbumRepository: ref.watch(localAlbumRepository),
localAssetRepository: ref.watch(localAssetRepository),
trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository),
final localSyncServiceProvider = Provider((ref) {
final db = ref.watch(driftProvider);
return LocalSyncService(
localAlbumRepository: db.localAlbumRepository,
localAssetRepository: db.localAssetRepository,
trashedLocalAssetRepository: db.trashedLocalAssetRepository,
assetMediaRepository: ref.watch(assetMediaRepositoryProvider),
permissionRepository: ref.watch(permissionRepositoryProvider),
nativeSyncApi: ref.watch(nativeSyncApiProvider),
cancellation: ref.watch(cancellationProvider),
),
);
);
});
final hashServiceProvider = Provider(
(ref) => HashService(
localAlbumRepository: ref.watch(localAlbumRepository),
localAssetRepository: ref.watch(localAssetRepository),
final hashServiceProvider = Provider((ref) {
final db = ref.watch(driftProvider);
return HashService(
localAlbumRepository: db.localAlbumRepository,
localAssetRepository: db.localAssetRepository,
nativeSyncApi: ref.watch(nativeSyncApiProvider),
trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository),
trashedLocalAssetRepository: db.trashedLocalAssetRepository,
cancellation: ref.watch(cancellationProvider),
),
);
);
});

View file

@ -1,16 +1,11 @@
import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
final timelineRepositoryProvider = Provider<DriftTimelineRepository>(
(ref) => DriftTimelineRepository(ref.watch(driftProvider)),
);
final timelineArgsProvider = Provider.autoDispose<TimelineArgs>(
(ref) => throw UnimplementedError('Will be overridden through a ProviderScope.'),
);
@ -29,7 +24,7 @@ final timelineServiceProvider = Provider<TimelineService>(
final timelineFactoryProvider = Provider<TimelineFactory>(
(ref) => TimelineFactory(
timelineRepository: ref.watch(timelineRepositoryProvider),
timelineRepository: ref.watch(driftProvider).timelineRepository,
settingsRepository: ref.watch(settingsProvider),
),
);
@ -43,7 +38,7 @@ final timelineUsersProvider = StreamProvider<List<String>>((ref) {
// Drift re-emits a fresh but content-identical list on unrelated table updates,
// which would dispose and rebuild the timeline service mid-load
return ref
.watch(timelineRepositoryProvider)
.watch(driftProvider).timelineRepository
.watchTimelineUserIds(currentUserId)
.distinct(const ListEquality<String>().equals);
});

View file

@ -1,11 +1,11 @@
import 'package:async/async.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
typedef TrashedAssetsCount = ({int total, int hashed});
final trashedAssetsCountProvider = StreamProvider<TrashedAssetsCount>((ref) {
final repo = ref.watch(trashedLocalAssetRepository);
final repo = ref.watch(driftProvider).trashedLocalAssetRepository;
final total$ = repo.watchCount();
final hashed$ = repo.watchHashedCount();
return StreamZip<int>([total$, hashed$]).map((values) => (total: values[0], hashed: values[1]));

View file

@ -1,16 +1,12 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/services/partner.service.dart';
import 'package:immich_mobile/domain/services/user.service.dart';
import 'package:immich_mobile/infrastructure/repositories/partner.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/store.provider.dart';
import 'package:immich_mobile/repositories/partner_api.repository.dart';
final userRepositoryProvider = Provider((ref) => UserRepository(ref.watch(driftProvider)));
final userApiRepositoryProvider = Provider((ref) => UserApiRepository(ref.watch(apiServiceProvider).usersApi));
final userServiceProvider = Provider(
@ -20,12 +16,7 @@ final userServiceProvider = Provider(
),
);
final partnerRepositoryProvider = Provider<PartnerRepository>((ref) => PartnerRepository(ref.watch(driftProvider)));
final partnerServiceProvider = Provider<PartnerService>(
(ref) => PartnerService(
ref.watch(userRepositoryProvider),
ref.watch(partnerRepositoryProvider),
ref.watch(partnerApiRepositoryProvider),
),
);
final partnerServiceProvider = Provider<PartnerService>((ref) {
final db = ref.watch(driftProvider);
return PartnerService(db.userRepository, db.partnerRepository, ref.watch(partnerApiRepositoryProvider));
});

View file

@ -1,16 +1,11 @@
import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
import 'package:immich_mobile/infrastructure/repositories/user_metadata.repository.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
final userMetadataRepository = Provider<DriftUserMetadataRepository>(
(ref) => DriftUserMetadataRepository(ref.watch(driftProvider)),
);
final userMetadataProvider = FutureProvider<List<UserMetadata>>((ref) async {
final repository = ref.watch(userMetadataRepository);
final repository = ref.watch(driftProvider).userMetadataRepository;
final user = ref.watch(currentUserProvider);
if (user == null) {
return [];

View file

@ -2,11 +2,11 @@ import 'dart:async';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/repositories/asset_api.repository.dart';
final actionServiceProvider = Provider<ActionService>(
(ref) => ActionService(ref.watch(assetApiRepositoryProvider), ref.watch(remoteAssetRepositoryProvider)),
(ref) => ActionService(ref.watch(assetApiRepositoryProvider), ref.watch(driftProvider).remoteAssetRepository),
);
class ActionService {

View file

@ -15,7 +15,7 @@ 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/settings.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/storage.repository.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/storage.provider.dart';
import 'package:immich_mobile/repositories/asset_media.repository.dart';
import 'package:immich_mobile/repositories/upload.repository.dart';
@ -26,11 +26,12 @@ import 'package:openapi/api.dart' as api;
import 'package:path/path.dart' as p;
final backgroundUploadServiceProvider = Provider((ref) {
final db = ref.watch(driftProvider);
final service = BackgroundUploadService(
ref.watch(uploadRepositoryProvider),
ref.watch(storageRepositoryProvider),
ref.watch(localAssetRepository),
ref.watch(backupRepositoryProvider),
db.localAssetRepository,
db.backupRepository,
ref.watch(assetMediaRepositoryProvider),
);
@ -112,8 +113,8 @@ class BackgroundUploadService {
final UploadRepository _uploadRepository;
final StorageRepository _storageRepository;
final DriftLocalAssetRepository _localAssetRepository;
final DriftBackupRepository _backupRepository;
final LocalAssetRepository _localAssetRepository;
final BackupRepository _backupRepository;
final AssetMediaRepository _assetMediaRepository;
final Logger _logger = Logger('BackgroundUploadService');

View file

@ -2,17 +2,17 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/extensions/platform_extensions.dart';
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/repositories/asset_media.repository.dart';
final cleanupServiceProvider = Provider<CleanupService>((ref) {
return CleanupService(ref.watch(localAssetRepository), ref.watch(assetMediaRepositoryProvider));
return CleanupService(ref.watch(driftProvider).localAssetRepository, ref.watch(assetMediaRepositoryProvider));
});
class CleanupService {
static final int _deleteBatchSize = CurrentPlatform.isAndroid ? 2000 : 10000;
final DriftLocalAssetRepository _localAssetRepository;
final LocalAssetRepository _localAssetRepository;
final AssetMediaRepository _assetMediaRepository;
const CleanupService(this._localAssetRepository, this._assetMediaRepository);
@ -46,7 +46,7 @@ class CleanupService {
final deletedIds = await _assetMediaRepository.deleteAll(batch);
if (deletedIds.isNotEmpty) {
await _localAssetRepository.delete(deletedIds);
await _localAssetRepository.deleteAssets(deletedIds);
deletedCount += deletedIds.length;
}
}

View file

@ -10,7 +10,7 @@ import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.page.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart' as beta_asset_provider;
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/people.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
@ -21,7 +21,7 @@ final deepLinkServiceProvider = Provider(
ref.watch(timelineFactoryProvider),
ref.watch(beta_asset_provider.assetServiceProvider),
ref.watch(remoteAlbumServiceProvider),
ref.watch(driftMemoryServiceProvider),
DriftMemoryService(ref.watch(driftProvider).memoryRepository),
ref.watch(driftPeopleServiceProvider),
ref.watch(currentUserProvider),
),

View file

@ -15,6 +15,7 @@ import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
import 'package:immich_mobile/platform/connectivity_api.g.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
import 'package:immich_mobile/repositories/asset_media.repository.dart';
@ -39,7 +40,7 @@ final foregroundUploadServiceProvider = Provider((ref) {
return ForegroundUploadService(
ref.watch(uploadRepositoryProvider),
ref.watch(storageRepositoryProvider),
ref.watch(backupRepositoryProvider),
ref.watch(driftProvider).backupRepository,
ref.watch(connectivityApiProvider),
ref.watch(assetMediaRepositoryProvider),
);
@ -61,7 +62,7 @@ class ForegroundUploadService {
final UploadRepository _uploadRepository;
final StorageRepository _storageRepository;
final DriftBackupRepository _backupRepository;
final BackupRepository _backupRepository;
final ConnectivityApi _connectivityApi;
final AssetMediaRepository _assetMediaRepository;
final Logger _logger = Logger('ForegroundUploadService');

View file

@ -4,7 +4,7 @@ import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
import 'package:immich_mobile/models/view_intent/view_intent_payload.extension.dart';
import 'package:immich_mobile/platform/view_intent_api.g.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/timeline.provider.dart';
import 'package:logging/logging.dart';
@ -19,13 +19,13 @@ class ViewIntentResolvedAsset {
final viewIntentAssetResolverProvider = Provider<ViewIntentAssetResolver>(
(ref) => ViewIntentAssetResolver(
localAssetRepository: ref.read(localAssetRepository),
localAssetRepository: ref.read(driftProvider).localAssetRepository,
timelineFactory: ref.read(timelineFactoryProvider),
),
);
class ViewIntentAssetResolver {
final DriftLocalAssetRepository _localAssetRepository;
final LocalAssetRepository _localAssetRepository;
final TimelineFactory _timelineFactory;
static final Logger _logger = Logger('ViewIntentAssetResolver');

View file

@ -49,7 +49,7 @@ abstract final class Bootstrap {
await configureSqliteCache();
final (db, updatePool) = await openSqliteConnectionWithUpdatePool(name: 'immich');
final drift = Drift.sqlite(db, updatePool);
final DriftStoreRepository storeRepo = DriftStoreRepository(drift);
final StoreRepository storeRepo = StoreRepository(drift);
await StoreService.init(storeRepository: storeRepo, listenUpdates: listenStoreUpdates);

View file

@ -3,6 +3,8 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/services/local_album.service.dart';
import 'package:immich_mobile/domain/services/memory.service.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';
@ -12,7 +14,6 @@ import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/infrastructure/album.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';
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';
@ -230,10 +231,11 @@ class _SyncStatsCounts extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final db = ref.watch(driftProvider);
final assetService = ref.watch(assetServiceProvider);
final localAlbumService = ref.watch(localAlbumServiceProvider);
final localAlbumService = LocalAlbumService(db.localAlbumRepository);
final remoteAlbumService = ref.watch(remoteAlbumServiceProvider);
final memoryService = ref.watch(driftMemoryServiceProvider);
final memoryService = DriftMemoryService(db.memoryRepository);
final appSettingsService = ref.watch(appSettingsServiceProvider);
Future<List<dynamic>> loadCounts() async {

View file

@ -23,9 +23,9 @@ import '../../service.mocks.dart';
void main() {
late LocalSyncService sut;
late DriftLocalAlbumRepository mockLocalAlbumRepository;
late DriftLocalAssetRepository mockLocalAssetRepository;
late DriftTrashedLocalAssetRepository mockTrashedLocalAssetRepository;
late LocalAlbumRepository mockLocalAlbumRepository;
late LocalAssetRepository mockLocalAssetRepository;
late TrashedLocalAssetRepository mockTrashedLocalAssetRepository;
late AssetMediaRepository mockAssetMediaRepository;
late MockPermissionRepository mockPermissionRepository;
late MockNativeSyncApi mockNativeSyncApi;
@ -36,7 +36,7 @@ void main() {
debugDefaultTargetPlatformOverride = TargetPlatform.android;
db = Drift(drift.DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
await StoreService.init(storeRepository: DriftStoreRepository(db));
await StoreService.init(storeRepository: StoreRepository(db));
});
tearDownAll(() async {

View file

@ -14,12 +14,12 @@ const _kVersion = 2;
void main() {
late StoreService sut;
late DriftStoreRepository mockDriftStoreRepo;
late StoreRepository mockDriftStoreRepo;
late StreamController<List<StoreDto<Object>>> controller;
setUp(() async {
controller = StreamController<List<StoreDto<Object>>>.broadcast();
mockDriftStoreRepo = MockDriftStoreRepository();
mockDriftStoreRepo = MockStoreRepository();
// For generics, we need to provide fallback to each concrete type to avoid runtime errors
registerFallbackValue(StoreKey.accessToken);
registerFallbackValue(StoreKey.version);
@ -124,12 +124,12 @@ void main() {
group('Store Service delete:', () {
setUp(() {
when(() => mockDriftStoreRepo.delete<String>(any<StoreKey<String>>())).thenAnswer((_) async => true);
when(() => mockDriftStoreRepo.deleteValue<String>(any<StoreKey<String>>())).thenAnswer((_) async => true);
});
test('Removes the value from the DB', () async {
await sut.delete(StoreKey.accessToken);
verify(() => mockDriftStoreRepo.delete<String>(StoreKey.accessToken)).called(1);
verify(() => mockDriftStoreRepo.deleteValue<String>(StoreKey.accessToken)).called(1);
});
test('Removes the value from the cache', () async {

View file

@ -1,9 +1,10 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/services/sync_linked_album.service.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/store.provider.dart';
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
import 'package:mocktail/mocktail.dart';
import '../../infrastructure/repository.mock.dart';
import '../../service.mocks.dart';
@ -13,10 +14,12 @@ void main() {
// alone, i.e. the root (main) isolate, where cancellationProvider has no
// override and throws if read. The UI reads this provider here.
ProviderContainer rootContainer() {
final drift = MockDrift();
when(() => drift.localAlbumRepository).thenReturn(MockLocalAlbumRepository());
when(() => drift.remoteAlbumRepository).thenReturn(MockRemoteAlbumRepository());
final container = ProviderContainer(
overrides: [
localAlbumRepository.overrideWithValue(MockLocalAlbumRepository()),
remoteAlbumRepository.overrideWithValue(MockRemoteAlbumRepository()),
driftProvider.overrideWithValue(drift),
driftAlbumApiRepositoryProvider.overrideWithValue(MockDriftAlbumApiRepository()),
storeServiceProvider.overrideWithValue(MockStoreService()),
],

View file

@ -40,8 +40,8 @@ void main() {
late SyncStreamService sut;
late SyncStreamRepository mockSyncStreamRepo;
late SyncApiRepository mockSyncApiRepo;
late DriftLocalAssetRepository mockLocalAssetRepo;
late DriftTrashedLocalAssetRepository mockTrashedLocalAssetRepo;
late LocalAssetRepository mockLocalAssetRepo;
late TrashedLocalAssetRepository mockTrashedLocalAssetRepo;
late AssetMediaRepository mockAssetMediaRepo;
late MockPermissionRepository mockPermissionRepo;
late MockApiService mockApi;
@ -60,7 +60,7 @@ void main() {
registerFallbackValue(const SemVer(major: 2, minor: 5, patch: 0));
db = Drift(drift.DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
await StoreService.init(storeRepository: DriftStoreRepository(db));
await StoreService.init(storeRepository: StoreRepository(db));
});
tearDownAll(() async {

View file

@ -23,7 +23,7 @@ void main() {
group('getAll', () {
test('sorts albums by backupSelection & isIosSharedAlbum', () async {
final localAlbumRepo = mediumFactory.getRepository<DriftLocalAlbumRepository>();
final localAlbumRepo = mediumFactory.getRepository<LocalAlbumRepository>();
await localAlbumRepo.upsert(mediumFactory.localAlbum(id: '1', backupSelection: BackupSelection.none));
await localAlbumRepo.upsert(mediumFactory.localAlbum(id: '2', backupSelection: BackupSelection.excluded));
await localAlbumRepo.upsert(
@ -46,7 +46,7 @@ void main() {
// Android was dropped. The delta reports only the asset's new album, and the
// stale link to its old album made the per-album delete sweep wipe the asset.
test('keeps an asset moved to another album that still holds other assets', () async {
final localAlbumRepo = mediumFactory.getRepository<DriftLocalAlbumRepository>();
final localAlbumRepo = mediumFactory.getRepository<LocalAlbumRepository>();
final moved = _localAsset('moved');
final other = _localAsset('other');
@ -83,7 +83,7 @@ void main() {
});
test('replaces album membership with exactly what the delta reports', () async {
final localAlbumRepo = mediumFactory.getRepository<DriftLocalAlbumRepository>();
final localAlbumRepo = mediumFactory.getRepository<LocalAlbumRepository>();
final moved = _localAsset('moved');
await localAlbumRepo.upsert(mediumFactory.localAlbum(id: 'src'), toUpsert: [moved]);

View file

@ -47,11 +47,11 @@ Future<void> _populateStore(Drift db) async {
void main() {
late Drift db;
late DriftStoreRepository sut;
late StoreRepository sut;
setUp(() async {
db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
sut = DriftStoreRepository(db);
sut = StoreRepository(db);
});
tearDown(() async {
@ -100,7 +100,7 @@ void main() {
test('delete()', () async {
bool? advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting);
expect(advancedTroubleshooting, isFalse);
await sut.delete(StoreKey.advancedTroubleshooting);
await sut.deleteValue(StoreKey.advancedTroubleshooting);
advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting);
expect(advancedTroubleshooting, isNull);
});

View file

@ -41,7 +41,7 @@ void main() {
setUpAll(() async {
final db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
await StoreService.init(storeRepository: DriftStoreRepository(db));
await StoreService.init(storeRepository: StoreRepository(db));
});
setUp(() {

View file

@ -1,7 +1,9 @@
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/db.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/log.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/memory.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/partner.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart';
@ -18,7 +20,11 @@ import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
import 'package:immich_mobile/repositories/upload.repository.dart';
import 'package:mocktail/mocktail.dart';
class MockDriftStoreRepository extends Mock implements DriftStoreRepository {}
class MockDrift extends Mock implements Drift {}
class MockMemoryRepository extends Mock implements MemoryRepository {}
class MockStoreRepository extends Mock implements StoreRepository {}
class MockSettingsRepository extends Mock implements SettingsRepository {}
@ -26,21 +32,19 @@ class MockLogRepository extends Mock implements LogRepository {}
class MockSyncStreamRepository extends Mock implements SyncStreamRepository {}
class MockLocalAlbumRepository extends Mock implements DriftLocalAlbumRepository {}
class MockLocalAlbumRepository extends Mock implements LocalAlbumRepository {}
class MockRemoteAlbumRepository extends Mock implements DriftRemoteAlbumRepository {}
class MockRemoteAlbumRepository extends Mock implements RemoteAlbumRepository {}
class MockLocalAssetRepository extends Mock implements DriftLocalAssetRepository {}
class MockDriftLocalAssetRepository extends Mock implements DriftLocalAssetRepository {}
class MockLocalAssetRepository extends Mock implements LocalAssetRepository {}
class MockRemoteAssetRepository extends Mock implements RemoteAssetRepository {}
class MockTrashedLocalAssetRepository extends Mock implements DriftTrashedLocalAssetRepository {}
class MockTrashedLocalAssetRepository extends Mock implements TrashedLocalAssetRepository {}
class MockStorageRepository extends Mock implements StorageRepository {}
class MockDriftBackupRepository extends Mock implements DriftBackupRepository {}
class MockBackupRepository extends Mock implements BackupRepository {}
class MockUploadRepository extends Mock implements UploadRepository {}

View file

@ -7,11 +7,11 @@ import '../repository_context.dart';
void main() {
late MediumRepositoryContext ctx;
late DriftBackupRepository sut;
late BackupRepository sut;
setUp(() {
ctx = MediumRepositoryContext();
sut = DriftBackupRepository(ctx.db);
sut = BackupRepository(ctx.db);
});
tearDown(() async {

View file

@ -8,11 +8,11 @@ import '../repository_context.dart';
void main() {
late MediumRepositoryContext ctx;
late DriftLocalAssetRepository sut;
late LocalAssetRepository sut;
setUp(() {
ctx = MediumRepositoryContext();
sut = DriftLocalAssetRepository(ctx.db);
sut = LocalAssetRepository(ctx.db);
});
tearDown(() async {

View file

@ -5,11 +5,11 @@ import '../repository_context.dart';
void main() {
late MediumRepositoryContext ctx;
late DriftMemoryRepository sut;
late MemoryRepository sut;
setUp(() {
ctx = MediumRepositoryContext();
sut = DriftMemoryRepository(ctx.db);
sut = MemoryRepository(ctx.db);
});
tearDown(() async {

View file

@ -78,7 +78,7 @@ void main() {
final sharer = await ctx.newUser();
await ctx.newPartner(sharedById: sharer.id, sharedWithId: me.id, inTimeline: false);
await sut.update(sharedById: sharer.id, sharedWithId: me.id, inTimeline: true);
await sut.updatePartner(sharedById: sharer.id, sharedWithId: me.id, inTimeline: true);
final result = await sut.get(sharedById: sharer.id, sharedWithId: me.id);
expect(result.inTimeline, isTrue);
@ -91,7 +91,7 @@ void main() {
final recipient = await ctx.newUser();
await ctx.newPartner(sharedById: me.id, sharedWithId: recipient.id);
await sut.delete(sharedById: me.id, sharedWithId: recipient.id);
await sut.deletePartner(sharedById: me.id, sharedWithId: recipient.id);
final rows = await ctx.db.select(ctx.db.partnerEntity).get();
expect(rows, isEmpty);

View file

@ -5,11 +5,11 @@ import '../repository_context.dart';
void main() {
late MediumRepositoryContext ctx;
late DriftPeopleRepository sut;
late PeopleRepository sut;
setUp(() {
ctx = MediumRepositoryContext();
sut = DriftPeopleRepository(ctx.db);
sut = PeopleRepository(ctx.db);
});
tearDown(() async {

View file

@ -6,11 +6,11 @@ import '../repository_context.dart';
void main() {
late MediumRepositoryContext ctx;
late DriftRemoteAlbumRepository sut;
late RemoteAlbumRepository sut;
setUp(() async {
ctx = MediumRepositoryContext();
sut = DriftRemoteAlbumRepository(ctx.db);
sut = RemoteAlbumRepository(ctx.db);
});
tearDown(() async {

View file

@ -7,7 +7,7 @@ import '../repository_context.dart';
void main() {
late MediumRepositoryContext ctx;
late DriftTimelineRepository sut;
late TimelineRepository sut;
setUpAll(() async {
await initializeDateFormatting();
@ -15,7 +15,7 @@ void main() {
setUp(() {
ctx = MediumRepositoryContext();
sut = DriftTimelineRepository(ctx.db);
sut = TimelineRepository(ctx.db);
});
tearDown(() async {

View file

@ -6,11 +6,11 @@ import '../repository_context.dart';
void main() {
late MediumRepositoryContext ctx;
late DriftTrashedLocalAssetRepository sut;
late TrashedLocalAssetRepository sut;
setUp(() {
ctx = MediumRepositoryContext();
sut = DriftTrashedLocalAssetRepository(ctx.db);
sut = TrashedLocalAssetRepository(ctx.db);
});
tearDown(() async {

View file

@ -32,7 +32,7 @@ void main() {
setUp(() async {
mapState = const MapState(themeMode: ThemeMode.dark);
mapStateNotifier = MockMapStateNotifier(mapState);
await StoreService.init(storeRepository: DriftStoreRepository(db));
await StoreService.init(storeRepository: StoreRepository(db));
overrides = [
mapStateNotifierProvider.overrideWith(() => mapStateNotifier),
localeProvider.overrideWithValue(const Locale("en")),

View file

@ -2,18 +2,20 @@ import 'package:fake_async/fake_async.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/domain/services/memory.service.dart';
import 'package:immich_mobile/domain/services/user.service.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/memory.repository.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:mocktail/mocktail.dart';
class MockDriftMemoryService extends Mock implements DriftMemoryService {}
import '../../infrastructure/repository.mock.dart';
class MockUserService extends Mock implements UserService {}
void main() {
late MockDriftMemoryService memoryService;
late MockMemoryRepository memoryRepository;
late MockUserService userService;
UserDto user({bool memoryEnabled = true}) => UserDto(
@ -24,10 +26,16 @@ void main() {
profileChangedAt: DateTime(2026),
);
Drift mockDrift(MemoryRepository repository) {
final drift = MockDrift();
when(() => drift.memoryRepository).thenReturn(repository);
return drift;
}
ProviderContainer makeContainer() {
final container = ProviderContainer(
overrides: [
driftMemoryServiceProvider.overrideWithValue(memoryService),
driftProvider.overrideWithValue(mockDrift(memoryRepository)),
currentUserProvider.overrideWith((ref) => CurrentUserProvider(userService)),
],
);
@ -36,10 +44,10 @@ void main() {
}
setUp(() {
memoryService = MockDriftMemoryService();
memoryRepository = MockMemoryRepository();
userService = MockUserService();
when(() => memoryService.getMemoryLane('user-1')).thenAnswer((_) async => []);
when(() => memoryRepository.getAll('user-1')).thenAnswer((_) async => []);
when(() => userService.tryGetMyUser()).thenReturn(user());
when(() => userService.watchMyUser()).thenAnswer((_) => const Stream.empty());
});
@ -51,15 +59,15 @@ void main() {
container.listen(driftMemoryFutureProvider, (_, __) {});
async.flushMicrotasks();
verify(() => memoryService.getMemoryLane('user-1')).called(1);
verify(() => memoryRepository.getAll('user-1')).called(1);
async.elapse(const Duration(seconds: 4));
async.flushMicrotasks();
verifyNever(() => memoryService.getMemoryLane('user-1'));
verifyNever(() => memoryRepository.getAll('user-1'));
async.elapse(const Duration(hours: 25));
async.flushMicrotasks();
verify(() => memoryService.getMemoryLane('user-1')).called(greaterThanOrEqualTo(1));
verify(() => memoryRepository.getAll('user-1')).called(greaterThanOrEqualTo(1));
});
});
@ -68,13 +76,13 @@ void main() {
final container = makeContainer();
final subscription = container.listen(driftMemoryFutureProvider, (_, __) {});
async.flushMicrotasks();
verify(() => memoryService.getMemoryLane('user-1')).called(1);
verify(() => memoryRepository.getAll('user-1')).called(1);
subscription.close();
async.elapse(const Duration(hours: 25));
async.flushMicrotasks();
verifyNever(() => memoryService.getMemoryLane('user-1'));
verifyNever(() => memoryRepository.getAll('user-1'));
});
});
@ -89,7 +97,7 @@ void main() {
async.elapse(const Duration(hours: 25));
async.flushMicrotasks();
verifyNever(() => memoryService.getMemoryLane(any()));
verifyNever(() => memoryRepository.getAll(any()));
});
});
});

View file

@ -25,7 +25,7 @@ void main() {
debugDefaultTargetPlatformOverride = TargetPlatform.android;
db = Drift(drift.DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
await StoreService.init(storeRepository: DriftStoreRepository(db));
await StoreService.init(storeRepository: StoreRepository(db));
});
tearDownAll(() async {

View file

@ -36,7 +36,7 @@ void main() {
setUpAll(() async {
WidgetsFlutterBinding.ensureInitialized();
db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
await StoreService.init(storeRepository: DriftStoreRepository(db));
await StoreService.init(storeRepository: StoreRepository(db));
});
tearDownAll(() async {

View file

@ -25,8 +25,8 @@ void main() {
late BackgroundUploadService sut;
late MockUploadRepository mockUploadRepository;
late MockStorageRepository mockStorageRepository;
late MockDriftLocalAssetRepository mockLocalAssetRepository;
late MockDriftBackupRepository mockBackupRepository;
late MockLocalAssetRepository mockLocalAssetRepository;
late MockBackupRepository mockBackupRepository;
late MockAssetMediaRepository mockAssetMediaRepository;
late Drift db;
@ -37,7 +37,7 @@ void main() {
(MethodCall methodCall) async => 'test',
);
db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
await StoreService.init(storeRepository: DriftStoreRepository(db));
await StoreService.init(storeRepository: StoreRepository(db));
await SettingsRepository.ensureInitialized(db);
await Store.put(StoreKey.serverEndpoint, 'http://test-server.com');
@ -47,8 +47,8 @@ void main() {
setUp(() {
mockUploadRepository = MockUploadRepository();
mockStorageRepository = MockStorageRepository();
mockLocalAssetRepository = MockDriftLocalAssetRepository();
mockBackupRepository = MockDriftBackupRepository();
mockLocalAssetRepository = MockLocalAssetRepository();
mockBackupRepository = MockBackupRepository();
mockAssetMediaRepository = MockAssetMediaRepository();
sut = BackgroundUploadService(

View file

@ -9,11 +9,11 @@ import '../repository.mocks.dart';
void main() {
late CleanupService sut;
late MockDriftLocalAssetRepository localAssetRepository;
late MockLocalAssetRepository localAssetRepository;
late MockAssetMediaRepository assetMediaRepository;
setUp(() {
localAssetRepository = MockDriftLocalAssetRepository();
localAssetRepository = MockLocalAssetRepository();
assetMediaRepository = MockAssetMediaRepository();
sut = CleanupService(localAssetRepository, assetMediaRepository);
});
@ -24,7 +24,7 @@ void main() {
expect(result, 0);
verifyNever(() => assetMediaRepository.deleteAll(any()));
verifyNever(() => localAssetRepository.delete(any()));
verifyNever(() => localAssetRepository.deleteAssets(any()));
});
test('deletes in a single batch when under limit', () async {
@ -33,13 +33,13 @@ void main() {
when(() => assetMediaRepository.deleteAll(any())).thenAnswer((invocation) async {
return (invocation.positionalArguments.first as List<String>).toList();
});
when(() => localAssetRepository.delete(any())).thenAnswer((_) async {});
when(() => localAssetRepository.deleteAssets(any())).thenAnswer((_) async {});
final result = await sut.deleteLocalAssets(ids);
expect(result, ids.length);
verify(() => assetMediaRepository.deleteAll(ids)).called(1);
verify(() => localAssetRepository.delete(ids)).called(1);
verify(() => localAssetRepository.deleteAssets(ids)).called(1);
});
test('deletes in platform-specific batches when over limit', () async {
@ -52,7 +52,7 @@ void main() {
capturedBatches.add(batch);
return batch;
});
when(() => localAssetRepository.delete(any())).thenAnswer((_) async {});
when(() => localAssetRepository.deleteAssets(any())).thenAnswer((_) async {});
final result = await sut.deleteLocalAssets(ids);
@ -67,7 +67,7 @@ void main() {
expect(capturedBatches[1].last, 'asset-${batchSize * 2 - 1}');
expect(capturedBatches[2].first, 'asset-${batchSize * 2}');
expect(capturedBatches[2].last, 'asset-${batchSize * 2 + 500}');
verify(() => localAssetRepository.delete(any())).called(3);
verify(() => localAssetRepository.deleteAssets(any())).called(3);
});
});
}

View file

@ -24,7 +24,7 @@ void main() {
late ForegroundUploadService sut;
late MockUploadRepository mockUploadRepository;
late MockStorageRepository mockStorageRepository;
late MockDriftBackupRepository mockBackupRepository;
late MockBackupRepository mockBackupRepository;
late MockConnectivityApi mockConnectivityApi;
late MockAssetMediaRepository mockAssetMediaRepository;
late Drift db;
@ -36,7 +36,7 @@ void main() {
(MethodCall methodCall) async => 'test',
);
db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
await StoreService.init(storeRepository: DriftStoreRepository(db));
await StoreService.init(storeRepository: StoreRepository(db));
await SettingsRepository.ensureInitialized(db);
await Store.put(StoreKey.serverEndpoint, 'http://demo.immich.app');
@ -49,7 +49,7 @@ void main() {
setUp(() {
mockUploadRepository = MockUploadRepository();
mockStorageRepository = MockStorageRepository();
mockBackupRepository = MockDriftBackupRepository();
mockBackupRepository = MockBackupRepository();
mockConnectivityApi = MockConnectivityApi();
mockAssetMediaRepository = MockAssetMediaRepository();

View file

@ -6,7 +6,7 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/platform/view_intent_api.g.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/timeline.provider.dart';
import 'package:immich_mobile/services/view_intent_asset_resolver.service.dart';
import 'package:mocktail/mocktail.dart';
@ -16,13 +16,13 @@ import '../infrastructure/repository.mock.dart';
class MockTimelineFactory extends Mock implements TimelineFactory {}
void main() {
late MockDriftLocalAssetRepository mockLocalAssetRepository;
late MockLocalAssetRepository mockLocalAssetRepository;
late MockTimelineFactory timelineFactory;
late List<TimelineService> createdTimelineServices;
late ProviderContainer container;
setUp(() {
mockLocalAssetRepository = MockDriftLocalAssetRepository();
mockLocalAssetRepository = MockLocalAssetRepository();
timelineFactory = MockTimelineFactory();
createdTimelineServices = [];
@ -33,9 +33,12 @@ void main() {
return timelineService;
});
final drift = MockDrift();
when(() => drift.localAssetRepository).thenReturn(mockLocalAssetRepository);
container = ProviderContainer(
overrides: [
localAssetRepository.overrideWith((ref) => mockLocalAssetRepository),
driftProvider.overrideWithValue(drift),
timelineFactoryProvider.overrideWith((ref) => timelineFactory),
],
);

View file

@ -31,8 +31,8 @@ class MediumFactory {
T getRepository<T>() {
switch (T) {
case const (DriftLocalAlbumRepository):
return DriftLocalAlbumRepository(_db) as T;
case const (LocalAlbumRepository):
return LocalAlbumRepository(_db) as T;
default:
throw Exception('Unknown repository: $T');
}

View file

@ -26,7 +26,7 @@ import 'factories/user_factory.dart';
class RepositoryMocks {
final localAlbum = LocalAlbumRepositoryStub(MockLocalAlbumRepository());
final localAsset = LocalAssetRepositoryStub(MockDriftLocalAssetRepository());
final localAsset = LocalAssetRepositoryStub(MockLocalAssetRepository());
final remoteAsset = RemoteAssetRepositoryStub(MockRemoteAssetRepository());
final remoteExif = RemoteExifRepositoryStub(MockRemoteExifRepository());
final trashedAsset = MockTrashedLocalAssetRepository();
@ -247,8 +247,8 @@ extension type const LocalAlbumRepositoryStub(MockLocalAlbumRepository repo) imp
() => repo.getAssetsToHash(any());
}
extension type const LocalAssetRepositoryStub(MockDriftLocalAssetRepository repo)
implements Stub<MockDriftLocalAssetRepository> {
extension type const LocalAssetRepositoryStub(MockLocalAssetRepository repo)
implements Stub<MockLocalAssetRepository> {
Future<void> Function() get reconcileHashesFromCloudId =>
() => repo.reconcileHashesFromCloudId();
@ -265,7 +265,7 @@ extension type const RemoteAssetRepositoryStub(MockRemoteAssetRepository repo)
() => repo.getAssetEdits(any());
Future<void> Function() get update =>
() => repo.update(
() => repo.updateAssets(
any(),
isFavorite: any(named: 'isFavorite'),
visibility: any(named: 'visibility'),
@ -275,7 +275,7 @@ extension type const RemoteAssetRepositoryStub(MockRemoteAssetRepository repo)
extension type const RemoteExifRepositoryStub(MockRemoteExifRepository repo) implements Stub<MockRemoteExifRepository> {
Future<void> Function() get update =>
() => repo.update(
() => repo.updateExif(
any(),
dateTimeOriginal: any(named: 'dateTimeOriginal'),
timeZone: any(named: 'timeZone'),

View file

@ -17,6 +17,7 @@ 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/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/providers/routes.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
@ -28,6 +29,7 @@ import 'package:immich_mobile/services/server_info.service.dart';
import 'package:immich_ui/immich_ui.dart';
import 'package:mocktail/mocktail.dart';
import '../../infrastructure/repository.mock.dart';
import '../../test_utils.dart';
import '../factories/user_factory.dart';
import '../mocks.dart';
@ -49,6 +51,7 @@ class PresentationContext {
final RepositoryMocks repository;
List<Override> get overrides => [
driftProvider.overrideWithValue(_mockDrift()),
currentUserProvider.overrideWith((ref) => CurrentUserProvider(service.user.service)),
assetServiceProvider.overrideWithValue(service.asset.service),
cleanupServiceProvider.overrideWithValue(service.cleanup.service),
@ -57,10 +60,15 @@ class PresentationContext {
gCastServiceProvider.overrideWithValue(service.cast),
serverInfoServiceProvider.overrideWithValue(service.serverInfo),
inLockedViewProvider.overrideWithValue(false),
remoteAssetRepositoryProvider.overrideWithValue(repository.remoteAsset.repo),
assetMediaRepositoryProvider.overrideWithValue(repository.assetMedia.api),
];
Drift _mockDrift() {
final drift = MockDrift();
when(() => drift.remoteAssetRepository).thenReturn(repository.remoteAsset.repo);
return drift;
}
List<Override> selected(Set<BaseAsset> assets) => [
multiSelectProvider.overrideWith(
() => MultiSelectNotifier(MultiSelectState(selectedAssets: assets, lockedSelectionAssets: const {})),
@ -71,7 +79,7 @@ class PresentationContext {
TestUtils.init();
if (_db == null) {
final db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
await StoreService.init(storeRepository: DriftStoreRepository(db), listenUpdates: false);
await StoreService.init(storeRepository: StoreRepository(db), listenUpdates: false);
await StoreService.I.put(StoreKey.serverEndpoint, serverEndpoint);
_db = db;
}

View file

@ -22,7 +22,7 @@ void main() {
sut = AssetService(
remoteRepository: remoteRepository,
exifRepository: exifRepository,
localRepository: MockDriftLocalAssetRepository(),
localRepository: MockLocalAssetRepository(),
apiRepository: apiRepository,
mediaRepository: mocks.assetMedia.api,
trashedLocalRepository: mocks.trashedAsset,
@ -37,9 +37,9 @@ void main() {
await sut.update(ids, dateTime: const .some(picked));
verify(() => apiRepository.update(ids, dateTimeOriginal: const .some(picked))).called(1);
verify(() => remoteRepository.update(ids, createdAt: .some(DateTime.parse(picked)))).called(1);
verify(() => remoteRepository.updateAssets(ids, createdAt: .some(DateTime.parse(picked)))).called(1);
verify(
() => exifRepository.update(
() => exifRepository.updateExif(
ids,
dateTimeOriginal: .some(DateTime.parse(picked)),
timeZone: const .some('UTC+06:00'),
@ -51,9 +51,9 @@ void main() {
const picked = '2026-01-05T08:00:00.000-05:30';
await sut.update(ids, dateTime: const .some(picked));
verify(() => remoteRepository.update(ids, createdAt: .some(DateTime.parse(picked)))).called(1);
verify(() => remoteRepository.updateAssets(ids, createdAt: .some(DateTime.parse(picked)))).called(1);
verify(
() => exifRepository.update(
() => exifRepository.updateExif(
ids,
dateTimeOriginal: .some(DateTime.parse(picked)),
timeZone: const .some('UTC-05:30'),
@ -65,9 +65,9 @@ void main() {
const picked = '2026-06-10T13:15:00.000Z';
await sut.update(ids, dateTime: const .some(picked));
verify(() => remoteRepository.update(ids, createdAt: .some(DateTime.parse(picked)))).called(1);
verify(() => remoteRepository.updateAssets(ids, createdAt: .some(DateTime.parse(picked)))).called(1);
verify(
() => exifRepository.update(ids, dateTimeOriginal: .some(DateTime.parse(picked)), timeZone: const .none()),
() => exifRepository.updateExif(ids, dateTimeOriginal: .some(DateTime.parse(picked)), timeZone: const .none()),
).called(1);
});

View file

@ -12,7 +12,7 @@ void main() {
setUpAll(() async {
final db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
await StoreService.init(storeRepository: DriftStoreRepository(db), listenUpdates: false);
await StoreService.init(storeRepository: StoreRepository(db), listenUpdates: false);
await StoreService.I.put(StoreKey.serverEndpoint, endpoint);
});