diff --git a/mobile/analysis_options.yaml b/mobile/analysis_options.yaml index 226e198a04..4e20acb72a 100644 --- a/mobile/analysis_options.yaml +++ b/mobile/analysis_options.yaml @@ -67,7 +67,7 @@ custom_lint: - lib/entities/*.entity.dart - lib/repositories/{album,asset,backup,database,etag,exif_info,user,timeline,partner}.repository.dart - lib/infrastructure/entities/*.entity.dart - - lib/infrastructure/repositories/{store,db,log}.repository.dart + - lib/infrastructure/repositories/{store,db,log,exif}.repository.dart - lib/providers/infrastructure/db.provider.dart # acceptable exceptions for the time being (until Isar is fully replaced) - lib/providers/app_life_cycle.provider.dart @@ -90,6 +90,7 @@ custom_lint: # required / wanted - lib/repositories/*_api.repository.dart - lib/infrastructure/repositories/*_api.repository.dart + - lib/infrastructure/utils/*.converter.dart # acceptable exceptions for the time being - lib/entities/{album,asset,exif_info,user}.entity.dart # to convert DTOs to entities - lib/utils/{image_url_builder,openapi_patching}.dart # utils are fine diff --git a/mobile/lib/domain/interfaces/exif.interface.dart b/mobile/lib/domain/interfaces/exif.interface.dart new file mode 100644 index 0000000000..a5de6167e9 --- /dev/null +++ b/mobile/lib/domain/interfaces/exif.interface.dart @@ -0,0 +1,14 @@ +import 'package:immich_mobile/domain/interfaces/db.interface.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; + +abstract interface class IExifInfoRepository implements IDatabaseRepository { + Future get(int assetId); + + Future update(ExifInfo exifInfo); + + Future> updateAll(List exifInfos); + + Future delete(int assetId); + + Future deleteAll(); +} diff --git a/mobile/lib/domain/interfaces/log.interface.dart b/mobile/lib/domain/interfaces/log.interface.dart index f1cbc977dd..27e91c5488 100644 --- a/mobile/lib/domain/interfaces/log.interface.dart +++ b/mobile/lib/domain/interfaces/log.interface.dart @@ -1,8 +1,9 @@ import 'dart:async'; +import 'package:immich_mobile/domain/interfaces/db.interface.dart'; import 'package:immich_mobile/domain/models/log.model.dart'; -abstract interface class ILogRepository { +abstract interface class ILogRepository implements IDatabaseRepository { Future insert(LogMessage log); Future insertAll(Iterable logs); diff --git a/mobile/lib/domain/models/exif.model.dart b/mobile/lib/domain/models/exif.model.dart new file mode 100644 index 0000000000..e95653ca4e --- /dev/null +++ b/mobile/lib/domain/models/exif.model.dart @@ -0,0 +1,177 @@ +class ExifInfo { + final int? assetId; + final int? fileSize; + final String? description; + final bool isFlipped; + final String? orientation; + final String? timeZone; + final DateTime? dateTimeOriginal; + + // GPS + final double? latitude; + final double? longitude; + final String? city; + final String? state; + final String? country; + + // Camera related + final String? make; + final String? model; + final String? lens; + final double? f; + final double? mm; + final int? iso; + final double? exposureSeconds; + + bool get hasCoordinates => + latitude != null && longitude != null && latitude != 0 && longitude != 0; + + String get exposureTime { + if (exposureSeconds == null) { + return ""; + } + if (exposureSeconds! < 1) { + return "1/${(1.0 / exposureSeconds!).round()} s"; + } + return "${exposureSeconds!.toStringAsFixed(1)} s"; + } + + String get fNumber => f == null ? "" : f!.toStringAsFixed(1); + + String get focalLength => mm == null ? "" : mm!.toStringAsFixed(1); + + const ExifInfo({ + this.assetId, + this.fileSize, + this.description, + this.orientation, + this.timeZone, + this.dateTimeOriginal, + this.isFlipped = false, + this.latitude, + this.longitude, + this.city, + this.state, + this.country, + this.make, + this.model, + this.lens, + this.f, + this.mm, + this.iso, + this.exposureSeconds, + }); + + @override + bool operator ==(covariant ExifInfo other) { + if (identical(this, other)) return true; + + return other.fileSize == fileSize && + other.description == description && + other.orientation == orientation && + other.timeZone == timeZone && + other.dateTimeOriginal == dateTimeOriginal && + other.latitude == latitude && + other.longitude == longitude && + other.city == city && + other.state == state && + other.country == country && + other.make == make && + other.model == model && + other.lens == lens && + other.f == f && + other.mm == mm && + other.iso == iso && + other.exposureSeconds == exposureSeconds && + other.assetId == assetId; + } + + @override + int get hashCode { + return fileSize.hashCode ^ + description.hashCode ^ + orientation.hashCode ^ + timeZone.hashCode ^ + dateTimeOriginal.hashCode ^ + latitude.hashCode ^ + longitude.hashCode ^ + city.hashCode ^ + state.hashCode ^ + country.hashCode ^ + make.hashCode ^ + model.hashCode ^ + lens.hashCode ^ + f.hashCode ^ + mm.hashCode ^ + iso.hashCode ^ + exposureSeconds.hashCode ^ + assetId.hashCode; + } + + @override + String toString() { + return '''{ +fileSize: ${fileSize ?? 'NA'}, +description: ${description ?? 'NA'}, +orientation: ${orientation ?? 'NA'}, +timeZone: ${timeZone ?? 'NA'}, +dateTimeOriginal: ${dateTimeOriginal ?? 'NA'}, +latitude: ${latitude ?? 'NA'}, +longitude: ${longitude ?? 'NA'}, +city: ${city ?? 'NA'}, +state: ${state ?? 'NA'}, +country: ${country ?? ''}, +make: ${make ?? 'NA'}, +model: ${model ?? 'NA'}, +lens: ${lens ?? 'NA'}, +f: ${f ?? 'NA'}, +mm: ${mm ?? ''}, +iso: ${iso ?? 'NA'}, +exposureSeconds: ${exposureSeconds ?? 'NA'}, +}'''; + } + + ExifInfo copyWith({ + int? assetId, + int? fileSize, + String? description, + String? orientation, + String? timeZone, + DateTime? dateTimeOriginal, + double? latitude, + double? longitude, + String? city, + String? state, + String? country, + bool? isFlipped, + String? make, + String? model, + String? lens, + double? f, + double? mm, + int? iso, + double? exposureSeconds, + }) { + return ExifInfo( + assetId: assetId ?? this.assetId, + fileSize: fileSize ?? this.fileSize, + description: description ?? this.description, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + isFlipped: isFlipped ?? this.isFlipped, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + f: f ?? this.f, + mm: mm ?? this.mm, + iso: iso ?? this.iso, + exposureSeconds: exposureSeconds ?? this.exposureSeconds, + ); + } +} diff --git a/mobile/lib/entities/asset.entity.dart b/mobile/lib/entities/asset.entity.dart index f6b10a3ab5..048068ad3d 100644 --- a/mobile/lib/entities/asset.entity.dart +++ b/mobile/lib/entities/asset.entity.dart @@ -1,13 +1,16 @@ import 'dart:convert'; import 'dart:io'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:immich_mobile/extensions/string_extensions.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart' + as entity; +import 'package:immich_mobile/infrastructure/utils/exif.converter.dart'; import 'package:immich_mobile/utils/hash.dart'; import 'package:isar/isar.dart'; import 'package:openapi/api.dart'; -import 'package:photo_manager/photo_manager.dart' show AssetEntity; -import 'package:immich_mobile/extensions/string_extensions.dart'; import 'package:path/path.dart' as p; +import 'package:photo_manager/photo_manager.dart' show AssetEntity; part 'asset.entity.g.dart'; @@ -27,8 +30,9 @@ class Asset { width = remote.exifInfo?.exifImageWidth?.toInt(), livePhotoVideoId = remote.livePhotoVideoId, ownerId = fastHash(remote.ownerId), - exifInfo = - remote.exifInfo != null ? ExifInfo.fromDto(remote.exifInfo!) : null, + exifInfo = remote.exifInfo == null + ? null + : ExifDtoConverter.fromDto(remote.exifInfo!), isFavorite = remote.isFavorite, isArchived = remote.isArchived, isTrashed = remote.isTrashed, @@ -359,14 +363,14 @@ class Asset { localId: localId, width: a.width ?? width, height: a.height ?? height, - exifInfo: a.exifInfo?.copyWith(id: id) ?? exifInfo, + exifInfo: a.exifInfo?.copyWith(assetId: id) ?? exifInfo, ); } else if (isRemote) { return _copyWith( localId: localId ?? a.localId, width: width ?? a.width, height: height ?? a.height, - exifInfo: exifInfo ?? a.exifInfo?.copyWith(id: id), + exifInfo: exifInfo ?? a.exifInfo?.copyWith(assetId: id), ); } else { // TODO: Revisit this and remove all bool field assignments @@ -407,7 +411,7 @@ class Asset { isArchived: a.isArchived, isTrashed: a.isTrashed, isOffline: a.isOffline, - exifInfo: a.exifInfo?.copyWith(id: id) ?? exifInfo, + exifInfo: a.exifInfo?.copyWith(assetId: id) ?? exifInfo, thumbhash: a.thumbhash, ); } else { @@ -416,7 +420,8 @@ class Asset { localId: localId ?? a.localId, width: width ?? a.width, height: height ?? a.height, - exifInfo: exifInfo ?? a.exifInfo?.copyWith(id: id), + exifInfo: exifInfo ?? + a.exifInfo?.copyWith(assetId: id), // updated to use assetId ); } } @@ -476,8 +481,8 @@ class Asset { Future put(Isar db) async { await db.assets.put(this); if (exifInfo != null) { - exifInfo!.id = id; - await db.exifInfos.put(exifInfo!); + await db.exifInfos + .put(entity.ExifInfo.fromDto(exifInfo!.copyWith(assetId: id))); } } diff --git a/mobile/lib/entities/exif_info.entity.dart b/mobile/lib/entities/exif_info.entity.dart deleted file mode 100644 index c46f3dddc1..0000000000 --- a/mobile/lib/entities/exif_info.entity.dart +++ /dev/null @@ -1,241 +0,0 @@ -import 'package:isar/isar.dart'; -import 'package:openapi/api.dart'; - -part 'exif_info.entity.g.dart'; - -/// Exif information 1:1 relation with Asset -@Collection(inheritance: false) -class ExifInfo { - Id? id; - int? fileSize; - DateTime? dateTimeOriginal; - String? timeZone; - String? make; - String? model; - String? lens; - float? f; - float? mm; - short? iso; - float? exposureSeconds; - float? lat; - float? long; - String? city; - String? state; - String? country; - String? description; - String? orientation; - - @ignore - bool get hasCoordinates => - latitude != null && longitude != null && latitude != 0 && longitude != 0; - - @ignore - String get exposureTime { - if (exposureSeconds == null) { - return ""; - } else if (exposureSeconds! < 1) { - return "1/${(1.0 / exposureSeconds!).round()} s"; - } else { - return "${exposureSeconds!.toStringAsFixed(1)} s"; - } - } - - @ignore - String get fNumber => f != null ? f!.toStringAsFixed(1) : ""; - - @ignore - String get focalLength => mm != null ? mm!.toStringAsFixed(1) : ""; - - @ignore - bool? _isFlipped; - - @ignore - @pragma('vm:prefer-inline') - bool get isFlipped => _isFlipped ??= _isOrientationFlipped(orientation); - - @ignore - double? get latitude => lat; - - @ignore - double? get longitude => long; - - ExifInfo.fromDto(ExifResponseDto dto) - : fileSize = dto.fileSizeInByte, - dateTimeOriginal = dto.dateTimeOriginal, - timeZone = dto.timeZone, - make = dto.make, - model = dto.model, - lens = dto.lensModel, - f = dto.fNumber?.toDouble(), - mm = dto.focalLength?.toDouble(), - iso = dto.iso?.toInt(), - exposureSeconds = _exposureTimeToSeconds(dto.exposureTime), - lat = dto.latitude?.toDouble(), - long = dto.longitude?.toDouble(), - city = dto.city, - state = dto.state, - country = dto.country, - description = dto.description, - orientation = dto.orientation; - - ExifInfo({ - this.id, - this.fileSize, - this.dateTimeOriginal, - this.timeZone, - this.make, - this.model, - this.lens, - this.f, - this.mm, - this.iso, - this.exposureSeconds, - this.lat, - this.long, - this.city, - this.state, - this.country, - this.description, - this.orientation, - }); - - ExifInfo copyWith({ - Id? id, - int? fileSize, - DateTime? dateTimeOriginal, - String? timeZone, - String? make, - String? model, - String? lens, - float? f, - float? mm, - short? iso, - float? exposureSeconds, - float? lat, - float? long, - String? city, - String? state, - String? country, - String? description, - String? orientation, - }) => - ExifInfo( - id: id ?? this.id, - fileSize: fileSize ?? this.fileSize, - dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, - timeZone: timeZone ?? this.timeZone, - make: make ?? this.make, - model: model ?? this.model, - lens: lens ?? this.lens, - f: f ?? this.f, - mm: mm ?? this.mm, - iso: iso ?? this.iso, - exposureSeconds: exposureSeconds ?? this.exposureSeconds, - lat: lat ?? this.lat, - long: long ?? this.long, - city: city ?? this.city, - state: state ?? this.state, - country: country ?? this.country, - description: description ?? this.description, - orientation: orientation ?? this.orientation, - ); - - @override - bool operator ==(other) { - if (other is! ExifInfo) return false; - return id == other.id && - fileSize == other.fileSize && - dateTimeOriginal == other.dateTimeOriginal && - timeZone == other.timeZone && - make == other.make && - model == other.model && - lens == other.lens && - f == other.f && - mm == other.mm && - iso == other.iso && - exposureSeconds == other.exposureSeconds && - lat == other.lat && - long == other.long && - city == other.city && - state == other.state && - country == other.country && - description == other.description && - orientation == other.orientation; - } - - @override - @ignore - int get hashCode => - id.hashCode ^ - fileSize.hashCode ^ - dateTimeOriginal.hashCode ^ - timeZone.hashCode ^ - make.hashCode ^ - model.hashCode ^ - lens.hashCode ^ - f.hashCode ^ - mm.hashCode ^ - iso.hashCode ^ - exposureSeconds.hashCode ^ - lat.hashCode ^ - long.hashCode ^ - city.hashCode ^ - state.hashCode ^ - country.hashCode ^ - description.hashCode ^ - orientation.hashCode; - - @override - String toString() { - return """ -{ - id: $id, - fileSize: $fileSize, - dateTimeOriginal: $dateTimeOriginal, - timeZone: $timeZone, - make: $make, - model: $model, - lens: $lens, - f: $f, - mm: $mm, - iso: $iso, - exposureSeconds: $exposureSeconds, - lat: $lat, - long: $long, - city: $city, - state: $state, - country: $country, - description: $description, - orientation: $orientation -}"""; - } -} - -bool _isOrientationFlipped(String? orientation) { - final value = orientation != null ? int.tryParse(orientation) : null; - if (value == null) { - return false; - } - final isRotated90CW = value == 5 || value == 6 || value == 90; - final isRotated270CW = value == 7 || value == 8 || value == -90; - return isRotated90CW || isRotated270CW; -} - -double? _exposureTimeToSeconds(String? s) { - if (s == null) { - return null; - } - double? value = double.tryParse(s); - if (value != null) { - return value; - } - final parts = s.split("/"); - if (parts.length == 2) { - final numerator = double.tryParse(parts[0]); - final denominator = double.tryParse(parts[1]); - if (numerator != null && denominator != null) { - return numerator / denominator; - } - } - return null; -} diff --git a/mobile/lib/infrastructure/entities/exif.entity.dart b/mobile/lib/infrastructure/entities/exif.entity.dart new file mode 100644 index 0000000000..42ec00a2f5 --- /dev/null +++ b/mobile/lib/infrastructure/entities/exif.entity.dart @@ -0,0 +1,90 @@ +import 'package:immich_mobile/domain/models/exif.model.dart' as domain; +import 'package:isar/isar.dart'; + +part 'exif.entity.g.dart'; + +/// Exif information 1:1 relation with Asset +@Collection(inheritance: false) +class ExifInfo { + final Id? id; + final int? fileSize; + final DateTime? dateTimeOriginal; + final String? timeZone; + final String? make; + final String? model; + final String? lens; + final float? f; + final float? mm; + final short? iso; + final float? exposureSeconds; + final float? lat; + final float? long; + final String? city; + final String? state; + final String? country; + final String? description; + final String? orientation; + + const ExifInfo({ + this.id, + this.fileSize, + this.dateTimeOriginal, + this.timeZone, + this.make, + this.model, + this.lens, + this.f, + this.mm, + this.iso, + this.exposureSeconds, + this.lat, + this.long, + this.city, + this.state, + this.country, + this.description, + this.orientation, + }); + + static ExifInfo fromDto(domain.ExifInfo dto) => ExifInfo( + id: dto.assetId, + fileSize: dto.fileSize, + dateTimeOriginal: dto.dateTimeOriginal, + timeZone: dto.timeZone, + make: dto.make, + model: dto.model, + lens: dto.lens, + f: dto.f, + mm: dto.mm, + iso: dto.iso?.toInt(), + exposureSeconds: dto.exposureSeconds, + lat: dto.latitude, + long: dto.longitude, + city: dto.city, + state: dto.state, + country: dto.country, + description: dto.description, + orientation: dto.orientation, + ); + + domain.ExifInfo toDto() => domain.ExifInfo( + assetId: id, + fileSize: fileSize, + description: description, + orientation: orientation, + timeZone: timeZone, + dateTimeOriginal: dateTimeOriginal, + latitude: lat, + longitude: long, + city: city, + state: state, + country: country, + make: make, + model: model, + lens: lens, + f: f, + mm: mm, + iso: iso?.toInt(), + exposureSeconds: exposureSeconds, + ); +} diff --git a/mobile/lib/entities/exif_info.entity.g.dart b/mobile/lib/infrastructure/entities/exif.entity.g.dart similarity index 99% rename from mobile/lib/entities/exif_info.entity.g.dart rename to mobile/lib/infrastructure/entities/exif.entity.g.dart index 0b744e5f20..989338abff 100644 --- a/mobile/lib/entities/exif_info.entity.g.dart +++ b/mobile/lib/infrastructure/entities/exif.entity.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'exif_info.entity.dart'; +part of 'exif.entity.dart'; // ************************************************************************** // IsarCollectionGenerator @@ -288,9 +288,7 @@ List> _exifInfoGetLinks(ExifInfo object) { return []; } -void _exifInfoAttach(IsarCollection col, Id id, ExifInfo object) { - object.id = id; -} +void _exifInfoAttach(IsarCollection col, Id id, ExifInfo object) {} extension ExifInfoQueryWhereSort on QueryBuilder { QueryBuilder anyId() { diff --git a/mobile/lib/infrastructure/repositories/exif.repository.dart b/mobile/lib/infrastructure/repositories/exif.repository.dart new file mode 100644 index 0000000000..2b4276dd57 --- /dev/null +++ b/mobile/lib/infrastructure/repositories/exif.repository.dart @@ -0,0 +1,50 @@ +import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart' + as entity; +import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; +import 'package:isar/isar.dart'; + +class IsarExifRepository extends IsarDatabaseRepository + implements IExifInfoRepository { + final Isar _db; + + const IsarExifRepository(this._db) : super(_db); + + @override + Future delete(int assetId) async { + await transaction(() async { + await _db.exifInfos.delete(assetId); + }); + } + + @override + Future deleteAll() async { + await transaction(() async { + await _db.exifInfos.clear(); + }); + } + + @override + Future get(int assetId) async { + return (await _db.exifInfos.get(assetId))?.toDto(); + } + + @override + Future update(ExifInfo exifInfo) { + return transaction(() async { + await _db.exifInfos.put(entity.ExifInfo.fromDto(exifInfo)); + return exifInfo; + }); + } + + @override + Future> updateAll(List exifInfos) { + return transaction(() async { + await _db.exifInfos.putAll( + exifInfos.map(entity.ExifInfo.fromDto).toList(), + ); + return exifInfos; + }); + } +} diff --git a/mobile/lib/infrastructure/utils/exif.converter.dart b/mobile/lib/infrastructure/utils/exif.converter.dart new file mode 100644 index 0000000000..ca4d175460 --- /dev/null +++ b/mobile/lib/infrastructure/utils/exif.converter.dart @@ -0,0 +1,56 @@ +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:openapi/api.dart'; + +abstract final class ExifDtoConverter { + static ExifInfo fromDto(ExifResponseDto dto) { + return ExifInfo( + fileSize: dto.fileSizeInByte, + description: dto.description, + orientation: dto.orientation, + timeZone: dto.timeZone, + dateTimeOriginal: dto.dateTimeOriginal, + isFlipped: _isOrientationFlipped(dto.orientation), + latitude: dto.latitude?.toDouble(), + longitude: dto.longitude?.toDouble(), + city: dto.city, + state: dto.state, + country: dto.country, + make: dto.make, + model: dto.model, + lens: dto.lensModel, + f: dto.fNumber?.toDouble(), + mm: dto.focalLength?.toDouble(), + iso: dto.iso?.toInt(), + exposureSeconds: _exposureTimeToSeconds(dto.exposureTime), + ); + } + + static bool _isOrientationFlipped(String? orientation) { + final value = orientation == null ? null : int.tryParse(orientation); + if (value == null) { + return false; + } + final isRotated90CW = value == 5 || value == 6 || value == 90; + final isRotated270CW = value == 7 || value == 8 || value == -90; + return isRotated90CW || isRotated270CW; + } + + static double? _exposureTimeToSeconds(String? s) { + if (s == null) { + return null; + } + double? value = double.tryParse(s); + if (value != null) { + return value; + } + final parts = s.split("/"); + if (parts.length == 2) { + final numerator = double.tryParse(parts.firstOrNull ?? "-"); + final denominator = double.tryParse(parts.lastOrNull ?? "-"); + if (numerator != null && denominator != null) { + return numerator / denominator; + } + } + return null; + } +} diff --git a/mobile/lib/interfaces/exif_info.interface.dart b/mobile/lib/interfaces/exif_info.interface.dart deleted file mode 100644 index ce379c926c..0000000000 --- a/mobile/lib/interfaces/exif_info.interface.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:immich_mobile/entities/exif_info.entity.dart'; -import 'package:immich_mobile/interfaces/database.interface.dart'; - -abstract interface class IExifInfoRepository implements IDatabaseRepository { - Future get(int id); - - Future update(ExifInfo exifInfo); - - Future> updateAll(List exifInfos); - - Future delete(int id); - - Future clearTable(); -} diff --git a/mobile/lib/providers/infrastructure/exif.provider.dart b/mobile/lib/providers/infrastructure/exif.provider.dart new file mode 100644 index 0000000000..7c95fd1a20 --- /dev/null +++ b/mobile/lib/providers/infrastructure/exif.provider.dart @@ -0,0 +1,10 @@ +import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; +import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart'; +import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'exif.provider.g.dart'; + +@Riverpod(keepAlive: true) +IExifInfoRepository exifRepository(ExifRepositoryRef ref) => + IsarExifRepository(ref.watch(isarProvider)); diff --git a/mobile/lib/providers/infrastructure/exif.provider.g.dart b/mobile/lib/providers/infrastructure/exif.provider.g.dart new file mode 100644 index 0000000000..a9e60ca463 --- /dev/null +++ b/mobile/lib/providers/infrastructure/exif.provider.g.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'exif.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exifRepositoryHash() => r'f8f94d2a43fa79b08e58d60e81d7877825f33ec0'; + +/// See also [exifRepository]. +@ProviderFor(exifRepository) +final exifRepositoryProvider = Provider.internal( + exifRepository, + name: r'exifRepositoryProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$exifRepositoryHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExifRepositoryRef = ProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/mobile/lib/repositories/asset.repository.dart b/mobile/lib/repositories/asset.repository.dart index ec7fe77dea..c27660c352 100644 --- a/mobile/lib/repositories/asset.repository.dart +++ b/mobile/lib/repositories/asset.repository.dart @@ -6,8 +6,8 @@ import 'package:immich_mobile/entities/android_device_asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/device_asset.entity.dart'; import 'package:immich_mobile/entities/duplicated_asset.entity.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; import 'package:immich_mobile/entities/ios_device_asset.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/repositories/database.repository.dart'; diff --git a/mobile/lib/repositories/asset_media.repository.dart b/mobile/lib/repositories/asset_media.repository.dart index 4f8272b44f..97d22f3600 100644 --- a/mobile/lib/repositories/asset_media.repository.dart +++ b/mobile/lib/repositories/asset_media.repository.dart @@ -1,7 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/interfaces/asset_media.interface.dart'; import 'package:photo_manager/photo_manager.dart' hide AssetType; @@ -39,7 +39,8 @@ class AssetMediaRepository implements IAssetMediaRepository { asset.fileCreatedAt = asset.fileModifiedAt; } if (local.latitude != null) { - asset.exifInfo = ExifInfo(lat: local.latitude, long: local.longitude); + asset.exifInfo = + ExifInfo(latitude: local.latitude, longitude: local.longitude); } asset.local = local; return asset; diff --git a/mobile/lib/repositories/auth.repository.dart b/mobile/lib/repositories/auth.repository.dart index 491db3c8a8..18e14865aa 100644 --- a/mobile/lib/repositories/auth.repository.dart +++ b/mobile/lib/repositories/auth.repository.dart @@ -5,9 +5,9 @@ import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/etag.entity.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; import 'package:immich_mobile/interfaces/auth.interface.dart'; import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; import 'package:immich_mobile/providers/db.provider.dart'; diff --git a/mobile/lib/repositories/exif_info.repository.dart b/mobile/lib/repositories/exif_info.repository.dart deleted file mode 100644 index a70b216df1..0000000000 --- a/mobile/lib/repositories/exif_info.repository.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; -import 'package:immich_mobile/interfaces/exif_info.interface.dart'; -import 'package:immich_mobile/providers/db.provider.dart'; -import 'package:immich_mobile/repositories/database.repository.dart'; - -final exifInfoRepositoryProvider = - Provider((ref) => ExifInfoRepository(ref.watch(dbProvider))); - -class ExifInfoRepository extends DatabaseRepository - implements IExifInfoRepository { - ExifInfoRepository(super.db); - - @override - Future delete(int id) => txn(() => db.exifInfos.delete(id)); - - @override - Future get(int id) => db.exifInfos.get(id); - - @override - Future update(ExifInfo exifInfo) async { - await txn(() => db.exifInfos.put(exifInfo)); - return exifInfo; - } - - @override - Future> updateAll(List exifInfos) async { - await txn(() => db.exifInfos.putAll(exifInfos)); - return exifInfos; - } - - @override - Future clearTable() { - return txn(() => db.exifInfos.clear()); - } -} diff --git a/mobile/lib/services/asset.service.dart b/mobile/lib/services/asset.service.dart index a4e77c216d..815962efac 100644 --- a/mobile/lib/services/asset.service.dart +++ b/mobile/lib/services/asset.service.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart'; @@ -12,16 +13,15 @@ import 'package:immich_mobile/interfaces/asset_api.interface.dart'; import 'package:immich_mobile/interfaces/asset_media.interface.dart'; import 'package:immich_mobile/interfaces/backup_album.interface.dart'; import 'package:immich_mobile/interfaces/etag.interface.dart'; -import 'package:immich_mobile/interfaces/exif_info.interface.dart'; import 'package:immich_mobile/interfaces/user.interface.dart'; import 'package:immich_mobile/models/backup/backup_candidate.model.dart'; import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/exif.provider.dart'; import 'package:immich_mobile/repositories/asset.repository.dart'; import 'package:immich_mobile/repositories/asset_api.repository.dart'; import 'package:immich_mobile/repositories/asset_media.repository.dart'; import 'package:immich_mobile/repositories/backup.repository.dart'; import 'package:immich_mobile/repositories/etag.repository.dart'; -import 'package:immich_mobile/repositories/exif_info.repository.dart'; import 'package:immich_mobile/repositories/user.repository.dart'; import 'package:immich_mobile/services/album.service.dart'; import 'package:immich_mobile/services/api.service.dart'; @@ -36,7 +36,7 @@ final assetServiceProvider = Provider( (ref) => AssetService( ref.watch(assetApiRepositoryProvider), ref.watch(assetRepositoryProvider), - ref.watch(exifInfoRepositoryProvider), + ref.watch(exifRepositoryProvider), ref.watch(userRepositoryProvider), ref.watch(etagRepositoryProvider), ref.watch(backupAlbumRepositoryProvider), @@ -166,17 +166,18 @@ class AssetService { /// Loads the exif information from the database. If there is none, loads /// the exif info from the server (remote assets only) Future loadExif(Asset a) async { - a.exifInfo ??= await _exifInfoRepository.get(a.id); + a.exifInfo ??= (await _exifInfoRepository.get(a.id)); // fileSize is always filled on the server but not set on client if (a.exifInfo?.fileSize == null) { if (a.isRemote) { final dto = await _apiService.assetsApi.getAssetInfo(a.remoteId!); if (dto != null && dto.exifInfo != null) { - final newExif = Asset.remote(dto).exifInfo!.copyWith(id: a.id); + final newExif = Asset.remote(dto).exifInfo!.copyWith(assetId: a.id); a.exifInfo = newExif; if (newExif != a.exifInfo) { if (a.isInDb) { - _assetRepository.transaction(() => _assetRepository.update(a)); + await _assetRepository + .transaction(() => _assetRepository.update(a)); } else { debugPrint("[loadExif] parameter Asset is not from DB!"); } @@ -257,7 +258,8 @@ class AssetService { for (var element in assets) { element.fileCreatedAt = DateTime.parse(updatedDt); - element.exifInfo?.dateTimeOriginal = DateTime.parse(updatedDt); + element.exifInfo ??= element.exifInfo + ?.copyWith(dateTimeOriginal: DateTime.parse(updatedDt)); } await _syncService.upsertAssetsWithExif(assets); @@ -283,8 +285,10 @@ class AssetService { ); for (var element in assets) { - element.exifInfo?.lat = location.latitude; - element.exifInfo?.long = location.longitude; + element.exifInfo ??= element.exifInfo?.copyWith( + latitude: location.latitude, + longitude: location.longitude, + ); } await _syncService.upsertAssetsWithExif(assets); @@ -348,7 +352,7 @@ class AssetService { String newDescription, ) async { final remoteAssetId = asset.remoteId; - final localExifId = asset.exifInfo?.id; + final localExifId = asset.exifInfo?.assetId; // Guard [remoteAssetId] and [localExifId] null if (remoteAssetId == null || localExifId == null) { @@ -366,14 +370,14 @@ class AssetService { var exifInfo = await _exifInfoRepository.get(localExifId); if (exifInfo != null) { - exifInfo.description = description; - await _exifInfoRepository.update(exifInfo); + await _exifInfoRepository + .update(exifInfo.copyWith(description: description)); } } } Future getDescription(Asset asset) async { - final localExifId = asset.exifInfo?.id; + final localExifId = asset.exifInfo?.assetId; // Guard [remoteAssetId] and [localExifId] null if (localExifId == null) { diff --git a/mobile/lib/services/background.service.dart b/mobile/lib/services/background.service.dart index 8183282586..548e4b1ad5 100644 --- a/mobile/lib/services/background.service.dart +++ b/mobile/lib/services/background.service.dart @@ -11,9 +11,11 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart'; import 'package:immich_mobile/interfaces/backup_album.interface.dart'; import 'package:immich_mobile/models/backup/backup_candidate.model.dart'; import 'package:immich_mobile/models/backup/current_upload_asset.model.dart'; @@ -28,7 +30,6 @@ import 'package:immich_mobile/repositories/auth.repository.dart'; import 'package:immich_mobile/repositories/auth_api.repository.dart'; import 'package:immich_mobile/repositories/backup.repository.dart'; import 'package:immich_mobile/repositories/etag.repository.dart'; -import 'package:immich_mobile/repositories/exif_info.repository.dart'; import 'package:immich_mobile/repositories/file_media.repository.dart'; import 'package:immich_mobile/repositories/network.repository.dart'; import 'package:immich_mobile/repositories/partner_api.repository.dart'; @@ -379,7 +380,7 @@ class BackgroundService { AlbumRepository albumRepository = AlbumRepository(db); AssetRepository assetRepository = AssetRepository(db); BackupAlbumRepository backupRepository = BackupAlbumRepository(db); - ExifInfoRepository exifInfoRepository = ExifInfoRepository(db); + IExifInfoRepository exifInfoRepository = IsarExifRepository(db); ETagRepository eTagRepository = ETagRepository(db); AlbumMediaRepository albumMediaRepository = AlbumMediaRepository(); FileMediaRepository fileMediaRepository = FileMediaRepository(); diff --git a/mobile/lib/services/backup_verification.service.dart b/mobile/lib/services/backup_verification.service.dart index 0d47d1a111..c2e93a678a 100644 --- a/mobile/lib/services/backup_verification.service.dart +++ b/mobile/lib/services/backup_verification.service.dart @@ -5,15 +5,16 @@ import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/utils/exif.converter.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart'; -import 'package:immich_mobile/interfaces/exif_info.interface.dart'; import 'package:immich_mobile/interfaces/file_media.interface.dart'; +import 'package:immich_mobile/providers/infrastructure/exif.provider.dart'; import 'package:immich_mobile/repositories/asset.repository.dart'; -import 'package:immich_mobile/repositories/exif_info.repository.dart'; import 'package:immich_mobile/repositories/file_media.repository.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/bootstrap.dart'; @@ -149,11 +150,11 @@ class BackupVerificationService { ) async { if (remote.checksum == local.checksum) return false; ExifInfo? exif = remote.exifInfo; - if (exif != null && exif.lat != null) return false; + if (exif != null && exif.latitude != null) return false; if (exif == null || exif.fileSize == null) { final dto = await apiService.assetsApi.getAssetInfo(remote.remoteId!); if (dto != null && dto.exifInfo != null) { - exif = ExifInfo.fromDto(dto.exifInfo!); + exif = ExifDtoConverter.fromDto(dto.exifInfo!); } } final file = await local.local!.originFile; @@ -162,7 +163,7 @@ class BackupVerificationService { if (exif.fileSize! == origSize || exif.fileSize! != origSize) { final latLng = await local.local!.latlngAsync(); - if (exif.lat == null && + if (exif.latitude == null && latLng.latitude != null && (remote.fileCreatedAt.isAtSameMomentAs(local.fileCreatedAt) || remote.fileModifiedAt.isAtSameMomentAs(local.fileModifiedAt) || @@ -215,6 +216,6 @@ final backupVerificationServiceProvider = Provider( (ref) => BackupVerificationService( ref.watch(fileMediaRepositoryProvider), ref.watch(assetRepositoryProvider), - ref.watch(exifInfoRepositoryProvider), + ref.watch(exifRepositoryProvider), ), ); diff --git a/mobile/lib/services/exif.service.dart b/mobile/lib/services/exif.service.dart index 2ce2b1ffa5..973f04303e 100644 --- a/mobile/lib/services/exif.service.dart +++ b/mobile/lib/services/exif.service.dart @@ -1,16 +1,16 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/interfaces/exif_info.interface.dart'; -import 'package:immich_mobile/repositories/exif_info.repository.dart'; +import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; +import 'package:immich_mobile/providers/infrastructure/exif.provider.dart'; final exifServiceProvider = - Provider((ref) => ExifService(ref.watch(exifInfoRepositoryProvider))); + Provider((ref) => ExifService(ref.watch(exifRepositoryProvider))); class ExifService { final IExifInfoRepository _exifInfoRepository; - ExifService(this._exifInfoRepository); + const ExifService(this._exifInfoRepository); Future clearTable() { - return _exifInfoRepository.clearTable(); + return _exifInfoRepository.deleteAll(); } } diff --git a/mobile/lib/services/sync.service.dart b/mobile/lib/services/sync.service.dart index d01a49a38d..b937dde320 100644 --- a/mobile/lib/services/sync.service.dart +++ b/mobile/lib/services/sync.service.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/etag.entity.dart'; @@ -12,14 +13,13 @@ import 'package:immich_mobile/interfaces/album_api.interface.dart'; import 'package:immich_mobile/interfaces/album_media.interface.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/etag.interface.dart'; -import 'package:immich_mobile/interfaces/exif_info.interface.dart'; import 'package:immich_mobile/interfaces/user.interface.dart'; +import 'package:immich_mobile/providers/infrastructure/exif.provider.dart'; import 'package:immich_mobile/repositories/album.repository.dart'; import 'package:immich_mobile/repositories/album_api.repository.dart'; import 'package:immich_mobile/repositories/album_media.repository.dart'; import 'package:immich_mobile/repositories/asset.repository.dart'; import 'package:immich_mobile/repositories/etag.repository.dart'; -import 'package:immich_mobile/repositories/exif_info.repository.dart'; import 'package:immich_mobile/repositories/user.repository.dart'; import 'package:immich_mobile/services/entity.service.dart'; import 'package:immich_mobile/services/hash.service.dart'; @@ -36,7 +36,7 @@ final syncServiceProvider = Provider( ref.watch(albumApiRepositoryProvider), ref.watch(albumRepositoryProvider), ref.watch(assetRepositoryProvider), - ref.watch(exifInfoRepositoryProvider), + ref.watch(exifRepositoryProvider), ref.watch(userRepositoryProvider), ref.watch(etagRepositoryProvider), ), @@ -756,7 +756,7 @@ class SyncService { await _assetRepository.transaction(() async { await _assetRepository.updateAll(assets); for (final Asset added in assets) { - added.exifInfo?.id = added.id; + added.exifInfo ??= added.exifInfo?.copyWith(assetId: added.id); } await _exifInfoRepository.updateAll(exifInfos); }); diff --git a/mobile/lib/utils/bootstrap.dart b/mobile/lib/utils/bootstrap.dart index 4a9ce1a5e1..db019798a3 100644 --- a/mobile/lib/utils/bootstrap.dart +++ b/mobile/lib/utils/bootstrap.dart @@ -9,9 +9,9 @@ import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/entities/duplicated_asset.entity.dart'; import 'package:immich_mobile/entities/etag.entity.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; import 'package:immich_mobile/entities/ios_device_asset.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; import 'package:immich_mobile/infrastructure/entities/log.entity.dart'; import 'package:immich_mobile/infrastructure/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/log.repository.dart'; diff --git a/mobile/lib/utils/migration.dart b/mobile/lib/utils/migration.dart index eedf8f40f8..990fc082d5 100644 --- a/mobile/lib/utils/migration.dart +++ b/mobile/lib/utils/migration.dart @@ -4,9 +4,9 @@ import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/etag.entity.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; import 'package:isar/isar.dart'; const int targetVersion = 8; diff --git a/mobile/lib/utils/selection_handlers.dart b/mobile/lib/utils/selection_handlers.dart index 56160a2efc..4a9fcc8c99 100644 --- a/mobile/lib/utils/selection_handlers.dart +++ b/mobile/lib/utils/selection_handlers.dart @@ -2,9 +2,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/extensions/asset_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/services/asset.service.dart'; import 'package:immich_mobile/services/share.service.dart'; diff --git a/mobile/lib/widgets/asset_viewer/description_input.dart b/mobile/lib/widgets/asset_viewer/description_input.dart index 3fdd40130a..2d9d71ac9e 100644 --- a/mobile/lib/widgets/asset_viewer/description_input.dart +++ b/mobile/lib/widgets/asset_viewer/description_input.dart @@ -2,9 +2,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; diff --git a/mobile/lib/widgets/asset_viewer/detail_panel/asset_details.dart b/mobile/lib/widgets/asset_viewer/detail_panel/asset_details.dart index a78a309512..59b52344e7 100644 --- a/mobile/lib/widgets/asset_viewer/detail_panel/asset_details.dart +++ b/mobile/lib/widgets/asset_viewer/detail_panel/asset_details.dart @@ -1,12 +1,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/asset.provider.dart'; -import 'package:immich_mobile/widgets/asset_viewer/detail_panel/file_info.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; import 'package:immich_mobile/widgets/asset_viewer/detail_panel/camera_info.dart'; +import 'package:immich_mobile/widgets/asset_viewer/detail_panel/file_info.dart'; class AssetDetails extends ConsumerWidget { final Asset asset; diff --git a/mobile/lib/widgets/asset_viewer/detail_panel/asset_location.dart b/mobile/lib/widgets/asset_viewer/detail_panel/asset_location.dart index 364b568d0a..40e09cb5c8 100644 --- a/mobile/lib/widgets/asset_viewer/detail_panel/asset_location.dart +++ b/mobile/lib/widgets/asset_viewer/detail_panel/asset_location.dart @@ -1,12 +1,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/utils/selection_handlers.dart'; import 'package:immich_mobile/widgets/asset_viewer/detail_panel/exif_map.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; class AssetLocation extends HookConsumerWidget { final Asset asset; diff --git a/mobile/lib/widgets/asset_viewer/detail_panel/camera_info.dart b/mobile/lib/widgets/asset_viewer/detail_panel/camera_info.dart index e6720e0255..aec18c6a16 100644 --- a/mobile/lib/widgets/asset_viewer/detail_panel/camera_info.dart +++ b/mobile/lib/widgets/asset_viewer/detail_panel/camera_info.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; class CameraInfo extends StatelessWidget { diff --git a/mobile/lib/widgets/asset_viewer/detail_panel/exif_map.dart b/mobile/lib/widgets/asset_viewer/detail_panel/exif_map.dart index 7878404273..f3f72dfd87 100644 --- a/mobile/lib/widgets/asset_viewer/detail_panel/exif_map.dart +++ b/mobile/lib/widgets/asset_viewer/detail_panel/exif_map.dart @@ -1,8 +1,8 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/widgets/map/map_thumbnail.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; import 'package:url_launcher/url_launcher.dart'; diff --git a/mobile/test/modules/extensions/asset_extensions_test.dart b/mobile/test/modules/extensions/asset_extensions_test.dart index d2b9b93d62..dd334c7b9d 100644 --- a/mobile/test/modules/extensions/asset_extensions_test.dart +++ b/mobile/test/modules/extensions/asset_extensions_test.dart @@ -1,7 +1,7 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/extensions/asset_extensions.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; +import 'package:immich_mobile/extensions/asset_extensions.dart'; import 'package:timezone/data/latest.dart'; import 'package:timezone/timezone.dart'; diff --git a/mobile/test/repository.mocks.dart b/mobile/test/repository.mocks.dart index bad7d3ebab..7443db2815 100644 --- a/mobile/test/repository.mocks.dart +++ b/mobile/test/repository.mocks.dart @@ -1,3 +1,4 @@ +import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; import 'package:immich_mobile/interfaces/album.interface.dart'; import 'package:immich_mobile/interfaces/album_api.interface.dart'; import 'package:immich_mobile/interfaces/album_media.interface.dart'; @@ -7,7 +8,6 @@ import 'package:immich_mobile/interfaces/auth.interface.dart'; import 'package:immich_mobile/interfaces/auth_api.interface.dart'; import 'package:immich_mobile/interfaces/backup_album.interface.dart'; import 'package:immich_mobile/interfaces/etag.interface.dart'; -import 'package:immich_mobile/interfaces/exif_info.interface.dart'; import 'package:immich_mobile/interfaces/file_media.interface.dart'; import 'package:immich_mobile/interfaces/user.interface.dart'; import 'package:mocktail/mocktail.dart'; diff --git a/mobile/test/test_utils.dart b/mobile/test/test_utils.dart index 825d77190b..19b2d6e705 100644 --- a/mobile/test/test_utils.dart +++ b/mobile/test/test_utils.dart @@ -11,9 +11,9 @@ import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/entities/duplicated_asset.entity.dart'; import 'package:immich_mobile/entities/etag.entity.dart'; -import 'package:immich_mobile/entities/exif_info.entity.dart'; import 'package:immich_mobile/entities/ios_device_asset.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; import 'package:immich_mobile/infrastructure/entities/log.entity.dart'; import 'package:immich_mobile/infrastructure/entities/store.entity.dart'; import 'package:isar/isar.dart';