mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 02:27:08 -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