2022-07-13 12:23:48 +00:00
|
|
|
import 'dart:async';
|
2024-12-04 21:03:46 +00:00
|
|
|
import 'dart:io';
|
2022-02-03 16:06:44 +00:00
|
|
|
|
2024-08-26 18:21:19 +00:00
|
|
|
import 'package:collection/collection.dart';
|
2022-02-03 16:06:44 +00:00
|
|
|
import 'package:flutter/material.dart';
|
2022-06-25 18:46:51 +00:00
|
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
2024-05-01 02:36:40 +00:00
|
|
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
2024-09-30 14:37:30 +00:00
|
|
|
import 'package:immich_mobile/entities/backup_album.entity.dart';
|
2024-05-01 02:36:40 +00:00
|
|
|
import 'package:immich_mobile/entities/user.entity.dart';
|
2024-09-30 14:37:30 +00:00
|
|
|
import 'package:immich_mobile/interfaces/asset.interface.dart';
|
2024-09-24 06:24:48 +00:00
|
|
|
import 'package:immich_mobile/interfaces/asset_api.interface.dart';
|
2024-09-30 14:37:30 +00:00
|
|
|
import 'package:immich_mobile/interfaces/backup.interface.dart';
|
|
|
|
import 'package:immich_mobile/interfaces/etag.interface.dart';
|
2024-09-24 06:24:48 +00:00
|
|
|
import 'package:immich_mobile/interfaces/exif_info.interface.dart';
|
2024-09-30 14:37:30 +00:00
|
|
|
import 'package:immich_mobile/interfaces/user.interface.dart';
|
2024-08-26 18:21:19 +00:00
|
|
|
import 'package:immich_mobile/models/backup/backup_candidate.model.dart';
|
2024-05-02 20:59:14 +00:00
|
|
|
import 'package:immich_mobile/providers/api.provider.dart';
|
2024-09-30 14:37:30 +00:00
|
|
|
import 'package:immich_mobile/repositories/asset.repository.dart';
|
2024-09-24 06:24:48 +00:00
|
|
|
import 'package:immich_mobile/repositories/asset_api.repository.dart';
|
2024-09-30 14:37:30 +00:00
|
|
|
import 'package:immich_mobile/repositories/backup.repository.dart';
|
|
|
|
import 'package:immich_mobile/repositories/etag.repository.dart';
|
2024-09-24 06:24:48 +00:00
|
|
|
import 'package:immich_mobile/repositories/exif_info.repository.dart';
|
2024-09-30 14:37:30 +00:00
|
|
|
import 'package:immich_mobile/repositories/user.repository.dart';
|
2024-08-26 18:21:19 +00:00
|
|
|
import 'package:immich_mobile/services/album.service.dart';
|
2024-05-02 20:59:14 +00:00
|
|
|
import 'package:immich_mobile/services/api.service.dart';
|
2024-08-26 18:21:19 +00:00
|
|
|
import 'package:immich_mobile/services/backup.service.dart';
|
2024-05-02 20:59:14 +00:00
|
|
|
import 'package:immich_mobile/services/sync.service.dart';
|
2024-05-14 15:35:37 +00:00
|
|
|
import 'package:immich_mobile/services/user.service.dart';
|
2022-11-30 16:58:07 +00:00
|
|
|
import 'package:logging/logging.dart';
|
2024-01-15 15:26:13 +00:00
|
|
|
import 'package:maplibre_gl/maplibre_gl.dart';
|
2022-07-13 12:23:48 +00:00
|
|
|
import 'package:openapi/api.dart';
|
2022-02-03 16:06:44 +00:00
|
|
|
|
2022-07-13 12:23:48 +00:00
|
|
|
final assetServiceProvider = Provider(
|
|
|
|
(ref) => AssetService(
|
2024-09-24 06:24:48 +00:00
|
|
|
ref.watch(assetApiRepositoryProvider),
|
2024-09-30 14:37:30 +00:00
|
|
|
ref.watch(assetRepositoryProvider),
|
2024-09-24 06:24:48 +00:00
|
|
|
ref.watch(exifInfoRepositoryProvider),
|
2024-09-30 14:37:30 +00:00
|
|
|
ref.watch(userRepositoryProvider),
|
|
|
|
ref.watch(etagRepositoryProvider),
|
|
|
|
ref.watch(backupRepositoryProvider),
|
2022-07-13 12:23:48 +00:00
|
|
|
ref.watch(apiServiceProvider),
|
2023-03-03 22:38:30 +00:00
|
|
|
ref.watch(syncServiceProvider),
|
2024-05-14 15:35:37 +00:00
|
|
|
ref.watch(userServiceProvider),
|
2024-08-26 18:21:19 +00:00
|
|
|
ref.watch(backupServiceProvider),
|
|
|
|
ref.watch(albumServiceProvider),
|
2022-07-13 12:23:48 +00:00
|
|
|
),
|
|
|
|
);
|
2022-06-25 18:46:51 +00:00
|
|
|
|
2022-02-03 16:06:44 +00:00
|
|
|
class AssetService {
|
2024-09-24 06:24:48 +00:00
|
|
|
final IAssetApiRepository _assetApiRepository;
|
2024-09-30 14:37:30 +00:00
|
|
|
final IAssetRepository _assetRepository;
|
2024-09-24 06:24:48 +00:00
|
|
|
final IExifInfoRepository _exifInfoRepository;
|
2024-09-30 14:37:30 +00:00
|
|
|
final IUserRepository _userRepository;
|
|
|
|
final IETagRepository _etagRepository;
|
|
|
|
final IBackupRepository _backupRepository;
|
2022-07-13 12:23:48 +00:00
|
|
|
final ApiService _apiService;
|
2023-03-03 22:38:30 +00:00
|
|
|
final SyncService _syncService;
|
2024-05-14 15:35:37 +00:00
|
|
|
final UserService _userService;
|
2024-08-26 18:21:19 +00:00
|
|
|
final BackupService _backupService;
|
|
|
|
final AlbumService _albumService;
|
2022-11-30 16:58:07 +00:00
|
|
|
final log = Logger('AssetService');
|
2022-02-03 16:06:44 +00:00
|
|
|
|
2023-03-03 22:38:30 +00:00
|
|
|
AssetService(
|
2024-09-24 06:24:48 +00:00
|
|
|
this._assetApiRepository,
|
2024-09-30 14:37:30 +00:00
|
|
|
this._assetRepository,
|
2024-09-24 06:24:48 +00:00
|
|
|
this._exifInfoRepository,
|
2024-09-30 14:37:30 +00:00
|
|
|
this._userRepository,
|
|
|
|
this._etagRepository,
|
|
|
|
this._backupRepository,
|
2023-03-03 22:38:30 +00:00
|
|
|
this._apiService,
|
|
|
|
this._syncService,
|
2024-05-14 15:35:37 +00:00
|
|
|
this._userService,
|
2024-08-26 18:21:19 +00:00
|
|
|
this._backupService,
|
|
|
|
this._albumService,
|
2023-03-03 22:38:30 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
/// Checks the server for updated assets and updates the local database if
|
|
|
|
/// required. Returns `true` if there were any changes.
|
2024-05-14 15:35:37 +00:00
|
|
|
Future<bool> refreshRemoteAssets() async {
|
2024-09-30 14:37:30 +00:00
|
|
|
final syncedUserIds = await _etagRepository.getAllIds();
|
2024-05-14 15:35:37 +00:00
|
|
|
final List<User> syncedUsers = syncedUserIds.isEmpty
|
|
|
|
? []
|
2024-09-30 14:37:30 +00:00
|
|
|
: await _userRepository.getByIds(syncedUserIds);
|
2023-03-03 22:38:30 +00:00
|
|
|
final Stopwatch sw = Stopwatch()..start();
|
2023-03-27 02:35:52 +00:00
|
|
|
final bool changes = await _syncService.syncRemoteAssetsToDb(
|
2024-05-14 15:35:37 +00:00
|
|
|
users: syncedUsers,
|
|
|
|
getChangedAssets: _getRemoteAssetChanges,
|
|
|
|
loadAssets: _getRemoteAssets,
|
|
|
|
refreshUsers: _userService.getUsersFromServer,
|
2023-03-27 02:35:52 +00:00
|
|
|
);
|
2023-03-03 22:38:30 +00:00
|
|
|
debugPrint("refreshRemoteAssets full took ${sw.elapsedMilliseconds}ms");
|
|
|
|
return changes;
|
|
|
|
}
|
2022-02-07 02:31:32 +00:00
|
|
|
|
2023-09-10 12:51:18 +00:00
|
|
|
/// Returns `(null, null)` if changes are invalid -> requires full sync
|
|
|
|
Future<(List<Asset>? toUpsert, List<String>? toDelete)>
|
2024-05-14 15:35:37 +00:00
|
|
|
_getRemoteAssetChanges(List<User> users, DateTime since) async {
|
|
|
|
final dto = AssetDeltaSyncDto(
|
|
|
|
updatedAfter: since,
|
|
|
|
userIds: users.map((e) => e.id).toList(),
|
|
|
|
);
|
|
|
|
final changes = await _apiService.syncApi.getDeltaSync(dto);
|
|
|
|
return changes == null || changes.needsFullSync
|
|
|
|
? (null, null)
|
|
|
|
: (changes.upserted.map(Asset.remote).toList(), changes.deleted);
|
2023-09-10 12:51:18 +00:00
|
|
|
}
|
|
|
|
|
2024-03-06 16:15:54 +00:00
|
|
|
/// Returns the list of people of the given asset id.
|
|
|
|
// If the server is not reachable `null` is returned.
|
|
|
|
Future<List<PersonWithFacesResponseDto>?> getRemotePeopleOfAsset(
|
|
|
|
String remoteId,
|
|
|
|
) async {
|
|
|
|
try {
|
|
|
|
final AssetResponseDto? dto =
|
2024-05-29 22:26:57 +00:00
|
|
|
await _apiService.assetsApi.getAssetInfo(remoteId);
|
2024-03-06 16:15:54 +00:00
|
|
|
|
|
|
|
return dto?.people;
|
|
|
|
} catch (error, stack) {
|
|
|
|
log.severe(
|
|
|
|
'Error while getting remote asset info: ${error.toString()}',
|
|
|
|
error,
|
|
|
|
stack,
|
|
|
|
);
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-26 16:16:02 +00:00
|
|
|
/// Returns `null` if the server state did not change, else list of assets
|
2024-05-14 15:35:37 +00:00
|
|
|
Future<List<Asset>?> _getRemoteAssets(User user, DateTime until) async {
|
2024-02-18 18:22:25 +00:00
|
|
|
const int chunkSize = 10000;
|
2022-11-30 16:58:07 +00:00
|
|
|
try {
|
2023-11-06 17:40:43 +00:00
|
|
|
final List<Asset> allAssets = [];
|
2024-05-14 15:35:37 +00:00
|
|
|
String? lastId;
|
|
|
|
// will break on error or once all assets are loaded
|
|
|
|
while (true) {
|
|
|
|
final dto = AssetFullSyncDto(
|
|
|
|
limit: chunkSize,
|
|
|
|
updatedUntil: until,
|
|
|
|
lastId: lastId,
|
2023-11-06 17:40:43 +00:00
|
|
|
userId: user.id,
|
|
|
|
);
|
2024-06-09 19:19:28 +00:00
|
|
|
log.fine("Requesting $chunkSize assets from $lastId");
|
2024-05-14 15:35:37 +00:00
|
|
|
final List<AssetResponseDto>? assets =
|
|
|
|
await _apiService.syncApi.getFullSyncForUser(dto);
|
|
|
|
if (assets == null) return null;
|
2024-06-09 19:19:28 +00:00
|
|
|
log.fine(
|
|
|
|
"Received ${assets.length} assets from ${assets.firstOrNull?.id} to ${assets.lastOrNull?.id}",
|
|
|
|
);
|
2023-11-06 17:40:43 +00:00
|
|
|
allAssets.addAll(assets.map(Asset.remote));
|
2024-06-09 19:19:28 +00:00
|
|
|
if (assets.length != chunkSize) break;
|
2024-05-14 15:35:37 +00:00
|
|
|
lastId = assets.last.id;
|
2023-03-03 22:38:30 +00:00
|
|
|
}
|
2023-11-06 17:40:43 +00:00
|
|
|
return allAssets;
|
2023-06-25 23:59:35 +00:00
|
|
|
} catch (error, stack) {
|
2024-05-14 15:35:37 +00:00
|
|
|
log.severe('Error while getting remote assets', error, stack);
|
2023-03-03 22:38:30 +00:00
|
|
|
return null;
|
2022-02-13 21:10:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-06 07:01:14 +00:00
|
|
|
Future<bool> deleteAssets(
|
|
|
|
Iterable<Asset> deleteAssets, {
|
|
|
|
bool? force = false,
|
|
|
|
}) async {
|
2022-02-13 21:10:42 +00:00
|
|
|
try {
|
2022-11-08 17:00:24 +00:00
|
|
|
final List<String> payload = [];
|
2022-02-13 21:10:42 +00:00
|
|
|
|
2022-11-08 17:00:24 +00:00
|
|
|
for (final asset in deleteAssets) {
|
2023-02-04 20:42:42 +00:00
|
|
|
payload.add(asset.remoteId!);
|
2022-02-13 21:10:42 +00:00
|
|
|
}
|
|
|
|
|
2024-05-29 22:26:57 +00:00
|
|
|
await _apiService.assetsApi.deleteAssets(
|
2023-10-06 07:01:14 +00:00
|
|
|
AssetBulkDeleteDto(
|
|
|
|
ids: payload,
|
|
|
|
force: force,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
return true;
|
2023-06-25 23:59:35 +00:00
|
|
|
} catch (error, stack) {
|
2024-02-24 03:38:57 +00:00
|
|
|
log.severe("Error while deleting assets", error, stack);
|
2022-02-11 02:40:11 +00:00
|
|
|
}
|
2023-10-06 07:01:14 +00:00
|
|
|
return false;
|
2022-02-11 02:40:11 +00:00
|
|
|
}
|
2023-02-05 03:25:11 +00:00
|
|
|
|
2023-03-03 22:38:30 +00:00
|
|
|
/// Loads the exif information from the database. If there is none, loads
|
|
|
|
/// the exif info from the server (remote assets only)
|
|
|
|
Future<Asset> loadExif(Asset a) async {
|
2024-09-30 14:37:30 +00:00
|
|
|
a.exifInfo ??= await _exifInfoRepository.get(a.id);
|
2023-05-17 17:36:02 +00:00
|
|
|
// fileSize is always filled on the server but not set on client
|
|
|
|
if (a.exifInfo?.fileSize == null) {
|
2023-03-03 22:38:30 +00:00
|
|
|
if (a.isRemote) {
|
2024-05-29 22:26:57 +00:00
|
|
|
final dto = await _apiService.assetsApi.getAssetInfo(a.remoteId!);
|
2023-03-03 22:38:30 +00:00
|
|
|
if (dto != null && dto.exifInfo != null) {
|
2023-05-17 17:36:02 +00:00
|
|
|
final newExif = Asset.remote(dto).exifInfo!.copyWith(id: a.id);
|
2024-08-09 13:43:47 +00:00
|
|
|
a.exifInfo = newExif;
|
2023-05-17 17:36:02 +00:00
|
|
|
if (newExif != a.exifInfo) {
|
|
|
|
if (a.isInDb) {
|
2024-09-30 14:37:30 +00:00
|
|
|
_assetRepository.transaction(() => _assetRepository.update(a));
|
2023-05-17 17:36:02 +00:00
|
|
|
} else {
|
|
|
|
debugPrint("[loadExif] parameter Asset is not from DB!");
|
|
|
|
}
|
2023-03-03 22:38:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// TODO implement local exif info parsing
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return a;
|
|
|
|
}
|
|
|
|
|
2024-04-27 18:10:27 +00:00
|
|
|
Future<void> updateAssets(
|
2023-05-17 17:36:02 +00:00
|
|
|
List<Asset> assets,
|
2023-02-09 17:32:08 +00:00
|
|
|
UpdateAssetDto updateAssetDto,
|
|
|
|
) async {
|
2024-05-29 22:26:57 +00:00
|
|
|
return await _apiService.assetsApi.updateAssets(
|
2024-04-27 18:10:27 +00:00
|
|
|
AssetBulkUpdateDto(
|
|
|
|
ids: assets.map((e) => e.remoteId!).toList(),
|
|
|
|
dateTimeOriginal: updateAssetDto.dateTimeOriginal,
|
|
|
|
isFavorite: updateAssetDto.isFavorite,
|
|
|
|
isArchived: updateAssetDto.isArchived,
|
|
|
|
latitude: updateAssetDto.latitude,
|
|
|
|
longitude: updateAssetDto.longitude,
|
2023-05-17 17:36:02 +00:00
|
|
|
),
|
|
|
|
);
|
2023-02-05 03:25:11 +00:00
|
|
|
}
|
|
|
|
|
2024-09-30 14:37:30 +00:00
|
|
|
Future<List<Asset>> changeFavoriteStatus(
|
2023-05-17 17:36:02 +00:00
|
|
|
List<Asset> assets,
|
|
|
|
bool isFavorite,
|
2024-04-27 18:10:27 +00:00
|
|
|
) async {
|
|
|
|
try {
|
|
|
|
await updateAssets(assets, UpdateAssetDto(isFavorite: isFavorite));
|
|
|
|
|
|
|
|
for (var element in assets) {
|
|
|
|
element.isFavorite = isFavorite;
|
|
|
|
}
|
|
|
|
|
|
|
|
await _syncService.upsertAssetsWithExif(assets);
|
|
|
|
|
|
|
|
return assets;
|
|
|
|
} catch (error, stack) {
|
|
|
|
log.severe("Error while changing favorite status", error, stack);
|
2024-09-30 14:37:30 +00:00
|
|
|
return [];
|
2024-04-27 18:10:27 +00:00
|
|
|
}
|
2023-02-05 03:25:11 +00:00
|
|
|
}
|
2023-04-17 05:02:07 +00:00
|
|
|
|
2024-09-30 14:37:30 +00:00
|
|
|
Future<List<Asset>> changeArchiveStatus(
|
2024-04-27 18:10:27 +00:00
|
|
|
List<Asset> assets,
|
|
|
|
bool isArchived,
|
|
|
|
) async {
|
|
|
|
try {
|
|
|
|
await updateAssets(assets, UpdateAssetDto(isArchived: isArchived));
|
|
|
|
|
|
|
|
for (var element in assets) {
|
|
|
|
element.isArchived = isArchived;
|
|
|
|
}
|
|
|
|
|
|
|
|
await _syncService.upsertAssetsWithExif(assets);
|
|
|
|
|
|
|
|
return assets;
|
|
|
|
} catch (error, stack) {
|
|
|
|
log.severe("Error while changing archive status", error, stack);
|
2024-09-30 14:37:30 +00:00
|
|
|
return [];
|
2024-04-27 18:10:27 +00:00
|
|
|
}
|
2023-04-17 05:02:07 +00:00
|
|
|
}
|
2023-12-05 19:34:37 +00:00
|
|
|
|
2024-09-30 14:37:30 +00:00
|
|
|
Future<List<Asset>?> changeDateTime(
|
2023-12-05 19:34:37 +00:00
|
|
|
List<Asset> assets,
|
|
|
|
String updatedDt,
|
2024-04-27 18:10:27 +00:00
|
|
|
) async {
|
|
|
|
try {
|
|
|
|
await updateAssets(
|
|
|
|
assets,
|
|
|
|
UpdateAssetDto(dateTimeOriginal: updatedDt),
|
|
|
|
);
|
|
|
|
|
|
|
|
for (var element in assets) {
|
|
|
|
element.fileCreatedAt = DateTime.parse(updatedDt);
|
|
|
|
element.exifInfo?.dateTimeOriginal = DateTime.parse(updatedDt);
|
|
|
|
}
|
|
|
|
|
|
|
|
await _syncService.upsertAssetsWithExif(assets);
|
|
|
|
|
|
|
|
return assets;
|
|
|
|
} catch (error, stack) {
|
|
|
|
log.severe("Error while changing date/time status", error, stack);
|
|
|
|
return Future.value(null);
|
|
|
|
}
|
2023-12-05 19:34:37 +00:00
|
|
|
}
|
|
|
|
|
2024-09-30 14:37:30 +00:00
|
|
|
Future<List<Asset>?> changeLocation(
|
2023-12-05 19:34:37 +00:00
|
|
|
List<Asset> assets,
|
|
|
|
LatLng location,
|
2024-04-27 18:10:27 +00:00
|
|
|
) async {
|
|
|
|
try {
|
|
|
|
await updateAssets(
|
|
|
|
assets,
|
|
|
|
UpdateAssetDto(
|
|
|
|
latitude: location.latitude,
|
|
|
|
longitude: location.longitude,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
|
|
|
for (var element in assets) {
|
|
|
|
element.exifInfo?.lat = location.latitude;
|
|
|
|
element.exifInfo?.long = location.longitude;
|
|
|
|
}
|
|
|
|
|
|
|
|
await _syncService.upsertAssetsWithExif(assets);
|
|
|
|
|
|
|
|
return assets;
|
|
|
|
} catch (error, stack) {
|
|
|
|
log.severe("Error while changing location status", error, stack);
|
|
|
|
return Future.value(null);
|
|
|
|
}
|
2023-12-05 19:34:37 +00:00
|
|
|
}
|
2024-08-26 18:21:19 +00:00
|
|
|
|
|
|
|
Future<void> syncUploadedAssetToAlbums() async {
|
|
|
|
try {
|
2024-09-30 14:37:30 +00:00
|
|
|
final selectedAlbums =
|
|
|
|
await _backupRepository.getAllBySelection(BackupSelection.select);
|
|
|
|
final excludedAlbums =
|
|
|
|
await _backupRepository.getAllBySelection(BackupSelection.exclude);
|
2024-08-26 18:21:19 +00:00
|
|
|
|
|
|
|
final candidates = await _backupService.buildUploadCandidates(
|
|
|
|
selectedAlbums,
|
|
|
|
excludedAlbums,
|
|
|
|
useTimeFilter: false,
|
|
|
|
);
|
|
|
|
|
|
|
|
await refreshRemoteAssets();
|
2024-09-30 14:37:30 +00:00
|
|
|
final owner = await _userRepository.me();
|
|
|
|
final remoteAssets = await _assetRepository.getAll(
|
|
|
|
ownerId: owner.isarId,
|
|
|
|
state: AssetState.merged,
|
|
|
|
);
|
2024-08-26 18:21:19 +00:00
|
|
|
|
|
|
|
/// Map<AlbumName, [AssetId]>
|
|
|
|
Map<String, List<String>> assetToAlbums = {};
|
|
|
|
|
|
|
|
for (BackupCandidate candidate in candidates) {
|
|
|
|
final asset = remoteAssets.firstWhereOrNull(
|
2024-09-18 15:15:52 +00:00
|
|
|
(a) => a.localId == candidate.asset.localId,
|
2024-08-26 18:21:19 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
if (asset != null) {
|
|
|
|
for (final albumName in candidate.albumNames) {
|
|
|
|
assetToAlbums.putIfAbsent(albumName, () => []).add(asset.remoteId!);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Upload assets to albums
|
|
|
|
for (final entry in assetToAlbums.entries) {
|
|
|
|
final albumName = entry.key;
|
|
|
|
final assetIds = entry.value;
|
|
|
|
|
|
|
|
await _albumService.syncUploadAlbums([albumName], assetIds);
|
|
|
|
}
|
|
|
|
} catch (error, stack) {
|
|
|
|
log.severe("Error while syncing uploaded asset to albums", error, stack);
|
|
|
|
}
|
|
|
|
}
|
2024-09-24 06:24:48 +00:00
|
|
|
|
|
|
|
Future<void> setDescription(
|
|
|
|
Asset asset,
|
|
|
|
String newDescription,
|
|
|
|
) async {
|
|
|
|
final remoteAssetId = asset.remoteId;
|
|
|
|
final localExifId = asset.exifInfo?.id;
|
|
|
|
|
|
|
|
// Guard [remoteAssetId] and [localExifId] null
|
|
|
|
if (remoteAssetId == null || localExifId == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
final result = await _assetApiRepository.update(
|
|
|
|
remoteAssetId,
|
|
|
|
description: newDescription,
|
|
|
|
);
|
|
|
|
|
|
|
|
final description = result.exifInfo?.description;
|
|
|
|
|
|
|
|
if (description != null) {
|
|
|
|
var exifInfo = await _exifInfoRepository.get(localExifId);
|
|
|
|
|
|
|
|
if (exifInfo != null) {
|
|
|
|
exifInfo.description = description;
|
|
|
|
await _exifInfoRepository.update(exifInfo);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<String> getDescription(Asset asset) async {
|
|
|
|
final localExifId = asset.exifInfo?.id;
|
|
|
|
|
|
|
|
// Guard [remoteAssetId] and [localExifId] null
|
|
|
|
if (localExifId == null) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
final exifInfo = await _exifInfoRepository.get(localExifId);
|
|
|
|
|
|
|
|
return exifInfo?.description ?? "";
|
|
|
|
}
|
2024-12-04 21:03:46 +00:00
|
|
|
|
|
|
|
Future<double> getAspectRatio(Asset asset) async {
|
|
|
|
// platform_manager always returns 0 for orientation on iOS, so only prefer it on Android
|
|
|
|
if (asset.isLocal && Platform.isAndroid) {
|
|
|
|
await asset.localAsync;
|
|
|
|
} else if (asset.isRemote) {
|
|
|
|
asset = await loadExif(asset);
|
|
|
|
} else if (asset.isLocal) {
|
|
|
|
await asset.localAsync;
|
|
|
|
}
|
|
|
|
|
|
|
|
final aspectRatio = asset.aspectRatio;
|
|
|
|
if (aspectRatio != null) {
|
|
|
|
return aspectRatio;
|
|
|
|
}
|
|
|
|
|
|
|
|
final width = asset.width;
|
|
|
|
final height = asset.height;
|
|
|
|
if (width != null && height != null) {
|
|
|
|
// we don't know the orientation, so assume it's normal
|
|
|
|
return width / height;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1.0;
|
|
|
|
}
|
2022-02-03 16:06:44 +00:00
|
|
|
}
|