From d515b12681894165739c958952d69bdca5ceb28f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tolgahan=20=C3=87elik?= Date: Thu, 8 Dec 2022 11:22:33 +0300 Subject: [PATCH] feat(all): file extension support (for CameraRoll.getPhotos ) (#440) * Ios added extension support. * Android added extesion support. * Android mimetype process moved to utils. * Typescript added types. * Readme added types. --- README.md | 2 + .../cameraroll/CameraRollModule.java | 11 ++++- .../cameraroll/Utils.java | 5 +++ ios/RNCCameraRollManager.m | 40 ++++++++++++------- src/CameraRoll.ts | 2 + 5 files changed, 44 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 9c80d57..5001c0a 100644 --- a/README.md +++ b/README.md @@ -237,6 +237,7 @@ Returns a Promise with photo identifier objects from the local camera roll of th * `include` : {Array} : Whether to include some fields that are slower to fetch * `filename` : Ensures `image.filename` is available in each node. This has a large performance impact on iOS. * `fileSize` : Ensures `image.fileSize` is available in each node. This has a large performance impact on iOS. + * `fileExtension` : Ensures `image.fileExtension` is available in each node. * `location`: Ensures `location` is available in each node. This has a large performance impact on Android. * `imageSize` : Ensures `image.width` and `image.height` are available in each node. This has a small performance impact on Android. * `playableDuration` : Ensures `image.playableDuration` is available in each node. This has a medium peformance impact on Android. @@ -250,6 +251,7 @@ Returns a Promise which when resolved will be of the following shape: * `image`: {object} : An object with the following shape: * `uri`: {string} * `filename`: {string | null} : Only set if the `include` parameter contains `filename` + * `extension`: {string | null} : Only set if the `include` parameter contains `fileExtension` * `height`: {number | null} : Only set if the `include` parameter contains `imageSize` * `width`: {number | null} : Only set if the `include` parameter contains `imageSize` * `fileSize`: {number | null} : Only set if the `include` parameter contains `fileSize` diff --git a/android/src/main/java/com/reactnativecommunity/cameraroll/CameraRollModule.java b/android/src/main/java/com/reactnativecommunity/cameraroll/CameraRollModule.java index d6af5ff..70739fd 100644 --- a/android/src/main/java/com/reactnativecommunity/cameraroll/CameraRollModule.java +++ b/android/src/main/java/com/reactnativecommunity/cameraroll/CameraRollModule.java @@ -80,6 +80,7 @@ public class CameraRollModule extends ReactContextBaseJavaModule { private static final String INCLUDE_FILENAME = "filename"; private static final String INCLUDE_FILE_SIZE = "fileSize"; + private static final String INCLUDE_FILE_EXTENSION = "fileExtension"; private static final String INCLUDE_LOCATION = "location"; private static final String INCLUDE_IMAGE_SIZE = "imageSize"; private static final String INCLUDE_PLAYABLE_DURATION = "playableDuration"; @@ -572,6 +573,7 @@ public class CameraRollModule extends ReactContextBaseJavaModule { boolean includeLocation = include.contains(INCLUDE_LOCATION); boolean includeFilename = include.contains(INCLUDE_FILENAME); boolean includeFileSize = include.contains(INCLUDE_FILE_SIZE); + boolean includeFileExtension = include.contains(INCLUDE_FILE_EXTENSION); boolean includeImageSize = include.contains(INCLUDE_IMAGE_SIZE); boolean includePlayableDuration = include.contains(INCLUDE_PLAYABLE_DURATION); @@ -580,7 +582,7 @@ public class CameraRollModule extends ReactContextBaseJavaModule { WritableMap node = new WritableNativeMap(); boolean imageInfoSuccess = putImageInfo(resolver, media, node, widthIndex, heightIndex, sizeIndex, dataIndex, - mimeTypeIndex, includeFilename, includeFileSize, includeImageSize, + mimeTypeIndex, includeFilename, includeFileSize, includeFileExtension, includeImageSize, includePlayableDuration); if (imageInfoSuccess) { putBasicNodeInfo(media, node, mimeTypeIndex, groupNameIndex, dateTakenIndex, dateAddedIndex, dateModifiedIndex); @@ -632,6 +634,7 @@ public class CameraRollModule extends ReactContextBaseJavaModule { int mimeTypeIndex, boolean includeFilename, boolean includeFileSize, + boolean includeFileExtension, boolean includeImageSize, boolean includePlayableDuration) { WritableMap image = new WritableNativeMap(); @@ -659,6 +662,12 @@ public class CameraRollModule extends ReactContextBaseJavaModule { image.putNull("fileSize"); } + if (includeFileExtension) { + image.putString("extension", Utils.getExtension(mimeType)); + } else { + image.putNull("extension"); + } + node.putMap("image", image); return putImageSizeSuccess && putPlayableDurationSuccess; } diff --git a/android/src/main/java/com/reactnativecommunity/cameraroll/Utils.java b/android/src/main/java/com/reactnativecommunity/cameraroll/Utils.java index accf026..a87bedf 100644 --- a/android/src/main/java/com/reactnativecommunity/cameraroll/Utils.java +++ b/android/src/main/java/com/reactnativecommunity/cameraroll/Utils.java @@ -13,4 +13,9 @@ public class Utils { return type; } + public static String getExtension(String mimeType) { + String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType); + return extension; + } + } diff --git a/ios/RNCCameraRollManager.m b/ios/RNCCameraRollManager.m index 570a434..8c8c1ac 100644 --- a/ios/RNCCameraRollManager.m +++ b/ios/RNCCameraRollManager.m @@ -293,6 +293,7 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params BOOL __block includeFilename = [include indexOfObject:@"filename"] != NSNotFound; BOOL __block includeFileSize = [include indexOfObject:@"fileSize"] != NSNotFound; + BOOL __block includeFileExtension = [include indexOfObject:@"fileExtension"] != NSNotFound; BOOL __block includeLocation = [include indexOfObject:@"location"] != NSNotFound; BOOL __block includeImageSize = [include indexOfObject:@"imageSize"] != NSNotFound; BOOL __block includePlayableDuration = [include indexOfObject:@"playableDuration"] != NSNotFound; @@ -338,7 +339,7 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params requestPhotoLibraryAccess(reject, ^(bool isLimited){ void (^collectAsset)(PHAsset*, NSUInteger, BOOL*) = ^(PHAsset * _Nonnull asset, NSUInteger assetIdx, BOOL * _Nonnull stopAssets) { NSString *const uri = [NSString stringWithFormat:@"ph://%@", [asset localIdentifier]]; - + if (afterCursor && !foundAfter) { if ([afterCursor isEqualToString:uri]) { foundAfter = YES; @@ -346,6 +347,7 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params return; } NSString *_Nullable originalFilename = NULL; + NSString *_Nullable fileExtension = NULL; PHAssetResource *_Nullable resource = NULL; NSNumber* fileSize = [NSNumber numberWithInt:0]; @@ -399,6 +401,13 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params : (asset.mediaType == PHAssetMediaTypeAudio ? @"audio" : @"unknown"))); + + if (includeFileExtension) { + NSString *name = [asset valueForKey:@"filename"]; + NSString *extension = [name pathExtension]; + fileExtension = [extension lowercaseString]; + } + CLLocation *const loc = asset.location; [assets addObject:@{ @@ -407,6 +416,7 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params @"group_name": currentCollectionName, @"image": @{ @"uri": uri, + @"extension": (includeFileExtension ? fileExtension : [NSNull null]), @"filename": (includeFilename && originalFilename ? originalFilename : [NSNull null]), @"height": (includeImageSize ? @([asset pixelHeight]) : [NSNull null]), @"width": (includeImageSize ? @([asset pixelWidth]) : [NSNull null]), @@ -487,25 +497,25 @@ RCT_EXPORT_METHOD(getPhotoByInternalID:(NSString *)internalId BOOL const convertHeic = [RCTConvert BOOL:options[@"convertHeicImages"]]; requestPhotoLibraryAccess(reject, ^(bool isLimited){ - + PHFetchResult *fetchResult; PHAsset *asset; - + NSString *mediaIdentifier = internalId; - + if ([internalId rangeOfString:@"ph://"].location != NSNotFound) { mediaIdentifier = [internalId stringByReplacingOccurrencesOfString:@"ph://" withString:@""]; } - + fetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:@[mediaIdentifier] options:nil]; if(fetchResult){ asset = fetchResult.firstObject;//only object in the array. } - + if(asset){ __block NSURL *imageURL = [[NSURL alloc]initWithString:@""]; - + NSString *const assetMediaTypeLabel = (asset.mediaType == PHAssetMediaTypeVideo ? @"video" : (asset.mediaType == PHAssetMediaTypeImage @@ -516,16 +526,16 @@ RCT_EXPORT_METHOD(getPhotoByInternalID:(NSString *)internalId CLLocation *const loc = asset.location; - + NSArray *const assetResources = [PHAssetResource assetResourcesForAsset:asset]; if (![assetResources firstObject]) { return; } PHAssetResource *const _Nonnull resource = [assetResources firstObject]; - + __block NSString *originalFilename = resource.originalFilename; NSString *const uniformMimeType = resource.uniformTypeIdentifier; - + __block NSString *filePath = @""; // check if HEIC extension asset @@ -535,7 +545,7 @@ RCT_EXPORT_METHOD(getPhotoByInternalID:(NSString *)internalId requestOptions.networkAccessAllowed = YES; requestOptions.version = PHImageRequestOptionsVersionUnadjusted; requestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; - + CGSize const targetSize = CGSizeMake((CGFloat)asset.pixelWidth, (CGFloat)asset.pixelHeight); [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:targetSize @@ -589,11 +599,11 @@ RCT_EXPORT_METHOD(getPhotoByInternalID:(NSString *)internalId PHContentEditingInputRequestOptions *const editOptions = [PHContentEditingInputRequestOptions new]; // Download asset if on icloud. editOptions.networkAccessAllowed = YES; - + [asset requestContentEditingInputWithOptions:editOptions completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info) { imageURL = contentEditingInput.fullSizeImageURL; if (imageURL.absoluteString.length != 0) { - + filePath = [imageURL.absoluteString stringByReplacingOccurrencesOfString:@"pathfile:" withString:@"file:"]; resolve(@{ @@ -626,14 +636,14 @@ RCT_EXPORT_METHOD(getPhotoByInternalID:(NSString *)internalId } }]; } - + } else { NSString *errorMessage = [NSString stringWithFormat:@"Failed to load asset" " with localIdentifier %@ with no error message.", internalId]; NSError *error = RCTErrorWithMessage(errorMessage); reject(@"No asset found",@"No asset found",error); } - + }, false); } diff --git a/src/CameraRoll.ts b/src/CameraRoll.ts index 684945a..ae07f9a 100644 --- a/src/CameraRoll.ts +++ b/src/CameraRoll.ts @@ -35,6 +35,7 @@ export type GroupTypes = export type Include = | 'filename' | 'fileSize' + | 'fileExtension' | 'location' | 'imageSize' | 'playableDuration'; @@ -101,6 +102,7 @@ export type PhotoIdentifier = { group_name: string; image: { filename: string | null; + extension: string | null; uri: string; height: number; width: number;