Make CameraRoll NativeModules TurboModule-compatible

Summary:
Aside from RCTCameraRollManager, this diff makes the following NativeModules TurboModule-compatible:
- RCTAssetsLibraryRequestHandler
- RCTImagePickerManager
- RCTPhotoLibraryImageLoader

I couldn't convert CameraRollManager to a TurboModule because its NativeMoudle spec is located in fb-internal code. We should probably just move all these NativeModules outside of react-native-github. See: T54882565.

Reviewed By: PeteTheHeat

Differential Revision: D17678033

fbshipit-source-id: 4d10b7b1ad4e167bb9e46ff2bfd1559a5092e201
This commit is contained in:
Ramanpreet Nara
2019-10-01 11:15:09 -07:00
committed by Facebook Github Bot
parent 3908702de9
commit 601981a67d
6 changed files with 148 additions and 32 deletions
@@ -7,8 +7,9 @@
#import "RCTAssetsLibraryRequestHandler.h"
#import <stdatomic.h>
#import <atomic>
#import <dlfcn.h>
#import <memory>
#import <objc/runtime.h>
#import <Photos/Photos.h>
@@ -17,6 +18,12 @@
#import <React/RCTBridge.h>
#import <React/RCTNetworking.h>
#import <React/RCTUtils.h>
#import <ReactCommon/RCTTurboModule.h>
#import "RCTCameraRollPlugins.h"
@interface RCTAssetsLibraryRequestHandler() <RCTTurboModule>
@end
@implementation RCTAssetsLibraryRequestHandler
@@ -38,9 +45,9 @@ RCT_EXPORT_MODULE()
- (id)sendRequest:(NSURLRequest *)request
withDelegate:(id<RCTURLRequestDelegate>)delegate
{
__block atomic_bool cancelled = ATOMIC_VAR_INIT(NO);
auto cancelled = std::make_shared<std::atomic<bool>>(false);
void (^cancellationBlock)(void) = ^{
atomic_store(&cancelled, YES);
cancelled->store(true);
};
NSURL *requestURL = request.URL;
@@ -48,15 +55,15 @@ RCT_EXPORT_MODULE()
if (isPHUpload) {
requestURL = [NSURL URLWithString:[@"ph" stringByAppendingString:[requestURL.absoluteString substringFromIndex:RCTNetworkingPHUploadHackScheme.length]]];
}
if (!requestURL) {
NSString *const msg = [NSString stringWithFormat:@"Cannot send request without URL"];
[delegate URLRequest:cancellationBlock didCompleteWithError:RCTErrorWithMessage(msg)];
return cancellationBlock;
}
PHFetchResult<PHAsset *> *fetchResult;
if ([requestURL.scheme caseInsensitiveCompare:@"ph"] == NSOrderedSame) {
// Fetch assets using PHAsset localIdentifier (recommended)
NSString *const localIdentifier = [requestURL.absoluteString substringFromIndex:@"ph://".length];
@@ -70,7 +77,7 @@ RCT_EXPORT_MODULE()
[delegate URLRequest:cancellationBlock didCompleteWithError:RCTErrorWithMessage(msg)];
return cancellationBlock;
}
if (![fetchResult firstObject]) {
NSString *errorMessage = [NSString stringWithFormat:@"Failed to load asset"
" at URL %@ with no error message.", requestURL];
@@ -78,8 +85,8 @@ RCT_EXPORT_MODULE()
[delegate URLRequest:cancellationBlock didCompleteWithError:error];
return cancellationBlock;
}
if (atomic_load(&cancelled)) {
if (cancelled->load()) {
return cancellationBlock;
}
@@ -149,7 +156,7 @@ RCT_EXPORT_MODULE()
[delegate URLRequest:cancellationBlock didCompleteWithError:nil];
}];
}
return cancellationBlock;
}
@@ -159,3 +166,7 @@ RCT_EXPORT_MODULE()
}
@end
Class RCTAssetsLibraryRequestHandlerCls(void) {
return RCTAssetsLibraryRequestHandler.class;
}
@@ -7,6 +7,7 @@
#import "RCTCameraRollManager.h"
#import <FBReactNativeSpec/FBReactNativeSpec.h>
#import <CoreLocation/CoreLocation.h>
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@@ -21,6 +22,7 @@
#import <React/RCTLog.h>
#import <React/RCTUtils.h>
#import "RCTCameraRollPlugins.h"
#import "RCTAssetsLibraryRequestHandler.h"
@implementation RCTConvert (PHAssetCollectionSubtype)
@@ -135,7 +137,7 @@ RCT_EXPORT_METHOD(saveToCameraRoll:(NSURLRequest *)request
}
}];
};
void (^loadBlock)(void) = ^void() {
if ([type isEqualToString:@"video"]) {
inputURI = request.URL;
@@ -146,13 +148,13 @@ RCT_EXPORT_METHOD(saveToCameraRoll:(NSURLRequest *)request
reject(kErrorUnableToLoad, nil, error);
return;
}
inputImage = image;
saveBlock();
}];
}
};
requestPhotoLibraryAccess(reject, loadBlock);
}
@@ -214,13 +216,13 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params
if (groupName != nil) {
collectionFetchOptions.predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:@"localizedTitle == '%@'", groupName]];
}
requestPhotoLibraryAccess(reject, ^{
PHFetchResult<PHAssetCollection *> *const assetCollectionFetchResult = [PHAssetCollection fetchAssetCollectionsWithType:collectionType subtype:collectionSubtype options:collectionFetchOptions];
[assetCollectionFetchResult enumerateObjectsUsingBlock:^(PHAssetCollection * _Nonnull assetCollection, NSUInteger collectionIdx, BOOL * _Nonnull stopCollections) {
// Enumerate assets within the collection
PHFetchResult<PHAsset *> *const assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:assetFetchOptions];
[assetsFetchResult enumerateObjectsUsingBlock:^(PHAsset * _Nonnull asset, NSUInteger assetIdx, BOOL * _Nonnull stopAssets) {
NSString *const uri = [NSString stringWithFormat:@"ph://%@", [asset localIdentifier]];
if (afterCursor && !foundAfter) {
@@ -229,18 +231,18 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params
}
return; // skip until we get to the first one
}
// Get underlying resources of an asset - this includes files as well as details about edited PHAssets
if ([mimeTypes count] > 0) {
NSArray<PHAssetResource *> *const assetResources = [PHAssetResource assetResourcesForAsset:asset];
if (![assetResources firstObject]) {
return;
}
PHAssetResource *const _Nonnull resource = [assetResources firstObject];
CFStringRef const uti = (__bridge CFStringRef _Nonnull)(resource.uniformTypeIdentifier);
NSString *const mimeType = (NSString *)CFBridgingRelease(UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType));
BOOL __block mimeTypeFound = NO;
[mimeTypes enumerateObjectsUsingBlock:^(NSString * _Nonnull mimeTypeFilter, NSUInteger idx, BOOL * _Nonnull stop) {
if ([mimeType isEqualToString:mimeTypeFilter]) {
@@ -248,12 +250,12 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params
*stop = YES;
}
}];
if (!mimeTypeFound) {
return;
}
}
// If we've accumulated enough results to resolve a single promise
if (first == assets.count) {
*stopAssets = YES;
@@ -264,7 +266,7 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params
resolvedPromise = YES;
return;
}
NSString *const assetMediaTypeLabel = (asset.mediaType == PHAssetMediaTypeVideo
? @"video"
: (asset.mediaType == PHAssetMediaTypeImage
@@ -273,7 +275,7 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params
? @"audio"
: @"unknown")));
CLLocation *const loc = asset.location;
// A note on isStored: in the previous code that used ALAssets, isStored
// was always set to YES, probably because iCloud-synced images were never returned (?).
// To get the "isStored" information and filename, we would need to actually request the
@@ -304,7 +306,7 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params
}];
}];
}];
// If we get this far and haven't resolved the promise yet, we reached the end of the list of photos
if (!resolvedPromise) {
hasNextPage = NO;
@@ -345,3 +347,7 @@ static void checkPhotoLibraryConfig()
}
@end
Class RCTCameraRollManagerCls(void) {
return RCTCameraRollManager.class;
}
@@ -0,0 +1,43 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated by an internal plugin build system
*/
#ifdef RN_DISABLE_OSS_PLUGIN_HEADER
// FB Internal: Plugins.h is autogenerated by the build system.
#import "Plugins.h"
#else
// OSS-compatibility layer
#import <Foundation/Foundation.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wreturn-type-c-linkage"
#ifdef __cplusplus
extern "C" {
#endif
// RCTTurboModuleManagerDelegate should call this to resolve module classes.
Class RCTCameraRollClassProvider(const char *name);
// Lookup functions
Class RCTAssetsLibraryRequestHandlerCls(void);
Class RCTCameraRollManagerCls(void);
Class RCTImagePickerManagerCls(void);
Class RCTPhotoLibraryImageLoaderCls(void);
#ifdef __cplusplus
}
#endif
#pragma GCC diagnostic pop
#endif // RN_DISABLE_OSS_PLUGIN_HEADER
@@ -0,0 +1,35 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated by an internal plugin build system
*/
#ifndef RN_DISABLE_OSS_PLUGIN_HEADER
// OSS-compatibility layer
#import "RCTCameraRollPlugins.h"
#import <string>
#import <unordered_map>
Class RCTCameraRollClassProvider(const char *name) {
static std::unordered_map<std::string, Class (*)(void)> sCoreModuleClassMap = {
{"AssetsLibraryRequestHandler", RCTAssetsLibraryRequestHandlerCls},
{"CameraRollManager", RCTCameraRollManagerCls},
{"ImagePickerIOS", RCTImagePickerManagerCls},
{"PhotoLibraryImageLoader", RCTPhotoLibraryImageLoaderCls},
};
auto p = sCoreModuleClassMap.find(name);
if (p != sCoreModuleClassMap.end()) {
auto classFunc = p->second;
return classFunc();
}
return nil;
}
#endif // RN_DISABLE_OSS_PLUGIN_HEADER
@@ -8,6 +8,7 @@
#import "RCTImagePickerManager.h"
#import <FBReactNativeSpec/FBReactNativeSpec.h>
#import <MobileCoreServices/UTCoreTypes.h>
#import <UIKit/UIKit.h>
@@ -16,6 +17,8 @@
#import <React/RCTRootView.h>
#import <React/RCTUtils.h>
#import "RCTCameraRollPlugins.h"
@interface RCTImagePickerController : UIImagePickerController
@property (nonatomic, assign) BOOL unmirrorFrontFacingCamera;
@@ -26,8 +29,7 @@
@end
@interface RCTImagePickerManager () <UIImagePickerControllerDelegate, UINavigationControllerDelegate>
@interface RCTImagePickerManager () <UIImagePickerControllerDelegate, UINavigationControllerDelegate, NativeImagePickerIOSSpec>
@end
@implementation RCTImagePickerManager
@@ -78,7 +80,7 @@ RCT_EXPORT_METHOD(canUseCamera:(RCTResponseSenderBlock)callback)
callback(@[@([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])]);
}
RCT_EXPORT_METHOD(openCameraDialog:(NSDictionary *)config
RCT_EXPORT_METHOD(openCameraDialog:(JS::NativeImagePickerIOS::SpecOpenCameraDialogConfig &)config
successCallback:(RCTResponseSenderBlock)callback
cancelCallback:(RCTResponseSenderBlock)cancelCallback)
{
@@ -92,9 +94,9 @@ RCT_EXPORT_METHOD(openCameraDialog:(NSDictionary *)config
imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
NSArray<NSString *> *availableMediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
imagePicker.mediaTypes = availableMediaTypes;
imagePicker.unmirrorFrontFacingCamera = [RCTConvert BOOL:config[@"unmirrorFrontFacingCamera"]];
imagePicker.unmirrorFrontFacingCamera = config.unmirrorFrontFacingCamera() ? YES : NO;
if ([RCTConvert BOOL:config[@"videoMode"]]) {
if (config.videoMode()) {
imagePicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModeVideo;
}
@@ -103,7 +105,7 @@ RCT_EXPORT_METHOD(openCameraDialog:(NSDictionary *)config
cancelCallback:cancelCallback];
}
RCT_EXPORT_METHOD(openSelectDialog:(NSDictionary *)config
RCT_EXPORT_METHOD(openSelectDialog:(JS::NativeImagePickerIOS::SpecOpenSelectDialogConfig &)config
successCallback:(RCTResponseSenderBlock)callback
cancelCallback:(RCTResponseSenderBlock)cancelCallback)
{
@@ -117,10 +119,10 @@ RCT_EXPORT_METHOD(openSelectDialog:(NSDictionary *)config
imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
NSMutableArray<NSString *> *allowedTypes = [NSMutableArray new];
if ([RCTConvert BOOL:config[@"showImages"]]) {
if (config.showImages()) {
[allowedTypes addObject:(NSString *)kUTTypeImage];
}
if ([RCTConvert BOOL:config[@"showVideos"]]) {
if (config.showVideos()) {
[allowedTypes addObject:(NSString *)kUTTypeMovie];
}
@@ -222,4 +224,13 @@ didFinishPickingMediaWithInfo:(NSDictionary<NSString *, id> *)info
}
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
{
return std::make_shared<facebook::react::NativeImagePickerIOSSpecJSI>(self, jsInvoker);
}
@end
Class RCTImagePickerManagerCls(void) {
return RCTImagePickerManager.class;
}
@@ -8,8 +8,14 @@
#import "RCTPhotoLibraryImageLoader.h"
#import <Photos/Photos.h>
#import <React/RCTLog.h>
#import <React/RCTUtils.h>
#import <ReactCommon/RCTTurboModule.h>
#import "RCTCameraRollPlugins.h"
@interface RCTPhotoLibraryImageLoader () <RCTTurboModule>
@end
@implementation RCTPhotoLibraryImageLoader
@@ -109,3 +115,7 @@ RCT_EXPORT_MODULE()
}
@end
Class RCTPhotoLibraryImageLoaderCls(void) {
return RCTPhotoLibraryImageLoader.class;
}