blob: 3476721ae61596e29c7f317b7f5c1ab7429ea00d [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <Flutter/Flutter.h>
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#import "FLTPHPickerSaveImageToPathOperation.h"
#import <os/log.h>
API_AVAILABLE(ios(14))
@interface FLTPHPickerSaveImageToPathOperation ()
@property(strong, nonatomic) PHPickerResult *result;
@property(strong, nonatomic) NSNumber *maxHeight;
@property(strong, nonatomic) NSNumber *maxWidth;
@property(strong, nonatomic) NSNumber *desiredImageQuality;
@property(assign, nonatomic) BOOL requestFullMetadata;
@end
@implementation FLTPHPickerSaveImageToPathOperation {
BOOL executing;
BOOL finished;
FLTGetSavedPath getSavedPath;
}
- (instancetype)initWithResult:(PHPickerResult *)result
maxHeight:(NSNumber *)maxHeight
maxWidth:(NSNumber *)maxWidth
desiredImageQuality:(NSNumber *)desiredImageQuality
fullMetadata:(BOOL)fullMetadata
savedPathBlock:(FLTGetSavedPath)savedPathBlock API_AVAILABLE(ios(14)) {
if (self = [super init]) {
if (result) {
self.result = result;
self.maxHeight = maxHeight;
self.maxWidth = maxWidth;
self.desiredImageQuality = desiredImageQuality;
self.requestFullMetadata = fullMetadata;
getSavedPath = savedPathBlock;
executing = NO;
finished = NO;
} else {
return nil;
}
return self;
} else {
return nil;
}
}
- (BOOL)isConcurrent {
return YES;
}
- (BOOL)isExecuting {
return executing;
}
- (BOOL)isFinished {
return finished;
}
- (void)setFinished:(BOOL)isFinished {
[self willChangeValueForKey:@"isFinished"];
self->finished = isFinished;
[self didChangeValueForKey:@"isFinished"];
}
- (void)setExecuting:(BOOL)isExecuting {
[self willChangeValueForKey:@"isExecuting"];
self->executing = isExecuting;
[self didChangeValueForKey:@"isExecuting"];
}
- (void)completeOperationWithPath:(NSString *)savedPath error:(FlutterError *)error {
getSavedPath(savedPath, error);
[self setExecuting:NO];
[self setFinished:YES];
}
- (void)start {
if ([self isCancelled]) {
[self setFinished:YES];
return;
}
if (@available(iOS 14, *)) {
[self setExecuting:YES];
// This supports uniform types that conform to UTTypeImage.
// This includes UTTypeHEIC, UTTypeHEIF, UTTypeLivePhoto, UTTypeICO, UTTypeICNS, UTTypePNG
// UTTypeGIF, UTTypeJPEG, UTTypeWebP, UTTypeTIFF, UTTypeBMP, UTTypeSVG, UTTypeRAWImage
if ([self.result.itemProvider hasItemConformingToTypeIdentifier:UTTypeImage.identifier]) {
[self.result.itemProvider
loadDataRepresentationForTypeIdentifier:UTTypeImage.identifier
completionHandler:^(NSData *_Nullable data,
NSError *_Nullable error) {
if (data != nil) {
[self processImage:data];
} else {
FlutterError *flutterError =
[FlutterError errorWithCode:@"invalid_image"
message:error.localizedDescription
details:error.domain];
[self completeOperationWithPath:nil error:flutterError];
}
}];
} else if ([self.result.itemProvider
// This supports uniform types that conform to UTTypeMovie.
// This includes kUTTypeVideo, kUTTypeMPEG4, public.3gpp, kUTTypeMPEG,
// public.3gpp2, public.avi, kUTTypeQuickTimeMovie.
hasItemConformingToTypeIdentifier:UTTypeMovie.identifier]) {
[self processVideo];
} else {
FlutterError *flutterError = [FlutterError errorWithCode:@"invalid_source"
message:@"Invalid media source."
details:nil];
[self completeOperationWithPath:nil error:flutterError];
}
} else {
[self setFinished:YES];
}
}
/**
* Processes the image.
*/
- (void)processImage:(NSData *)pickerImageData API_AVAILABLE(ios(14)) {
UIImage *localImage = [[UIImage alloc] initWithData:pickerImageData];
PHAsset *originalAsset;
// Only if requested, fetch the full "PHAsset" metadata, which requires "Photo Library Usage"
// permissions.
if (self.requestFullMetadata) {
originalAsset = [FLTImagePickerPhotoAssetUtil getAssetFromPHPickerResult:self.result];
}
if (self.maxWidth != nil || self.maxHeight != nil) {
localImage = [FLTImagePickerImageUtil scaledImage:localImage
maxWidth:self.maxWidth
maxHeight:self.maxHeight
isMetadataAvailable:YES];
}
if (originalAsset) {
void (^resultHandler)(NSData *imageData, NSString *dataUTI, NSDictionary *info) =
^(NSData *_Nullable imageData, NSString *_Nullable dataUTI, NSDictionary *_Nullable info) {
// maxWidth and maxHeight are used only for GIF images.
NSString *savedPath = [FLTImagePickerPhotoAssetUtil
saveImageWithOriginalImageData:imageData
image:localImage
maxWidth:self.maxWidth
maxHeight:self.maxHeight
imageQuality:self.desiredImageQuality];
[self completeOperationWithPath:savedPath error:nil];
};
if (@available(iOS 13.0, *)) {
[[PHImageManager defaultManager]
requestImageDataAndOrientationForAsset:originalAsset
options:nil
resultHandler:^(NSData *_Nullable imageData,
NSString *_Nullable dataUTI,
CGImagePropertyOrientation orientation,
NSDictionary *_Nullable info) {
resultHandler(imageData, dataUTI, info);
}];
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[[PHImageManager defaultManager]
requestImageDataForAsset:originalAsset
options:nil
resultHandler:^(NSData *_Nullable imageData, NSString *_Nullable dataUTI,
UIImageOrientation orientation, NSDictionary *_Nullable info) {
resultHandler(imageData, dataUTI, info);
}];
#pragma clang diagnostic pop
}
} else {
// Image picked without an original asset (e.g. User pick image without permission)
// maxWidth and maxHeight are used only for GIF images.
NSString *savedPath =
[FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:pickerImageData
image:localImage
maxWidth:self.maxWidth
maxHeight:self.maxHeight
imageQuality:self.desiredImageQuality];
[self completeOperationWithPath:savedPath error:nil];
}
}
/**
* Processes the video.
*/
- (void)processVideo API_AVAILABLE(ios(14)) {
NSString *typeIdentifier = self.result.itemProvider.registeredTypeIdentifiers.firstObject;
[self.result.itemProvider
loadFileRepresentationForTypeIdentifier:typeIdentifier
completionHandler:^(NSURL *_Nullable videoURL,
NSError *_Nullable error) {
if (error != nil) {
FlutterError *flutterError =
[FlutterError errorWithCode:@"invalid_image"
message:error.localizedDescription
details:error.domain];
[self completeOperationWithPath:nil error:flutterError];
return;
}
NSURL *destination =
[FLTImagePickerPhotoAssetUtil saveVideoFromURL:videoURL];
if (destination == nil) {
[self
completeOperationWithPath:nil
error:[FlutterError
errorWithCode:
@"flutter_image_picker_copy_"
@"video_error"
message:@"Could not cache "
@"the video file."
details:nil]];
return;
}
[self completeOperationWithPath:[destination path] error:nil];
}];
}
@end