yeet
This commit is contained in:
15
node_modules/react-native/Libraries/CameraRoll/RCTAssetsLibraryRequestHandler.h
generated
vendored
Normal file
15
node_modules/react-native/Libraries/CameraRoll/RCTAssetsLibraryRequestHandler.h
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTURLRequestHandler.h>
|
||||
|
||||
@class PHPhotoLibrary;
|
||||
|
||||
@interface RCTAssetsLibraryRequestHandler : NSObject <RCTURLRequestHandler>
|
||||
|
||||
@end
|
172
node_modules/react-native/Libraries/CameraRoll/RCTAssetsLibraryRequestHandler.mm
generated
vendored
Normal file
172
node_modules/react-native/Libraries/CameraRoll/RCTAssetsLibraryRequestHandler.mm
generated
vendored
Normal file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import "RCTAssetsLibraryRequestHandler.h"
|
||||
|
||||
#import <atomic>
|
||||
#import <dlfcn.h>
|
||||
#import <memory>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#import <Photos/Photos.h>
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
|
||||
#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
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
#pragma mark - RCTURLRequestHandler
|
||||
|
||||
- (BOOL)canHandleRequest:(NSURLRequest *)request
|
||||
{
|
||||
if (![PHAsset class]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [request.URL.scheme caseInsensitiveCompare:@"assets-library"] == NSOrderedSame
|
||||
|| [request.URL.scheme caseInsensitiveCompare:@"ph"] == NSOrderedSame
|
||||
|| [request.URL.scheme caseInsensitiveCompare:RCTNetworkingPHUploadHackScheme] == NSOrderedSame;
|
||||
}
|
||||
|
||||
- (id)sendRequest:(NSURLRequest *)request
|
||||
withDelegate:(id<RCTURLRequestDelegate>)delegate
|
||||
{
|
||||
auto cancelled = std::make_shared<std::atomic<bool>>(false);
|
||||
void (^cancellationBlock)(void) = ^{
|
||||
cancelled->store(true);
|
||||
};
|
||||
|
||||
NSURL *requestURL = request.URL;
|
||||
BOOL isPHUpload = [requestURL.scheme caseInsensitiveCompare:RCTNetworkingPHUploadHackScheme] == NSOrderedSame;
|
||||
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];
|
||||
fetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:@[localIdentifier] options:nil];
|
||||
} else if ([requestURL.scheme caseInsensitiveCompare:@"assets-library"] == NSOrderedSame) {
|
||||
// This is the older, deprecated way of fetching assets from assets-library
|
||||
// using the "assets-library://" protocol
|
||||
fetchResult = [PHAsset fetchAssetsWithALAssetURLs:@[requestURL] options:nil];
|
||||
} else {
|
||||
NSString *const msg = [NSString stringWithFormat:@"Cannot send request with unknown protocol: %@", requestURL];
|
||||
[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];
|
||||
NSError *error = RCTErrorWithMessage(errorMessage);
|
||||
[delegate URLRequest:cancellationBlock didCompleteWithError:error];
|
||||
return cancellationBlock;
|
||||
}
|
||||
|
||||
if (cancelled->load()) {
|
||||
return cancellationBlock;
|
||||
}
|
||||
|
||||
PHAsset *const _Nonnull asset = [fetchResult firstObject];
|
||||
|
||||
// When we're uploading a video, provide the full data but in any other case,
|
||||
// provide only the thumbnail of the video.
|
||||
if (asset.mediaType == PHAssetMediaTypeVideo && isPHUpload) {
|
||||
PHVideoRequestOptions *const requestOptions = [PHVideoRequestOptions new];
|
||||
requestOptions.networkAccessAllowed = YES;
|
||||
[[PHImageManager defaultManager] requestAVAssetForVideo:asset options:requestOptions resultHandler:^(AVAsset * _Nullable avAsset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
|
||||
NSError *error = [info objectForKey:PHImageErrorKey];
|
||||
if (error) {
|
||||
[delegate URLRequest:cancellationBlock didCompleteWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
if (![avAsset isKindOfClass:[AVURLAsset class]]) {
|
||||
error = [NSError errorWithDomain:RCTErrorDomain code:0 userInfo:
|
||||
@{
|
||||
NSLocalizedDescriptionKey: @"Unable to load AVURLAsset",
|
||||
}];
|
||||
[delegate URLRequest:cancellationBlock didCompleteWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
NSData *data = [NSData dataWithContentsOfURL:((AVURLAsset *)avAsset).URL
|
||||
options:(NSDataReadingOptions)0
|
||||
error:&error];
|
||||
if (data) {
|
||||
NSURLResponse *const response = [[NSURLResponse alloc] initWithURL:request.URL MIMEType:nil expectedContentLength:data.length textEncodingName:nil];
|
||||
[delegate URLRequest:cancellationBlock didReceiveResponse:response];
|
||||
[delegate URLRequest:cancellationBlock didReceiveData:data];
|
||||
}
|
||||
[delegate URLRequest:cancellationBlock didCompleteWithError:error];
|
||||
}];
|
||||
} else {
|
||||
// By default, allow downloading images from iCloud
|
||||
PHImageRequestOptions *const requestOptions = [PHImageRequestOptions new];
|
||||
requestOptions.networkAccessAllowed = YES;
|
||||
|
||||
[[PHImageManager defaultManager] requestImageDataForAsset:asset
|
||||
options:requestOptions
|
||||
resultHandler:^(NSData * _Nullable imageData,
|
||||
NSString * _Nullable dataUTI,
|
||||
UIImageOrientation orientation,
|
||||
NSDictionary * _Nullable info) {
|
||||
NSError *const error = [info objectForKey:PHImageErrorKey];
|
||||
if (error) {
|
||||
[delegate URLRequest:cancellationBlock didCompleteWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
NSInteger const length = [imageData length];
|
||||
CFStringRef const dataUTIStringRef = (__bridge CFStringRef _Nonnull)(dataUTI);
|
||||
CFStringRef const mimeType = UTTypeCopyPreferredTagWithClass(dataUTIStringRef, kUTTagClassMIMEType);
|
||||
|
||||
NSURLResponse *const response = [[NSURLResponse alloc] initWithURL:request.URL
|
||||
MIMEType:(__bridge NSString *)(mimeType)
|
||||
expectedContentLength:length
|
||||
textEncodingName:nil];
|
||||
CFRelease(mimeType);
|
||||
|
||||
[delegate URLRequest:cancellationBlock didReceiveResponse:response];
|
||||
|
||||
[delegate URLRequest:cancellationBlock didReceiveData:imageData];
|
||||
[delegate URLRequest:cancellationBlock didCompleteWithError:nil];
|
||||
}];
|
||||
}
|
||||
|
||||
return cancellationBlock;
|
||||
}
|
||||
|
||||
- (void)cancelRequest:(id)requestToken
|
||||
{
|
||||
((void (^)(void))requestToken)();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Class RCTAssetsLibraryRequestHandlerCls(void) {
|
||||
return RCTAssetsLibraryRequestHandler.class;
|
||||
}
|
22
node_modules/react-native/Libraries/CameraRoll/RCTCameraRollManager.h
generated
vendored
Normal file
22
node_modules/react-native/Libraries/CameraRoll/RCTCameraRollManager.h
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <Photos/Photos.h>
|
||||
|
||||
#import <React/RCTBridgeModule.h>
|
||||
#import <React/RCTConvert.h>
|
||||
|
||||
@interface RCTConvert (PHFetchOptions)
|
||||
|
||||
+ (PHFetchOptions *)PHFetchOptionsFromMediaType:(NSString *)mediaType;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface RCTCameraRollManager : NSObject <RCTBridgeModule>
|
||||
|
||||
@end
|
363
node_modules/react-native/Libraries/CameraRoll/RCTCameraRollManager.mm
generated
vendored
Normal file
363
node_modules/react-native/Libraries/CameraRoll/RCTCameraRollManager.mm
generated
vendored
Normal file
@ -0,0 +1,363 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import "RCTCameraRollManager.h"
|
||||
|
||||
#import <FBReactNativeSpec/FBReactNativeSpec.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <Photos/Photos.h>
|
||||
#import <dlfcn.h>
|
||||
#import <objc/runtime.h>
|
||||
#import <MobileCoreServices/UTType.h>
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTConvert.h>
|
||||
#import <React/RCTImageLoader.h>
|
||||
#import <React/RCTLog.h>
|
||||
#import <React/RCTUtils.h>
|
||||
|
||||
#import "RCTCameraRollPlugins.h"
|
||||
#import "RCTAssetsLibraryRequestHandler.h"
|
||||
|
||||
@implementation RCTConvert (PHAssetCollectionSubtype)
|
||||
|
||||
RCT_ENUM_CONVERTER(PHAssetCollectionSubtype, (@{
|
||||
@"album": @(PHAssetCollectionSubtypeAny),
|
||||
@"all": @(PHAssetCollectionSubtypeSmartAlbumUserLibrary),
|
||||
@"event": @(PHAssetCollectionSubtypeAlbumSyncedEvent),
|
||||
@"faces": @(PHAssetCollectionSubtypeAlbumSyncedFaces),
|
||||
@"library": @(PHAssetCollectionSubtypeSmartAlbumUserLibrary),
|
||||
@"photo-stream": @(PHAssetCollectionSubtypeAlbumMyPhotoStream), // incorrect, but legacy
|
||||
@"photostream": @(PHAssetCollectionSubtypeAlbumMyPhotoStream),
|
||||
@"saved-photos": @(PHAssetCollectionSubtypeAny), // incorrect, but legacy
|
||||
@"savedphotos": @(PHAssetCollectionSubtypeAny), // This was ALAssetsGroupSavedPhotos, seems to have no direct correspondence in PHAssetCollectionSubtype
|
||||
}), PHAssetCollectionSubtypeAny, integerValue)
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTConvert (PHFetchOptions)
|
||||
|
||||
+ (PHFetchOptions *)PHFetchOptionsFromMediaType:(NSString *)mediaType
|
||||
{
|
||||
// This is not exhaustive in terms of supported media type predicates; more can be added in the future
|
||||
NSString *const lowercase = [mediaType lowercaseString];
|
||||
|
||||
if ([lowercase isEqualToString:@"photos"]) {
|
||||
PHFetchOptions *const options = [PHFetchOptions new];
|
||||
options.predicate = [NSPredicate predicateWithFormat:@"mediaType = %d", PHAssetMediaTypeImage];
|
||||
return options;
|
||||
} else if ([lowercase isEqualToString:@"videos"]) {
|
||||
PHFetchOptions *const options = [PHFetchOptions new];
|
||||
options.predicate = [NSPredicate predicateWithFormat:@"mediaType = %d", PHAssetMediaTypeVideo];
|
||||
return options;
|
||||
} else {
|
||||
if (![lowercase isEqualToString:@"all"]) {
|
||||
RCTLogError(@"Invalid filter option: '%@'. Expected one of 'photos',"
|
||||
"'videos' or 'all'.", mediaType);
|
||||
}
|
||||
// This case includes the "all" mediatype
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTCameraRollManager() <NativeCameraRollManagerSpec>
|
||||
@end
|
||||
|
||||
@implementation RCTCameraRollManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
static NSString *const kErrorUnableToSave = @"E_UNABLE_TO_SAVE";
|
||||
static NSString *const kErrorUnableToLoad = @"E_UNABLE_TO_LOAD";
|
||||
|
||||
static NSString *const kErrorAuthRestricted = @"E_PHOTO_LIBRARY_AUTH_RESTRICTED";
|
||||
static NSString *const kErrorAuthDenied = @"E_PHOTO_LIBRARY_AUTH_DENIED";
|
||||
|
||||
typedef void (^PhotosAuthorizedBlock)(void);
|
||||
|
||||
static void requestPhotoLibraryAccess(RCTPromiseRejectBlock reject, PhotosAuthorizedBlock authorizedBlock) {
|
||||
PHAuthorizationStatus authStatus = [PHPhotoLibrary authorizationStatus];
|
||||
if (authStatus == PHAuthorizationStatusRestricted) {
|
||||
reject(kErrorAuthRestricted, @"Access to photo library is restricted", nil);
|
||||
} else if (authStatus == PHAuthorizationStatusAuthorized) {
|
||||
authorizedBlock();
|
||||
} else if (authStatus == PHAuthorizationStatusNotDetermined) {
|
||||
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
|
||||
requestPhotoLibraryAccess(reject, authorizedBlock);
|
||||
}];
|
||||
} else {
|
||||
reject(kErrorAuthDenied, @"Access to photo library was denied", nil);
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(saveToCameraRoll:(NSURLRequest *)request
|
||||
type:(NSString *)type
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
__block PHObjectPlaceholder *placeholder;
|
||||
|
||||
// We load images and videos differently.
|
||||
// Images have many custom loaders which can load images from ALAssetsLibrary URLs, PHPhotoLibrary
|
||||
// URLs, `data:` URIs, etc. Video URLs are passed directly through for now; it may be nice to support
|
||||
// more ways of loading videos in the future.
|
||||
__block NSURL *inputURI = nil;
|
||||
__block UIImage *inputImage = nil;
|
||||
|
||||
void (^saveBlock)(void) = ^void() {
|
||||
// performChanges and the completionHandler are called on
|
||||
// arbitrary threads, not the main thread - this is safe
|
||||
// for now since all JS is queued and executed on a single thread.
|
||||
// We should reevaluate this if that assumption changes.
|
||||
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
|
||||
PHAssetChangeRequest *changeRequest;
|
||||
|
||||
// Defaults to "photo". `type` is an optional param.
|
||||
if ([type isEqualToString:@"video"]) {
|
||||
changeRequest = [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:inputURI];
|
||||
} else {
|
||||
changeRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:inputImage];
|
||||
}
|
||||
|
||||
placeholder = [changeRequest placeholderForCreatedAsset];
|
||||
} completionHandler:^(BOOL success, NSError * _Nullable error) {
|
||||
if (success) {
|
||||
NSString *uri = [NSString stringWithFormat:@"ph://%@", [placeholder localIdentifier]];
|
||||
resolve(uri);
|
||||
} else {
|
||||
reject(kErrorUnableToSave, nil, error);
|
||||
}
|
||||
}];
|
||||
};
|
||||
|
||||
void (^loadBlock)(void) = ^void() {
|
||||
if ([type isEqualToString:@"video"]) {
|
||||
inputURI = request.URL;
|
||||
saveBlock();
|
||||
} else {
|
||||
[[self.bridge moduleForClass:[RCTImageLoader class]] loadImageWithURLRequest:request callback:^(NSError *error, UIImage *image) {
|
||||
if (error) {
|
||||
reject(kErrorUnableToLoad, nil, error);
|
||||
return;
|
||||
}
|
||||
|
||||
inputImage = image;
|
||||
saveBlock();
|
||||
}];
|
||||
}
|
||||
};
|
||||
|
||||
requestPhotoLibraryAccess(reject, loadBlock);
|
||||
}
|
||||
|
||||
static void RCTResolvePromise(RCTPromiseResolveBlock resolve,
|
||||
NSArray<NSDictionary<NSString *, id> *> *assets,
|
||||
BOOL hasNextPage)
|
||||
{
|
||||
if (!assets.count) {
|
||||
resolve(@{
|
||||
@"edges": assets,
|
||||
@"page_info": @{
|
||||
@"has_next_page": @NO,
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
resolve(@{
|
||||
@"edges": assets,
|
||||
@"page_info": @{
|
||||
@"start_cursor": assets[0][@"node"][@"image"][@"uri"],
|
||||
@"end_cursor": assets[assets.count - 1][@"node"][@"image"][@"uri"],
|
||||
@"has_next_page": @(hasNextPage),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(getPhotos:(JS::NativeCameraRollManager::GetPhotosParams &)params
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
checkPhotoLibraryConfig();
|
||||
|
||||
NSUInteger const first = [RCTConvert NSInteger:[NSNumber numberWithDouble:params.first()]];
|
||||
NSString *const afterCursor = [RCTConvert NSString:params.after()];
|
||||
NSString *const groupName = [RCTConvert NSString:params.groupName()];
|
||||
NSString *const groupTypes = [[RCTConvert NSString:params.groupTypes()] lowercaseString];
|
||||
NSString *const mediaType = [RCTConvert NSString:params.assetType()];
|
||||
NSArray<NSString *> *const mimeTypes = [RCTConvert NSStringArray:RCTConvertOptionalVecToArray(params.mimeTypes())];
|
||||
|
||||
// If groupTypes is "all", we want to fetch the SmartAlbum "all photos". Otherwise, all
|
||||
// other groupTypes values require the "album" collection type.
|
||||
PHAssetCollectionType const collectionType = ([groupTypes isEqualToString:@"all"]
|
||||
? PHAssetCollectionTypeSmartAlbum
|
||||
: PHAssetCollectionTypeAlbum);
|
||||
PHAssetCollectionSubtype const collectionSubtype = [RCTConvert PHAssetCollectionSubtype:groupTypes];
|
||||
|
||||
// Predicate for fetching assets within a collection
|
||||
PHFetchOptions *const assetFetchOptions = [RCTConvert PHFetchOptionsFromMediaType:mediaType];
|
||||
assetFetchOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
|
||||
|
||||
BOOL __block foundAfter = NO;
|
||||
BOOL __block hasNextPage = NO;
|
||||
BOOL __block resolvedPromise = NO;
|
||||
NSMutableArray<NSDictionary<NSString *, id> *> *assets = [NSMutableArray new];
|
||||
|
||||
// Filter collection name ("group")
|
||||
PHFetchOptions *const collectionFetchOptions = [PHFetchOptions new];
|
||||
collectionFetchOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"endDate" ascending:NO]];
|
||||
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) {
|
||||
if ([afterCursor isEqualToString:uri]) {
|
||||
foundAfter = YES;
|
||||
}
|
||||
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]) {
|
||||
mimeTypeFound = YES;
|
||||
*stop = YES;
|
||||
}
|
||||
}];
|
||||
|
||||
if (!mimeTypeFound) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If we've accumulated enough results to resolve a single promise
|
||||
if (first == assets.count) {
|
||||
*stopAssets = YES;
|
||||
*stopCollections = YES;
|
||||
hasNextPage = YES;
|
||||
RCTAssert(resolvedPromise == NO, @"Resolved the promise before we finished processing the results.");
|
||||
RCTResolvePromise(resolve, assets, hasNextPage);
|
||||
resolvedPromise = YES;
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *const assetMediaTypeLabel = (asset.mediaType == PHAssetMediaTypeVideo
|
||||
? @"video"
|
||||
: (asset.mediaType == PHAssetMediaTypeImage
|
||||
? @"image"
|
||||
: (asset.mediaType == PHAssetMediaTypeAudio
|
||||
? @"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
|
||||
// image data from the image manager. Those operations could get really expensive and
|
||||
// would definitely utilize the disk too much.
|
||||
// Thus, this field is actually not reliable.
|
||||
// Note that Android also does not return the `isStored` field at all.
|
||||
[assets addObject:@{
|
||||
@"node": @{
|
||||
@"type": assetMediaTypeLabel, // TODO: switch to mimeType?
|
||||
@"group_name": [assetCollection localizedTitle],
|
||||
@"image": @{
|
||||
@"uri": uri,
|
||||
@"height": @([asset pixelHeight]),
|
||||
@"width": @([asset pixelWidth]),
|
||||
@"isStored": @YES, // this field doesn't seem to exist on android
|
||||
@"playableDuration": @([asset duration]) // fractional seconds
|
||||
},
|
||||
@"timestamp": @(asset.creationDate.timeIntervalSince1970),
|
||||
@"location": (loc ? @{
|
||||
@"latitude": @(loc.coordinate.latitude),
|
||||
@"longitude": @(loc.coordinate.longitude),
|
||||
@"altitude": @(loc.altitude),
|
||||
@"heading": @(loc.course),
|
||||
@"speed": @(loc.speed), // speed in m/s
|
||||
} : @{})
|
||||
}
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
|
||||
// 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;
|
||||
RCTResolvePromise(resolve, assets, hasNextPage);
|
||||
resolvedPromise = YES;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(deletePhotos:(NSArray<NSString *>*)assets
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
NSArray<NSURL *> *assets_ = [RCTConvert NSURLArray:assets];
|
||||
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
|
||||
PHFetchResult<PHAsset *> *fetched =
|
||||
[PHAsset fetchAssetsWithALAssetURLs:assets_ options:nil];
|
||||
[PHAssetChangeRequest deleteAssets:fetched];
|
||||
}
|
||||
completionHandler:^(BOOL success, NSError *error) {
|
||||
if (success == YES) {
|
||||
resolve(@(success));
|
||||
}
|
||||
else {
|
||||
reject(@"Couldn't delete", @"Couldn't delete assets", error);
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
static void checkPhotoLibraryConfig()
|
||||
{
|
||||
#if RCT_DEV
|
||||
if (![[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSPhotoLibraryUsageDescription"]) {
|
||||
RCTLogError(@"NSPhotoLibraryUsageDescription key must be present in Info.plist to use camera roll.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (std::shared_ptr<facebook::react::TurboModule>)
|
||||
getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
|
||||
nativeInvoker:(std::shared_ptr<facebook::react::CallInvoker>)nativeInvoker
|
||||
perfLogger:(id<RCTTurboModulePerformanceLogger>)perfLogger
|
||||
{
|
||||
return std::make_shared<facebook::react::NativeCameraRollManagerSpecJSI>(self, jsInvoker, nativeInvoker, perfLogger);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Class RCTCameraRollManagerCls(void) {
|
||||
return RCTCameraRollManager.class;
|
||||
}
|
43
node_modules/react-native/Libraries/CameraRoll/RCTCameraRollPlugins.h
generated
vendored
Normal file
43
node_modules/react-native/Libraries/CameraRoll/RCTCameraRollPlugins.h
generated
vendored
Normal file
@ -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: FBRCTCameraRollPlugins.h is autogenerated by the build system.
|
||||
#import <RCTCameraRollApple/FBRCTCameraRollPlugins.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) __attribute__((used));
|
||||
Class RCTCameraRollManagerCls(void) __attribute__((used));
|
||||
Class RCTImagePickerManagerCls(void) __attribute__((used));
|
||||
Class RCTPhotoLibraryImageLoaderCls(void) __attribute__((used));
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#endif // RN_DISABLE_OSS_PLUGIN_HEADER
|
35
node_modules/react-native/Libraries/CameraRoll/RCTCameraRollPlugins.mm
generated
vendored
Normal file
35
node_modules/react-native/Libraries/CameraRoll/RCTCameraRollPlugins.mm
generated
vendored
Normal file
@ -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
|
12
node_modules/react-native/Libraries/CameraRoll/RCTImagePickerManager.h
generated
vendored
Normal file
12
node_modules/react-native/Libraries/CameraRoll/RCTImagePickerManager.h
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <React/RCTBridgeModule.h>
|
||||
|
||||
@interface RCTImagePickerManager : NSObject <RCTBridgeModule>
|
||||
|
||||
@end
|
264
node_modules/react-native/Libraries/CameraRoll/RCTImagePickerManager.mm
generated
vendored
Normal file
264
node_modules/react-native/Libraries/CameraRoll/RCTImagePickerManager.mm
generated
vendored
Normal file
@ -0,0 +1,264 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import "RCTImagePickerManager.h"
|
||||
|
||||
#import <FBReactNativeSpec/FBReactNativeSpec.h>
|
||||
#import <MobileCoreServices/UTCoreTypes.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <React/RCTConvert.h>
|
||||
#import <React/RCTImageStoreManager.h>
|
||||
#import <React/RCTRootView.h>
|
||||
#import <React/RCTUtils.h>
|
||||
|
||||
#import "RCTCameraRollPlugins.h"
|
||||
|
||||
@interface RCTImagePickerController : UIImagePickerController
|
||||
|
||||
@property (nonatomic, assign) BOOL unmirrorFrontFacingCamera;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTImagePickerController
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTImagePickerManager () <UIImagePickerControllerDelegate, UINavigationControllerDelegate, NativeImagePickerIOSSpec>
|
||||
@end
|
||||
|
||||
@implementation RCTImagePickerManager
|
||||
{
|
||||
NSMutableArray<UIImagePickerController *> *_pickers;
|
||||
NSMutableArray<RCTResponseSenderBlock> *_pickerCallbacks;
|
||||
NSMutableArray<RCTResponseSenderBlock> *_pickerCancelCallbacks;
|
||||
NSMutableDictionary<NSString *, NSDictionary<NSString *, id> *> *_pendingVideoInfo;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE(ImagePickerIOS);
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(cameraChanged:)
|
||||
name:@"AVCaptureDeviceDidStartRunningNotification"
|
||||
object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL)requiresMainQueueSetup
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
return dispatch_get_main_queue();
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(canRecordVideos:(RCTResponseSenderBlock)callback)
|
||||
{
|
||||
NSArray<NSString *> *availableMediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
|
||||
callback(@[@([availableMediaTypes containsObject:(NSString *)kUTTypeMovie])]);
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(canUseCamera:(RCTResponseSenderBlock)callback)
|
||||
{
|
||||
callback(@[@([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])]);
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(openCameraDialog:(JS::NativeImagePickerIOS::SpecOpenCameraDialogConfig &)config
|
||||
successCallback:(RCTResponseSenderBlock)callback
|
||||
cancelCallback:(RCTResponseSenderBlock)cancelCallback)
|
||||
{
|
||||
if (RCTRunningInAppExtension()) {
|
||||
cancelCallback(@[@"Camera access is unavailable in an app extension"]);
|
||||
return;
|
||||
}
|
||||
|
||||
RCTImagePickerController *imagePicker = [RCTImagePickerController new];
|
||||
imagePicker.delegate = self;
|
||||
imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
|
||||
NSArray<NSString *> *availableMediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
|
||||
imagePicker.mediaTypes = availableMediaTypes;
|
||||
imagePicker.unmirrorFrontFacingCamera = config.unmirrorFrontFacingCamera() ? YES : NO;
|
||||
|
||||
if (config.videoMode()) {
|
||||
imagePicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModeVideo;
|
||||
}
|
||||
|
||||
[self _presentPicker:imagePicker
|
||||
successCallback:callback
|
||||
cancelCallback:cancelCallback];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(openSelectDialog:(JS::NativeImagePickerIOS::SpecOpenSelectDialogConfig &)config
|
||||
successCallback:(RCTResponseSenderBlock)callback
|
||||
cancelCallback:(RCTResponseSenderBlock)cancelCallback)
|
||||
{
|
||||
if (RCTRunningInAppExtension()) {
|
||||
cancelCallback(@[@"Image picker is currently unavailable in an app extension"]);
|
||||
return;
|
||||
}
|
||||
|
||||
UIImagePickerController *imagePicker = [UIImagePickerController new];
|
||||
imagePicker.delegate = self;
|
||||
imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
|
||||
|
||||
NSMutableArray<NSString *> *allowedTypes = [NSMutableArray new];
|
||||
if (config.showImages()) {
|
||||
[allowedTypes addObject:(NSString *)kUTTypeImage];
|
||||
}
|
||||
if (config.showVideos()) {
|
||||
[allowedTypes addObject:(NSString *)kUTTypeMovie];
|
||||
}
|
||||
|
||||
imagePicker.mediaTypes = allowedTypes;
|
||||
|
||||
[self _presentPicker:imagePicker
|
||||
successCallback:callback
|
||||
cancelCallback:cancelCallback];
|
||||
}
|
||||
|
||||
// In iOS 13, the URLs provided when selecting videos from the library are only valid while the
|
||||
// info object provided by the delegate is retained.
|
||||
// This method provides a way to clear out all retained pending info objects.
|
||||
RCT_EXPORT_METHOD(clearAllPendingVideos)
|
||||
{
|
||||
[_pendingVideoInfo removeAllObjects];
|
||||
_pendingVideoInfo = [NSMutableDictionary new];
|
||||
}
|
||||
|
||||
// In iOS 13, the URLs provided when selecting videos from the library are only valid while the
|
||||
// info object provided by the delegate is retained.
|
||||
// This method provides a way to release the info object for a particular file url when the application
|
||||
// is done with it, for example after the video has been uploaded or copied locally.
|
||||
RCT_EXPORT_METHOD(removePendingVideo:(NSString *)url)
|
||||
{
|
||||
[_pendingVideoInfo removeObjectForKey:url];
|
||||
}
|
||||
|
||||
- (void)imagePickerController:(UIImagePickerController *)picker
|
||||
didFinishPickingMediaWithInfo:(NSDictionary<NSString *, id> *)info
|
||||
{
|
||||
NSString *mediaType = info[UIImagePickerControllerMediaType];
|
||||
BOOL isMovie = [mediaType isEqualToString:(NSString *)kUTTypeMovie];
|
||||
NSString *key = isMovie ? UIImagePickerControllerMediaURL : UIImagePickerControllerReferenceURL;
|
||||
NSURL *imageURL = info[key];
|
||||
UIImage *image = info[UIImagePickerControllerOriginalImage];
|
||||
NSNumber *width = 0;
|
||||
NSNumber *height = 0;
|
||||
if (image) {
|
||||
height = @(image.size.height);
|
||||
width = @(image.size.width);
|
||||
}
|
||||
if (imageURL) {
|
||||
NSString *imageURLString = imageURL.absoluteString;
|
||||
// In iOS 13, video URLs are only valid while info dictionary is retained
|
||||
if (@available(iOS 13.0, *)) {
|
||||
if (isMovie) {
|
||||
_pendingVideoInfo[imageURLString] = info;
|
||||
}
|
||||
}
|
||||
|
||||
[self _dismissPicker:picker args:@[imageURLString, RCTNullIfNil(height), RCTNullIfNil(width)]];
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a newly taken image, and doesn't have a URL yet.
|
||||
// We need to save it to the image store first.
|
||||
UIImage *originalImage = info[UIImagePickerControllerOriginalImage];
|
||||
|
||||
// WARNING: Using ImageStoreManager may cause a memory leak because the
|
||||
// image isn't automatically removed from store once we're done using it.
|
||||
[_bridge.imageStoreManager storeImage:originalImage withBlock:^(NSString *tempImageTag) {
|
||||
[self _dismissPicker:picker args:tempImageTag ? @[tempImageTag, RCTNullIfNil(height), RCTNullIfNil(width)] : nil];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
|
||||
{
|
||||
[self _dismissPicker:picker args:nil];
|
||||
}
|
||||
|
||||
- (void)_presentPicker:(UIImagePickerController *)imagePicker
|
||||
successCallback:(RCTResponseSenderBlock)callback
|
||||
cancelCallback:(RCTResponseSenderBlock)cancelCallback
|
||||
{
|
||||
if (!_pickers) {
|
||||
_pickers = [NSMutableArray new];
|
||||
_pickerCallbacks = [NSMutableArray new];
|
||||
_pickerCancelCallbacks = [NSMutableArray new];
|
||||
_pendingVideoInfo = [NSMutableDictionary new];
|
||||
}
|
||||
|
||||
[_pickers addObject:imagePicker];
|
||||
[_pickerCallbacks addObject:callback];
|
||||
[_pickerCancelCallbacks addObject:cancelCallback];
|
||||
|
||||
UIViewController *rootViewController = RCTPresentedViewController();
|
||||
[rootViewController presentViewController:imagePicker animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)_dismissPicker:(UIImagePickerController *)picker args:(NSArray *)args
|
||||
{
|
||||
NSUInteger index = [_pickers indexOfObject:picker];
|
||||
if (index == NSNotFound) {
|
||||
// This happens if the user selects multiple items in succession.
|
||||
return;
|
||||
}
|
||||
|
||||
RCTResponseSenderBlock successCallback = _pickerCallbacks[index];
|
||||
RCTResponseSenderBlock cancelCallback = _pickerCancelCallbacks[index];
|
||||
|
||||
[_pickers removeObjectAtIndex:index];
|
||||
[_pickerCallbacks removeObjectAtIndex:index];
|
||||
[_pickerCancelCallbacks removeObjectAtIndex:index];
|
||||
|
||||
UIViewController *rootViewController = RCTPresentedViewController();
|
||||
[rootViewController dismissViewControllerAnimated:YES completion:nil];
|
||||
|
||||
if (args) {
|
||||
successCallback(args);
|
||||
} else {
|
||||
cancelCallback(@[]);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cameraChanged:(NSNotification *)notification
|
||||
{
|
||||
for (UIImagePickerController *picker in _pickers) {
|
||||
if (picker.sourceType != UIImagePickerControllerSourceTypeCamera) {
|
||||
continue;
|
||||
}
|
||||
if ([picker isKindOfClass:[RCTImagePickerController class]]
|
||||
&& ((RCTImagePickerController *)picker).unmirrorFrontFacingCamera
|
||||
&& picker.cameraDevice == UIImagePickerControllerCameraDeviceFront) {
|
||||
picker.cameraViewTransform = CGAffineTransformScale(CGAffineTransformIdentity, -1, 1);
|
||||
} else {
|
||||
picker.cameraViewTransform = CGAffineTransformIdentity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (std::shared_ptr<facebook::react::TurboModule>)
|
||||
getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
|
||||
nativeInvoker:(std::shared_ptr<facebook::react::CallInvoker>)nativeInvoker
|
||||
perfLogger:(id<RCTTurboModulePerformanceLogger>)perfLogger
|
||||
{
|
||||
return std::make_shared<facebook::react::NativeImagePickerIOSSpecJSI>(self, jsInvoker, nativeInvoker, perfLogger);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Class RCTImagePickerManagerCls(void) {
|
||||
return RCTImagePickerManager.class;
|
||||
}
|
12
node_modules/react-native/Libraries/CameraRoll/RCTPhotoLibraryImageLoader.h
generated
vendored
Normal file
12
node_modules/react-native/Libraries/CameraRoll/RCTPhotoLibraryImageLoader.h
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <React/RCTImageLoader.h>
|
||||
|
||||
@interface RCTPhotoLibraryImageLoader : NSObject <RCTImageURLLoader>
|
||||
|
||||
@end
|
121
node_modules/react-native/Libraries/CameraRoll/RCTPhotoLibraryImageLoader.mm
generated
vendored
Normal file
121
node_modules/react-native/Libraries/CameraRoll/RCTPhotoLibraryImageLoader.mm
generated
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#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
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
#pragma mark - RCTImageLoader
|
||||
|
||||
- (BOOL)canLoadImageURL:(NSURL *)requestURL
|
||||
{
|
||||
if (![PHAsset class]) {
|
||||
return NO;
|
||||
}
|
||||
return [requestURL.scheme caseInsensitiveCompare:@"assets-library"] == NSOrderedSame ||
|
||||
[requestURL.scheme caseInsensitiveCompare:@"ph"] == NSOrderedSame;
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(RCTResizeMode)resizeMode
|
||||
progressHandler:(RCTImageLoaderProgressBlock)progressHandler
|
||||
partialLoadHandler:(RCTImageLoaderPartialLoadBlock)partialLoadHandler
|
||||
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
{
|
||||
// Using PhotoKit for iOS 8+
|
||||
// The 'ph://' prefix is used by FBMediaKit to differentiate between
|
||||
// assets-library. It is prepended to the local ID so that it is in the
|
||||
// form of an NSURL which is what assets-library uses.
|
||||
NSString *assetID = @"";
|
||||
PHFetchResult *results;
|
||||
if (!imageURL) {
|
||||
completionHandler(RCTErrorWithMessage(@"Cannot load a photo library asset with no URL"), nil);
|
||||
return ^{};
|
||||
} else if ([imageURL.scheme caseInsensitiveCompare:@"assets-library"] == NSOrderedSame) {
|
||||
assetID = [imageURL absoluteString];
|
||||
results = [PHAsset fetchAssetsWithALAssetURLs:@[imageURL] options:nil];
|
||||
} else {
|
||||
assetID = [imageURL.absoluteString substringFromIndex:@"ph://".length];
|
||||
results = [PHAsset fetchAssetsWithLocalIdentifiers:@[assetID] options:nil];
|
||||
}
|
||||
if (results.count == 0) {
|
||||
NSString *errorText = [NSString stringWithFormat:@"Failed to fetch PHAsset with local identifier %@ with no error message.", assetID];
|
||||
completionHandler(RCTErrorWithMessage(errorText), nil);
|
||||
return ^{};
|
||||
}
|
||||
|
||||
PHAsset *asset = [results firstObject];
|
||||
PHImageRequestOptions *imageOptions = [PHImageRequestOptions new];
|
||||
|
||||
// Allow PhotoKit to fetch images from iCloud
|
||||
imageOptions.networkAccessAllowed = YES;
|
||||
|
||||
if (progressHandler) {
|
||||
imageOptions.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary<NSString *, id> *info) {
|
||||
static const double multiplier = 1e6;
|
||||
progressHandler(progress * multiplier, multiplier);
|
||||
};
|
||||
}
|
||||
|
||||
// Note: PhotoKit defaults to a deliveryMode of PHImageRequestOptionsDeliveryModeOpportunistic
|
||||
// which means it may call back multiple times - we probably don't want that
|
||||
imageOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
|
||||
|
||||
BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
|
||||
CGSize targetSize;
|
||||
if (useMaximumSize) {
|
||||
targetSize = PHImageManagerMaximumSize;
|
||||
imageOptions.resizeMode = PHImageRequestOptionsResizeModeNone;
|
||||
} else {
|
||||
targetSize = CGSizeApplyAffineTransform(size, CGAffineTransformMakeScale(scale, scale));
|
||||
imageOptions.resizeMode = PHImageRequestOptionsResizeModeFast;
|
||||
}
|
||||
|
||||
PHImageContentMode contentMode = PHImageContentModeAspectFill;
|
||||
if (resizeMode == RCTResizeModeContain) {
|
||||
contentMode = PHImageContentModeAspectFit;
|
||||
}
|
||||
|
||||
PHImageRequestID requestID =
|
||||
[[PHImageManager defaultManager] requestImageForAsset:asset
|
||||
targetSize:targetSize
|
||||
contentMode:contentMode
|
||||
options:imageOptions
|
||||
resultHandler:^(UIImage *result, NSDictionary<NSString *, id> *info) {
|
||||
if (result) {
|
||||
completionHandler(nil, result);
|
||||
} else {
|
||||
completionHandler(info[PHImageErrorKey], nil);
|
||||
}
|
||||
}];
|
||||
|
||||
return ^{
|
||||
[[PHImageManager defaultManager] cancelImageRequest:requestID];
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Class RCTPhotoLibraryImageLoaderCls(void) {
|
||||
return RCTPhotoLibraryImageLoader.class;
|
||||
}
|
Reference in New Issue
Block a user