BlueWallet/scripts/maccatalystpatches/ImagePickerManager.patch

619 lines
25 KiB
Diff

--- ImagePickerManager.m 1985-10-26 04:15:00.000000000 -0400
+++ ImagePickerManager.m 2020-11-15 21:01:40.000000000 -0500
@@ -1,9 +1,11 @@
#import "ImagePickerManager.h"
#import <React/RCTConvert.h>
-#import <AssetsLibrary/AssetsLibrary.h>
#import <AVFoundation/AVFoundation.h>
#import <Photos/Photos.h>
#import <React/RCTUtils.h>
+#if !TARGET_OS_MACCATALYST
+#import <AssetsLibrary/AssetsLibrary.h>
+#endif
@import MobileCoreServices;
@@ -42,25 +44,25 @@
{
self.callback = callback; // Save the callback so we can use it from the delegate methods
self.options = options;
-
+
dispatch_async(dispatch_get_main_queue(), ^{
-
+
NSString *title = [self.options valueForKey:@"title"];
if ([title isEqual:[NSNull null]] || title.length == 0) {
title = nil; // A more visually appealing UIAlertControl is displayed with a nil title rather than title = @""
}
NSString *cancelTitle = [self.options valueForKey:@"cancelButtonTitle"];
NSString *takePhotoButtonTitle = [self.options valueForKey:@"takePhotoButtonTitle"];
- NSString *chooseFromLibraryButtonTitle = [self.options valueForKey:@"chooseFromLibraryButtonTitle"];
-
+ NSString *chooseFromLibraryButtonTitle = [self.options valueForKey:@"chooseFromLibraryButtonTitle"];
+
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleActionSheet];
alertController.view.tintColor = [RCTConvert UIColor:options[@"tintColor"]];
-
+
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:cancelTitle style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) {
self.callback(@[@{@"didCancel": @YES}]); // Return callback for 'cancel' action (if is required)
}];
[alertController addAction:cancelAction];
-
+
if (![takePhotoButtonTitle isEqual:[NSNull null]] && takePhotoButtonTitle.length > 0) {
UIAlertAction *takePhotoAction = [UIAlertAction actionWithTitle:takePhotoButtonTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
[self actionHandler:action];
@@ -73,7 +75,7 @@
}];
[alertController addAction:chooseFromLibraryAction];
}
-
+
// Add custom buttons to action sheet
if ([self.options objectForKey:@"customButtons"] && [[self.options objectForKey:@"customButtons"] isKindOfClass:[NSArray class]]) {
self.customButtons = [self.options objectForKey:@"customButtons"];
@@ -85,16 +87,16 @@
[alertController addAction:customAction];
}
}
-
+
UIViewController *root = RCTPresentedViewController();
-
+
/* On iPad, UIAlertController presents a popover view rather than an action sheet like on iPhone. We must provide the location
- of the location to show the popover in this case. For simplicity, we'll just display it on the bottom center of the screen
- to mimic an action sheet */
+ of the location to show the popover in this case. For simplicity, we'll just display it on the bottom center of the screen
+ to mimic an action sheet */
alertController.popoverPresentationController.sourceView = root.view;
alertController.popoverPresentationController.sourceRect = CGRectMake(root.view.bounds.size.width / 2.0, root.view.bounds.size.height, 1.0, 1.0);
-
- if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
+
+ if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
alertController.popoverPresentationController.permittedArrowDirections = 0;
for (id subview in alertController.view.subviews) {
if ([subview isMemberOfClass:[UIView class]]) {
@@ -102,7 +104,7 @@
}
}
}
-
+
[root presentViewController:alertController animated:YES completion:nil];
});
}
@@ -119,7 +121,7 @@
return;
}
}
-
+
if ([action.title isEqualToString:[self.options valueForKey:@"takePhotoButtonTitle"]]) { // Take photo
[self launchImagePicker:RNImagePickerTargetCamera];
}
@@ -137,7 +139,7 @@
- (void)launchImagePicker:(RNImagePickerTarget)target
{
self.picker = [[UIImagePickerController alloc] init];
-
+
if (target == RNImagePickerTargetCamera) {
#if TARGET_IPHONE_SIMULATOR
self.callback(@[@{@"error": @"Camera not available on simulator"}]);
@@ -155,10 +157,10 @@
else { // RNImagePickerTargetLibrarySingleImage
self.picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
}
-
+
if ([[self.options objectForKey:@"mediaType"] isEqualToString:@"video"]
|| [[self.options objectForKey:@"mediaType"] isEqualToString:@"mixed"]) {
-
+
if ([[self.options objectForKey:@"videoQuality"] isEqualToString:@"high"]) {
self.picker.videoQuality = UIImagePickerControllerQualityTypeHigh;
}
@@ -168,7 +170,7 @@
else {
self.picker.videoQuality = UIImagePickerControllerQualityTypeMedium;
}
-
+
id durationLimit = [self.options objectForKey:@"durationLimit"];
if (durationLimit) {
self.picker.videoMaximumDuration = [durationLimit doubleValue];
@@ -182,13 +184,13 @@
} else {
self.picker.mediaTypes = @[(NSString *)kUTTypeImage];
}
-
+
if ([[self.options objectForKey:@"allowsEditing"] boolValue]) {
self.picker.allowsEditing = true;
}
self.picker.modalPresentationStyle = UIModalPresentationCurrentContext;
self.picker.delegate = self;
-
+
// Check permissions
void (^showPickerViewController)() = ^void() {
dispatch_async(dispatch_get_main_queue(), ^{
@@ -196,57 +198,57 @@
[root presentViewController:self.picker animated:YES completion:nil];
});
};
-
+
if (target == RNImagePickerTargetCamera) {
[self checkCameraPermissions:^(BOOL granted) {
if (!granted) {
self.callback(@[@{@"error": @"Camera permissions not granted"}]);
return;
}
-
+
showPickerViewController();
}];
}
else { // RNImagePickerTargetLibrarySingleImage
- if (@available(iOS 11.0, *)) {
- showPickerViewController();
- } else {
- [self checkPhotosPermissions:^(BOOL granted) {
- if (!granted) {
- self.callback(@[@{@"error": @"Photo library permissions not granted"}]);
- return;
- }
-
- showPickerViewController();
- }];
- }
+ if (@available(iOS 11.0, *)) {
+ showPickerViewController();
+ } else {
+ [self checkPhotosPermissions:^(BOOL granted) {
+ if (!granted) {
+ self.callback(@[@{@"error": @"Photo library permissions not granted"}]);
+ return;
+ }
+
+ showPickerViewController();
+ }];
+ }
}
}
- (NSString * _Nullable)originalFilenameForAsset:(PHAsset * _Nullable)asset assetType:(PHAssetResourceType)type {
if (!asset) { return nil; }
-
+
PHAssetResource *originalResource;
// Get the underlying resources for the PHAsset (PhotoKit)
NSArray<PHAssetResource *> *pickedAssetResources = [PHAssetResource assetResourcesForAsset:asset];
-
+
// Find the original resource (underlying image) for the asset, which has the desired filename
for (PHAssetResource *resource in pickedAssetResources) {
if (resource.type == type) {
originalResource = resource;
}
}
-
+
return originalResource.originalFilename;
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
{
dispatch_block_t dismissCompletionBlock = ^{
-
- NSURL *imageURL = [info valueForKey:UIImagePickerControllerReferenceURL];
+
+ NSURL *imageURL = [info valueForKey:UIImagePickerControllerPHAsset];
NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType];
-
+
NSString *fileName;
if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) {
NSString *tempFileName = [[NSUUID UUID] UUIDString];
@@ -264,18 +266,18 @@
NSURL *videoURL = info[UIImagePickerControllerMediaURL];
fileName = videoURL.lastPathComponent;
}
-
+
// We default to path to the temporary directory
NSString *path = [[NSTemporaryDirectory()stringByStandardizingPath] stringByAppendingPathComponent:fileName];
-
+
// If storage options are provided, we use the documents directory which is persisted
if ([self.options objectForKey:@"storageOptions"] && [[self.options objectForKey:@"storageOptions"] isKindOfClass:[NSDictionary class]]) {
NSDictionary *storageOptions = [self.options objectForKey:@"storageOptions"];
-
+
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
path = [documentsDirectory stringByAppendingPathComponent:fileName];
-
+
// Creates documents subdirectory, if provided
if ([storageOptions objectForKey:@"path"]) {
NSString *newPath = [documentsDirectory stringByAppendingPathComponent:[storageOptions objectForKey:@"path"]];
@@ -291,10 +293,10 @@
}
}
}
-
+
// Create the response object
self.response = [[NSMutableDictionary alloc] init];
-
+
if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) { // PHOTOS
UIImage *originalImage;
if ([[self.options objectForKey:@"allowsEditing"] boolValue]) {
@@ -303,13 +305,15 @@
else {
originalImage = [info objectForKey:UIImagePickerControllerOriginalImage];
}
-
+
if (imageURL) {
PHAsset *pickedAsset;
if (@available(iOS 11.0, *)) {
- pickedAsset = [info objectForKey: UIImagePickerControllerPHAsset];
+ pickedAsset = [info objectForKey: UIImagePickerControllerPHAsset];
} else {
- pickedAsset = [PHAsset fetchAssetsWithALAssetURLs:@[imageURL] options:nil].lastObject;
+#if !TARGET_OS_MACCATALYST
+ pickedAsset = [PHAsset fetchAssetsWithALAssetURLs:@[imageURL] options:nil].lastObject;
+#endif
}
NSString *originalFilename = [self originalFilenameForAsset:pickedAsset assetType:PHAssetResourceTypePhoto];
@@ -322,9 +326,10 @@
self.response[@"timestamp"] = [[ImagePickerManager ISO8601DateFormatter] stringFromDate:pickedAsset.creationDate];
}
}
-
+
// GIFs break when resized, so we handle them differently
if (imageURL && [[imageURL absoluteString] rangeOfString:@"ext=GIF"].location != NSNotFound) {
+#if !TARGET_OS_MACCATALYST
ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init];
[assetsLibrary assetForURL:imageURL resultBlock:^(ALAsset *asset) {
ALAssetRepresentation *rep = [asset defaultRepresentation];
@@ -333,38 +338,39 @@
NSUInteger buffered = [rep getBytes:buffer fromOffset:0.0 length:repSize error:nil];
NSData *data = [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];
[data writeToFile:path atomically:YES];
-
+
NSMutableDictionary *gifResponse = [[NSMutableDictionary alloc] init];
[gifResponse setObject:@(originalImage.size.width) forKey:@"width"];
[gifResponse setObject:@(originalImage.size.height) forKey:@"height"];
-
+
BOOL vertical = (originalImage.size.width < originalImage.size.height) ? YES : NO;
[gifResponse setObject:@(vertical) forKey:@"isVertical"];
-
+
if (![[self.options objectForKey:@"noData"] boolValue]) {
NSString *dataString = [data base64EncodedStringWithOptions:0];
[gifResponse setObject:dataString forKey:@"data"];
}
-
+
NSURL *fileURL = [NSURL fileURLWithPath:path];
[gifResponse setObject:[fileURL absoluteString] forKey:@"uri"];
-
+
NSNumber *fileSizeValue = nil;
NSError *fileSizeError = nil;
[fileURL getResourceValue:&fileSizeValue forKey:NSURLFileSizeKey error:&fileSizeError];
if (fileSizeValue){
[gifResponse setObject:fileSizeValue forKey:@"fileSize"];
}
-
+
self.callback(@[gifResponse]);
} failureBlock:^(NSError *error) {
self.callback(@[@{@"error": error.localizedFailureReason}]);
}];
+#endif
return;
}
-
+
UIImage *editedImage = [self fixOrientation:originalImage]; // Rotate the image for upload to web
-
+
// If needed, downscale image
float maxWidth = editedImage.size.width;
float maxHeight = editedImage.size.height;
@@ -375,7 +381,7 @@
maxHeight = [[self.options valueForKey:@"maxHeight"] floatValue];
}
editedImage = [self downscaleImageIfNecessary:editedImage maxWidth:maxWidth maxHeight:maxHeight];
-
+
NSData *data;
NSString *mimeType;
if ([[self.options objectForKey:@"imageFileType"] isEqualToString:@"png"]) {
@@ -388,36 +394,37 @@
}
[self.response setObject:mimeType forKey:@"type"];
[data writeToFile:path atomically:YES];
-
+
if (![[self.options objectForKey:@"noData"] boolValue]) {
NSString *dataString = [data base64EncodedStringWithOptions:0]; // base64 encoded image string
[self.response setObject:dataString forKey:@"data"];
}
-
+
BOOL vertical = (editedImage.size.width < editedImage.size.height) ? YES : NO;
[self.response setObject:@(vertical) forKey:@"isVertical"];
NSURL *fileURL = [NSURL fileURLWithPath:path];
NSString *filePath = [fileURL absoluteString];
[self.response setObject:filePath forKey:@"uri"];
-
+
// add ref to the original image
NSString *origURL = [imageURL absoluteString];
if (origURL) {
- [self.response setObject:origURL forKey:@"origURL"];
+ [self.response setObject:origURL forKey:@"origURL"];
}
-
+
NSNumber *fileSizeValue = nil;
NSError *fileSizeError = nil;
[fileURL getResourceValue:&fileSizeValue forKey:NSURLFileSizeKey error:&fileSizeError];
if (fileSizeValue){
[self.response setObject:fileSizeValue forKey:@"fileSize"];
}
-
+
[self.response setObject:@(editedImage.size.width) forKey:@"width"];
[self.response setObject:@(editedImage.size.height) forKey:@"height"];
-
+
NSDictionary *storageOptions = [self.options objectForKey:@"storageOptions"];
if (storageOptions && [[storageOptions objectForKey:@"cameraRoll"] boolValue] == YES && self.picker.sourceType == UIImagePickerControllerSourceTypeCamera) {
+#if !TARGET_OS_MACCATALYST
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
if ([[storageOptions objectForKey:@"waitUntilSaved"] boolValue]) {
[library writeImageToSavedPhotosAlbum:originalImage.CGImage metadata:[info valueForKey:UIImagePickerControllerMediaMetadata] completionBlock:^(NSURL *assetURL, NSError *error) {
@@ -440,13 +447,15 @@
} else {
[library writeImageToSavedPhotosAlbum:originalImage.CGImage metadata:[info valueForKey:UIImagePickerControllerMediaMetadata] completionBlock:nil];
}
+#endif
}
}
else { // VIDEO
- NSURL *videoRefURL = info[UIImagePickerControllerReferenceURL];
+ NSURL *videoRefURL = info[UIImagePickerControllerPHAsset];
NSURL *videoURL = info[UIImagePickerControllerMediaURL];
NSURL *videoDestinationURL = [NSURL fileURLWithPath:path];
-
+
+#if !TARGET_OS_MACCATALYST
if (videoRefURL) {
PHAsset *pickedAsset = [PHAsset fetchAssetsWithALAssetURLs:@[videoRefURL] options:nil].lastObject;
NSString *originalFilename = [self originalFilenameForAsset:pickedAsset assetType:PHAssetResourceTypeVideo];
@@ -459,39 +468,41 @@
self.response[@"timestamp"] = [[ImagePickerManager ISO8601DateFormatter] stringFromDate:pickedAsset.creationDate];
}
}
-
+#endif
+
if ([videoURL.URLByResolvingSymlinksInPath.path isEqualToString:videoDestinationURL.URLByResolvingSymlinksInPath.path] == NO) {
NSFileManager *fileManager = [NSFileManager defaultManager];
-
+
// Delete file if it already exists
if ([fileManager fileExistsAtPath:videoDestinationURL.path]) {
[fileManager removeItemAtURL:videoDestinationURL error:nil];
}
-
+
if (videoURL) { // Protect against reported crash
- NSError *error = nil;
-
- // If we have write access to the source file, move it. Otherwise use copy.
- if ([fileManager isWritableFileAtPath:[videoURL path]]) {
- [fileManager moveItemAtURL:videoURL toURL:videoDestinationURL error:&error];
- } else {
- [fileManager copyItemAtURL:videoURL toURL:videoDestinationURL error:&error];
- }
-
- if (error) {
- self.callback(@[@{@"error": error.localizedFailureReason}]);
- return;
- }
+ NSError *error = nil;
+
+ // If we have write access to the source file, move it. Otherwise use copy.
+ if ([fileManager isWritableFileAtPath:[videoURL path]]) {
+ [fileManager moveItemAtURL:videoURL toURL:videoDestinationURL error:&error];
+ } else {
+ [fileManager copyItemAtURL:videoURL toURL:videoDestinationURL error:&error];
+ }
+
+ if (error) {
+ self.callback(@[@{@"error": error.localizedFailureReason}]);
+ return;
+ }
}
}
-
+
[self.response setObject:videoDestinationURL.absoluteString forKey:@"uri"];
if (videoRefURL.absoluteString) {
[self.response setObject:videoRefURL.absoluteString forKey:@"origURL"];
}
-
+
NSDictionary *storageOptions = [self.options objectForKey:@"storageOptions"];
if (storageOptions && [[storageOptions objectForKey:@"cameraRoll"] boolValue] == YES && self.picker.sourceType == UIImagePickerControllerSourceTypeCamera) {
+#if !TARGET_OS_MACCATALYST
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library writeVideoAtPathToSavedPhotosAlbum:videoDestinationURL completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
@@ -509,22 +520,23 @@
self.response[@"timestamp"] = [[ImagePickerManager ISO8601DateFormatter] stringFromDate:capturedAsset.creationDate];
}
}
-
+
self.callback(@[self.response]);
}
}
}];
+#endif
}
}
-
+
// If storage options are provided, check the skipBackup flag
if ([self.options objectForKey:@"storageOptions"] && [[self.options objectForKey:@"storageOptions"] isKindOfClass:[NSDictionary class]]) {
NSDictionary *storageOptions = [self.options objectForKey:@"storageOptions"];
-
+
if ([[storageOptions objectForKey:@"skipBackup"] boolValue]) {
[self addSkipBackupAttributeToItemAtPath:path]; // Don't back up the file to iCloud
}
-
+
if ([[storageOptions objectForKey:@"waitUntilSaved"] boolValue] == NO ||
[[storageOptions objectForKey:@"cameraRoll"] boolValue] == NO ||
self.picker.sourceType != UIImagePickerControllerSourceTypeCamera)
@@ -536,7 +548,7 @@
self.callback(@[self.response]);
}
};
-
+
dispatch_async(dispatch_get_main_queue(), ^{
[picker dismissViewControllerAnimated:YES completion:dismissCompletionBlock];
});
@@ -595,12 +607,12 @@
- (UIImage*)downscaleImageIfNecessary:(UIImage*)image maxWidth:(float)maxWidth maxHeight:(float)maxHeight
{
UIImage* newImage = image;
-
+
// Nothing to do here
if (image.size.width <= maxWidth && image.size.height <= maxHeight) {
return newImage;
}
-
+
CGSize scaledSize = CGSizeMake(image.size.width, image.size.height);
if (maxWidth < scaledSize.width) {
scaledSize = CGSizeMake(maxWidth, (maxWidth / scaledSize.width) * scaledSize.height);
@@ -608,11 +620,11 @@
if (maxHeight < scaledSize.height) {
scaledSize = CGSizeMake((maxHeight / scaledSize.height) * scaledSize.width, maxHeight);
}
-
+
// If the pixels are floats, it causes a white line in iOS8 and probably other versions too
scaledSize.width = (int)scaledSize.width;
scaledSize.height = (int)scaledSize.height;
-
+
UIGraphicsBeginImageContext(scaledSize); // this will resize
[image drawInRect:CGRectMake(0, 0, scaledSize.width, scaledSize.height)];
newImage = UIGraphicsGetImageFromCurrentImageContext();
@@ -620,7 +632,7 @@
NSLog(@"could not scale image");
}
UIGraphicsEndImageContext();
-
+
return newImage;
}
@@ -628,7 +640,7 @@
if (srcImg.imageOrientation == UIImageOrientationUp) {
return srcImg;
}
-
+
CGAffineTransform transform = CGAffineTransformIdentity;
switch (srcImg.imageOrientation) {
case UIImageOrientationDown:
@@ -636,13 +648,13 @@
transform = CGAffineTransformTranslate(transform, srcImg.size.width, srcImg.size.height);
transform = CGAffineTransformRotate(transform, M_PI);
break;
-
+
case UIImageOrientationLeft:
case UIImageOrientationLeftMirrored:
transform = CGAffineTransformTranslate(transform, srcImg.size.width, 0);
transform = CGAffineTransformRotate(transform, M_PI_2);
break;
-
+
case UIImageOrientationRight:
case UIImageOrientationRightMirrored:
transform = CGAffineTransformTranslate(transform, 0, srcImg.size.height);
@@ -652,14 +664,14 @@
case UIImageOrientationUpMirrored:
break;
}
-
+
switch (srcImg.imageOrientation) {
case UIImageOrientationUpMirrored:
case UIImageOrientationDownMirrored:
transform = CGAffineTransformTranslate(transform, srcImg.size.width, 0);
transform = CGAffineTransformScale(transform, -1, 1);
break;
-
+
case UIImageOrientationLeftMirrored:
case UIImageOrientationRightMirrored:
transform = CGAffineTransformTranslate(transform, srcImg.size.height, 0);
@@ -671,7 +683,7 @@
case UIImageOrientationRight:
break;
}
-
+
CGContextRef ctx = CGBitmapContextCreate(NULL, srcImg.size.width, srcImg.size.height, CGImageGetBitsPerComponent(srcImg.CGImage), 0, CGImageGetColorSpace(srcImg.CGImage), CGImageGetBitmapInfo(srcImg.CGImage));
CGContextConcatCTM(ctx, transform);
switch (srcImg.imageOrientation) {
@@ -681,12 +693,12 @@
case UIImageOrientationRightMirrored:
CGContextDrawImage(ctx, CGRectMake(0,0,srcImg.size.height,srcImg.size.width), srcImg.CGImage);
break;
-
+
default:
CGContextDrawImage(ctx, CGRectMake(0,0,srcImg.size.width,srcImg.size.height), srcImg.CGImage);
break;
}
-
+
CGImageRef cgimg = CGBitmapContextCreateImage(ctx);
UIImage *img = [UIImage imageWithCGImage:cgimg];
CGContextRelease(ctx);
@@ -701,7 +713,7 @@
NSError *error = nil;
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
forKey: NSURLIsExcludedFromBackupKey error: &error];
-
+
if(!success){
NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
}