mirror of
https://github.com/immich-app/immich.git
synced 2025-05-24 02:13:51 -04:00
fix(mobile): live / motion photo download (#5607)
* reverts: 5566 * fix: stitch livePhoto only in iOS * fix: PMProgressHandler only on iOS * ios: fallback to saving image if livephoto fails --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
parent
33529d1d9b
commit
960b68b02f
@ -19,9 +19,11 @@ class ImageViewerService {
|
|||||||
ImageViewerService(this._apiService);
|
ImageViewerService(this._apiService);
|
||||||
|
|
||||||
Future<bool> downloadAssetToDevice(Asset asset) async {
|
Future<bool> downloadAssetToDevice(Asset asset) async {
|
||||||
|
File? imageFile;
|
||||||
|
File? videoFile;
|
||||||
try {
|
try {
|
||||||
// Download LivePhotos image and motion part
|
// Download LivePhotos image and motion part
|
||||||
if (asset.isImage && asset.livePhotoVideoId != null) {
|
if (asset.isImage && asset.livePhotoVideoId != null && Platform.isIOS) {
|
||||||
var imageResponse = await _apiService.assetApi.downloadFileWithHttpInfo(
|
var imageResponse = await _apiService.assetApi.downloadFileWithHttpInfo(
|
||||||
asset.remoteId!,
|
asset.remoteId!,
|
||||||
);
|
);
|
||||||
@ -40,11 +42,11 @@ class ImageViewerService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final AssetEntity? entity;
|
AssetEntity? entity;
|
||||||
|
|
||||||
final tempDir = await getTemporaryDirectory();
|
final tempDir = await getTemporaryDirectory();
|
||||||
File videoFile = await File('${tempDir.path}/livephoto.mov').create();
|
videoFile = await File('${tempDir.path}/livephoto.mov').create();
|
||||||
File imageFile = await File('${tempDir.path}/livephoto.heic').create();
|
imageFile = await File('${tempDir.path}/livephoto.heic').create();
|
||||||
videoFile.writeAsBytesSync(motionReponse.bodyBytes);
|
videoFile.writeAsBytesSync(motionReponse.bodyBytes);
|
||||||
imageFile.writeAsBytesSync(imageResponse.bodyBytes);
|
imageFile.writeAsBytesSync(imageResponse.bodyBytes);
|
||||||
|
|
||||||
@ -54,6 +56,17 @@ class ImageViewerService {
|
|||||||
title: asset.fileName,
|
title: asset.fileName,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (entity == null) {
|
||||||
|
_log.warning(
|
||||||
|
"Asset cannot be saved as a live photo. This is most likely a motion photo. Saving only the image file",
|
||||||
|
);
|
||||||
|
|
||||||
|
entity = await PhotoManager.editor.saveImage(
|
||||||
|
imageResponse.bodyBytes,
|
||||||
|
title: asset.fileName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return entity != null;
|
return entity != null;
|
||||||
} else {
|
} else {
|
||||||
var res = await _apiService.assetApi
|
var res = await _apiService.assetApi
|
||||||
@ -75,17 +88,20 @@ class ImageViewerService {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final tempDir = await getTemporaryDirectory();
|
final tempDir = await getTemporaryDirectory();
|
||||||
File tempFile =
|
videoFile = await File('${tempDir.path}/${asset.fileName}').create();
|
||||||
await File('${tempDir.path}/${asset.fileName}').create();
|
videoFile.writeAsBytesSync(res.bodyBytes);
|
||||||
tempFile.writeAsBytesSync(res.bodyBytes);
|
|
||||||
entity = await PhotoManager.editor
|
entity = await PhotoManager.editor
|
||||||
.saveVideo(tempFile, title: asset.fileName);
|
.saveVideo(videoFile, title: asset.fileName);
|
||||||
}
|
}
|
||||||
return entity != null;
|
return entity != null;
|
||||||
}
|
}
|
||||||
} catch (error, stack) {
|
} catch (error, stack) {
|
||||||
_log.severe("Error saving file ${error.toString()}", error, stack);
|
_log.severe("Error saving file ${error.toString()}", error, stack);
|
||||||
return false;
|
return false;
|
||||||
|
} finally {
|
||||||
|
// Clear temp files
|
||||||
|
imageFile?.delete();
|
||||||
|
videoFile?.delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -453,7 +453,7 @@ class BackgroundService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
_cancellationToken = CancellationToken();
|
_cancellationToken = CancellationToken();
|
||||||
final pmProgressHandler = PMProgressHandler();
|
final pmProgressHandler = Platform.isIOS ? PMProgressHandler() : null;
|
||||||
|
|
||||||
final bool ok = await backupService.backupAsset(
|
final bool ok = await backupService.backupAsset(
|
||||||
toUpload,
|
toUpload,
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:cancellation_token_http/http.dart';
|
import 'package:cancellation_token_http/http.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
@ -447,9 +449,9 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
// Perform Backup
|
// Perform Backup
|
||||||
state = state.copyWith(cancelToken: CancellationToken());
|
state = state.copyWith(cancelToken: CancellationToken());
|
||||||
|
|
||||||
final pmProgressHandler = PMProgressHandler();
|
final pmProgressHandler = Platform.isIOS ? PMProgressHandler() : null;
|
||||||
|
|
||||||
pmProgressHandler.stream.listen((event) {
|
pmProgressHandler?.stream.listen((event) {
|
||||||
final double progress = event.progress;
|
final double progress = event.progress;
|
||||||
state = state.copyWith(iCloudDownloadProgress: progress);
|
state = state.copyWith(iCloudDownloadProgress: progress);
|
||||||
});
|
});
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:cancellation_token_http/http.dart';
|
import 'package:cancellation_token_http/http.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
@ -208,7 +210,7 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
|
|||||||
state.totalAssetsToUpload == 1;
|
state.totalAssetsToUpload == 1;
|
||||||
state =
|
state =
|
||||||
state.copyWith(showDetailedNotification: showDetailedNotification);
|
state.copyWith(showDetailedNotification: showDetailedNotification);
|
||||||
final pmProgressHandler = PMProgressHandler();
|
final pmProgressHandler = Platform.isIOS ? PMProgressHandler() : null;
|
||||||
|
|
||||||
final bool ok = await ref.read(backupServiceProvider).backupAsset(
|
final bool ok = await ref.read(backupServiceProvider).backupAsset(
|
||||||
allUploadAssets,
|
allUploadAssets,
|
||||||
|
@ -206,7 +206,7 @@ class BackupService {
|
|||||||
Future<bool> backupAsset(
|
Future<bool> backupAsset(
|
||||||
Iterable<AssetEntity> assetList,
|
Iterable<AssetEntity> assetList,
|
||||||
http.CancellationToken cancelToken,
|
http.CancellationToken cancelToken,
|
||||||
PMProgressHandler pmProgressHandler,
|
PMProgressHandler? pmProgressHandler,
|
||||||
Function(String, String, bool) uploadSuccessCb,
|
Function(String, String, bool) uploadSuccessCb,
|
||||||
Function(int, int) uploadProgressCb,
|
Function(int, int) uploadProgressCb,
|
||||||
Function(CurrentUploadAsset) setCurrentUploadAssetCb,
|
Function(CurrentUploadAsset) setCurrentUploadAssetCb,
|
||||||
|
@ -3,7 +3,6 @@ import 'dart:math';
|
|||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
|
||||||
import 'package:immich_mobile/shared/models/store.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/models/user.dart';
|
import 'package:immich_mobile/shared/models/user.dart';
|
||||||
import 'package:immich_mobile/shared/ui/transparent_image.dart';
|
import 'package:immich_mobile/shared/ui/transparent_image.dart';
|
||||||
@ -35,11 +34,10 @@ class UserCircleAvatar extends ConsumerWidget {
|
|||||||
color: isDarkTheme && user.avatarColor == AvatarColorEnum.primary
|
color: isDarkTheme && user.avatarColor == AvatarColorEnum.primary
|
||||||
? Colors.black
|
? Colors.black
|
||||||
: Colors.white,
|
: Colors.white,
|
||||||
backgroundColor: user.avatarColor.toColor(),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return CircleAvatar(
|
return CircleAvatar(
|
||||||
backgroundColor: context.primaryColor,
|
backgroundColor: user.avatarColor.toColor(),
|
||||||
radius: radius,
|
radius: radius,
|
||||||
child: user.profileImagePath.isEmpty
|
child: user.profileImagePath.isEmpty
|
||||||
? textIcon
|
? textIcon
|
||||||
|
Loading…
x
Reference in New Issue
Block a user