Skip to content

Commit

Permalink
Return originalPath for iOS as well. forceOldAndroidPhotoPicker optio…
Browse files Browse the repository at this point in the history
…n for Android
  • Loading branch information
budowski committed Oct 1, 2023
1 parent 1c7d59c commit 2dac5ff
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 35 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ The `callback` will be called with a response object, refer to [The Response Obj
## Options
| Option | iOS | Android | Web | Description |
| ----------------------- | --- | ------- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ----------------------- |-----|---------| --- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| mediaType | OK | OK | OK | `photo` or `video` or `mixed`(`launchCamera` on Android does not support 'mixed'). Web only supports 'photo' for now. |
| maxWidth | OK | OK | NO | To resize the image. |
| maxHeight | OK | OK | NO | To resize the image. |
Expand All @@ -106,6 +106,7 @@ The `callback` will be called with a response object, refer to [The Response Obj
| presentationStyle | OK | NO | NO | Controls how the picker is presented. `currentContext`, `pageSheet`, `fullScreen`, `formSheet`, `popover`, `overFullScreen`, `overCurrentContext`. Default is `currentContext`. |
| formatAsMp4 | OK | NO | NO | Converts the selected video to MP4 (iOS Only). |
| assetRepresentationMode | OK | NO | NO | A mode that determines which representation to use if an asset contains more than one. Possible values: 'auto', 'current', 'compatible'. Default is 'auto'. |
| forceOldAndroidPhotoPicker | NO | OK | NO | If true, forces to use old photo picker on Android, since new one redacts EXIF metadata (see https://issuetracker.google.com/issues/243294058) |
## The Response Object
Expand All @@ -120,10 +121,10 @@ The `callback` will be called with a response object, refer to [The Response Obj
## Asset Object
| key | iOS | Android | Web | Photo/Video | Requires Permissions | Description |
| --------- | --- | ------- | --- | ----------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| --------- |-----| ------- | --- |-------------| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| base64 | OK | OK | OK | PHOTO ONLY | NO | The base64 string of the image (photos only) |
| uri | OK | OK | OK | BOTH | NO | The file uri in app specific cache storage. Except when picking **video from Android gallery** where you will get read only content uri, to get file uri in this case copy the file to app specific storage using any react-native library. For web it uses the base64 as uri. |
| originalPath | NO | OK | NO | BOTH | NO | The original file path. |
| originalPath | OK | OK | NO | BOTH | NO | The original file path. |
| width | OK | OK | OK | BOTH | NO | Asset dimensions |
| height | OK | OK | OK | BOTH | NO | Asset dimensions |
| fileSize | OK | OK | NO | BOTH | NO | The file size |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,11 @@ public void launchImageLibrary(final ReadableMap options, final Callback callbac
boolean isPhoto = this.options.mediaType.equals(mediaTypePhoto);
boolean isVideo = this.options.mediaType.equals(mediaTypeVideo);

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
if (this.options.forceOldAndroidPhotoPicker || Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
if (isSingleSelect && (isPhoto || isVideo)) {
libraryIntent = new Intent(Intent.ACTION_PICK);
} else {
libraryIntent = new Intent(Intent.ACTION_GET_CONTENT);
libraryIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
libraryIntent.addCategory(Intent.CATEGORY_OPENABLE);
}
} else {
Expand All @@ -140,7 +140,7 @@ public void launchImageLibrary(final ReadableMap options, final Callback callbac
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
libraryIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
} else {
if (selectionLimit != 1) {
if ((selectionLimit != 1) && (!libraryIntent.getAction().equals(Intent.ACTION_GET_CONTENT))) {
int maxNum = selectionLimit;
if (selectionLimit == 0) maxNum = MediaStore.getPickImagesMaxLimit();
libraryIntent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, maxNum);
Expand Down
2 changes: 2 additions & 0 deletions android/src/main/java/com/imagepicker/Options.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ public class Options {
int durationLimit;
Boolean useFrontCamera = false;
String mediaType;
Boolean forceOldAndroidPhotoPicker = false;


Options(ReadableMap options) {
mediaType = options.getString("mediaType");
selectionLimit = options.getInt("selectionLimit");
includeBase64 = options.getBoolean("includeBase64");
includeExtra = options.getBoolean("includeExtra");
forceOldAndroidPhotoPicker = options.getBoolean("forceOldAndroidPhotoPicker");

String videoQualityString = options.getString("videoQuality");
if (!TextUtils.isEmpty(videoQualityString) && !videoQualityString.toLowerCase().equals("high")) {
Expand Down
59 changes: 30 additions & 29 deletions ios/ImagePickerManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,12 @@ @implementation ImagePickerManager
- (void)launchImagePicker:(NSDictionary *)options callback:(RCTResponseSenderBlock)callback
{
self.callback = callback;

if (target == camera && [ImagePickerUtils isSimulator]) {
self.callback(@[@{@"errorCode": errCameraUnavailable}]);
return;
}

self.options = options;

#if __has_include(<PhotosUI/PHPicker.h>)
Expand All @@ -85,7 +85,7 @@ - (void)launchImagePicker:(NSDictionary *)options callback:(RCTResponseSenderBlo
picker.presentationController.delegate = self;

if([self.options[@"includeExtra"] boolValue]) {

[self checkPhotosPermissions:^(BOOL granted) {
if (!granted) {
self.callback(@[@{@"errorCode": errPermission}]);
Expand All @@ -96,15 +96,15 @@ - (void)launchImagePicker:(NSDictionary *)options callback:(RCTResponseSenderBlo
} else {
[self showPickerViewController:picker];
}

return;
}
}
#endif
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
[ImagePickerUtils setupPickerFromOptions:picker options:self.options target:target];
picker.delegate = self;

if([self.options[@"includeExtra"] boolValue]) {
[self checkPhotosPermissions:^(BOOL granted) {
if (!granted) {
Expand All @@ -131,7 +131,7 @@ - (void) showPickerViewController:(UIViewController *)picker
NSData* extractImageData(UIImage* image){
CFMutableDataRef imageData = CFDataCreateMutable(NULL, 0);
CGImageDestinationRef destination = CGImageDestinationCreateWithData(imageData, kUTTypeJPEG, 1, NULL);

CFStringRef orientationKey[1];
CFTypeRef orientationValue[1];
CGImagePropertyOrientation CGOrientation = CGImagePropertyOrientationForUIImageOrientation(image.imageOrientation);
Expand All @@ -141,11 +141,11 @@ - (void) showPickerViewController:(UIViewController *)picker

CFDictionaryRef imageProps = CFDictionaryCreate( NULL, (const void **)orientationKey, (const void **)orientationValue, 1,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

CGImageDestinationAddImage(destination, image.CGImage, imageProps);

CGImageDestinationFinalize(destination);

CFRelease(destination);
CFRelease(orientationValue[0]);
CFRelease(imageProps);
Expand All @@ -162,7 +162,7 @@ -(NSMutableDictionary *)mapImageToAsset:(UIImage *)image data:(NSData *)data phA
}
data = extractImageData(image);
}

UIImage* newImage = image;
if (![fileType isEqualToString:@"gif"]) {
newImage = [ImagePickerUtils resizeImage:image
Expand All @@ -178,7 +178,7 @@ -(NSMutableDictionary *)mapImageToAsset:(UIImage *)image data:(NSData *)data phA
data = UIImagePNGRepresentation(newImage);
}
}

NSMutableDictionary *asset = [[NSMutableDictionary alloc] init];
asset[@"type"] = [@"image/" stringByAppendingString:fileType];

Expand All @@ -203,13 +203,14 @@ -(NSMutableDictionary *)mapImageToAsset:(UIImage *)image data:(NSData *)data phA
asset[@"fileName"] = fileName;
asset[@"width"] = @(newImage.size.width);
asset[@"height"] = @(newImage.size.height);

if(phAsset){
asset[@"timestamp"] = [self getDateTimeInUTC:phAsset.creationDate];
asset[@"id"] = phAsset.localIdentifier;
asset[@"originalPath"] = [NSString stringWithFormat:@"ph://%@", phAsset.localIdentifier];
// Add more extra data here ...
}

return asset;
}

Expand All @@ -236,10 +237,10 @@ -(NSMutableDictionary *)mapVideoToAsset:(NSURL *)url phAsset:(PHAsset * _Nullabl
if ((target == camera) && [self.options[@"saveToPhotos"] boolValue]) {
UISaveVideoAtPathToSavedPhotosAlbum(url.path, nil, nil, nil);
}

if (![url.URLByResolvingSymlinksInPath.path isEqualToString:videoDestinationURL.URLByResolvingSymlinksInPath.path]) {
NSFileManager *fileManager = [NSFileManager defaultManager];

// Delete file if it already exists
if ([fileManager fileExistsAtPath:videoDestinationURL.path]) {
[fileManager removeItemAtURL:videoDestinationURL error:nil];
Expand All @@ -259,25 +260,25 @@ -(NSMutableDictionary *)mapVideoToAsset:(NSURL *)url phAsset:(PHAsset * _Nullabl
}
}
}

NSMutableDictionary *response = [[NSMutableDictionary alloc] init];

if([self.options[@"formatAsMp4"] boolValue] && ![fileExtension isEqualToString:@"mp4"]) {
NSURL *parentURL = [videoDestinationURL URLByDeletingLastPathComponent];
NSString *path = [[parentURL.path stringByAppendingString:@"/"] stringByAppendingString:[[NSUUID UUID] UUIDString]];
path = [path stringByAppendingString:@".mp4"];
NSURL *outputURL = [NSURL fileURLWithPath:path];

[[NSFileManager defaultManager] removeItemAtURL:outputURL error:nil];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:videoDestinationURL options:nil];
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetPassthrough];

exportSession.outputURL = outputURL;
exportSession.outputFileType = AVFileTypeMPEG4;
exportSession.shouldOptimizeForNetworkUse = YES;

dispatch_semaphore_t sem = dispatch_semaphore_create(0);

[exportSession exportAsynchronouslyWithCompletionHandler:^(void) {
if (exportSession.status == AVAssetExportSessionStatusCompleted) {
CGSize dimentions = [ImagePickerUtils getVideoDimensionsFromUrl:outputURL];
Expand All @@ -288,14 +289,14 @@ -(NSMutableDictionary *)mapVideoToAsset:(NSURL *)url phAsset:(PHAsset * _Nullabl
response[@"fileSize"] = [ImagePickerUtils getFileSizeFromUrl:outputURL];
response[@"width"] = @(dimentions.width);
response[@"height"] = @(dimentions.height);

dispatch_semaphore_signal(sem);
} else if (exportSession.status == AVAssetExportSessionStatusFailed || exportSession.status == AVAssetExportSessionStatusCancelled) {
dispatch_semaphore_signal(sem);
}
}];


dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
} else {
CGSize dimentions = [ImagePickerUtils getVideoDimensionsFromUrl:videoDestinationURL];
Expand All @@ -306,7 +307,7 @@ -(NSMutableDictionary *)mapVideoToAsset:(NSURL *)url phAsset:(PHAsset * _Nullabl
response[@"fileSize"] = [ImagePickerUtils getFileSizeFromUrl:videoDestinationURL];
response[@"width"] = @(dimentions.width);
response[@"height"] = @(dimentions.height);

if(phAsset){
response[@"timestamp"] = [self getDateTimeInUTC:phAsset.creationDate];
response[@"id"] = phAsset.localIdentifier;
Expand Down Expand Up @@ -456,12 +457,12 @@ - (void)imagePickerController:(UIImagePickerController *)picker didFinishPicking

if ([info[UIImagePickerControllerMediaType] isEqualToString:(NSString *) kUTTypeImage]) {
UIImage *image = [ImagePickerManager getUIImageFromInfo:info];

[assets addObject:[self mapImageToAsset:image data:[NSData dataWithContentsOfURL:[ImagePickerManager getNSURLFromInfo:info]] phAsset:asset]];
} else {
NSError *error;
NSDictionary *videoAsset = [self mapVideoToAsset:info[UIImagePickerControllerMediaURL] phAsset:asset error:&error];

if (videoAsset == nil) {
NSString *errorMessage = error.localizedFailureReason;
if (errorMessage == nil) errorMessage = @"Video asset not found";
Expand Down Expand Up @@ -512,7 +513,7 @@ - (void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray<PHPick
return;
}
photoSelected = YES;

if (results.count == 0) {
dispatch_async(dispatch_get_main_queue(), ^{
self.callback(@[@{@"didCancel": @YES}]);
Expand All @@ -535,7 +536,7 @@ - (void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray<PHPick
PHFetchResult* fetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:@[result.assetIdentifier] options:nil];
asset = fetchResult.firstObject;
}

dispatch_group_enter(completionGroup);

if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]) {
Expand All @@ -549,7 +550,7 @@ - (void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray<PHPick
[provider loadFileRepresentationForTypeIdentifier:identifier completionHandler:^(NSURL * _Nullable url, NSError * _Nullable error) {
NSData *data = [[NSData alloc] initWithContentsOfURL:url];
UIImage *image = [[UIImage alloc] initWithData:data];

assets[index] = [self mapImageToAsset:image data:data phAsset:asset];
dispatch_group_leave(completionGroup);
}];
Expand Down

0 comments on commit 2dac5ff

Please sign in to comment.