mirror of
https://github.com/status-im/react-native.git
synced 2025-01-10 01:25:39 +00:00
e1577df1fd
Summary: To make React Native play nicely with our internal build infrastructure we need to properly namespace all of our header includes. Where previously you could do `#import "RCTBridge.h"`, you must now write this as `#import <React/RCTBridge.h>`. If your xcode project still has a custom header include path, both variants will likely continue to work, but for new projects, we're defaulting the header include path to `$(BUILT_PRODUCTS_DIR)/usr/local/include`, where the React and CSSLayout targets will copy a subset of headers too. To make Xcode copy headers phase work properly, you may need to add React as an explicit dependency to your app's scheme and disable "parallelize build". Reviewed By: mmmulani Differential Revision: D4213120 fbshipit-source-id: 84a32a4b250c27699e6795f43584f13d594a9a82
241 lines
8.0 KiB
Objective-C
241 lines
8.0 KiB
Objective-C
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*/
|
|
|
|
#import "RCTCameraRollManager.h"
|
|
|
|
#import <CoreLocation/CoreLocation.h>
|
|
#import <Foundation/Foundation.h>
|
|
#import <UIKit/UIKit.h>
|
|
|
|
#import <React/RCTBridge.h>
|
|
#import <React/RCTConvert.h>
|
|
#import <React/RCTImageLoader.h>
|
|
#import <React/RCTLog.h>
|
|
#import <React/RCTUtils.h>
|
|
|
|
#import "RCTAssetsLibraryRequestHandler.h"
|
|
|
|
@implementation RCTConvert (ALAssetGroup)
|
|
|
|
RCT_ENUM_CONVERTER(ALAssetsGroupType, (@{
|
|
|
|
// New values
|
|
@"album": @(ALAssetsGroupAlbum),
|
|
@"all": @(ALAssetsGroupAll),
|
|
@"event": @(ALAssetsGroupEvent),
|
|
@"faces": @(ALAssetsGroupFaces),
|
|
@"library": @(ALAssetsGroupLibrary),
|
|
@"photo-stream": @(ALAssetsGroupPhotoStream),
|
|
@"saved-photos": @(ALAssetsGroupSavedPhotos),
|
|
|
|
// Legacy values
|
|
@"Album": @(ALAssetsGroupAlbum),
|
|
@"All": @(ALAssetsGroupAll),
|
|
@"Event": @(ALAssetsGroupEvent),
|
|
@"Faces": @(ALAssetsGroupFaces),
|
|
@"Library": @(ALAssetsGroupLibrary),
|
|
@"PhotoStream": @(ALAssetsGroupPhotoStream),
|
|
@"SavedPhotos": @(ALAssetsGroupSavedPhotos),
|
|
|
|
}), ALAssetsGroupSavedPhotos, integerValue)
|
|
|
|
+ (ALAssetsFilter *)ALAssetsFilter:(id)json
|
|
{
|
|
static NSDictionary<NSString *, ALAssetsFilter *> *options;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
options = @{
|
|
|
|
// New values
|
|
@"photos": [ALAssetsFilter allPhotos],
|
|
@"videos": [ALAssetsFilter allVideos],
|
|
@"all": [ALAssetsFilter allAssets],
|
|
|
|
// Legacy values
|
|
@"Photos": [ALAssetsFilter allPhotos],
|
|
@"Videos": [ALAssetsFilter allVideos],
|
|
@"All": [ALAssetsFilter allAssets],
|
|
};
|
|
});
|
|
|
|
ALAssetsFilter *filter = options[json ?: @"photos"];
|
|
if (!filter) {
|
|
RCTLogError(@"Invalid filter option: '%@'. Expected one of 'photos',"
|
|
"'videos' or 'all'.", json);
|
|
}
|
|
return filter ?: [ALAssetsFilter allPhotos];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation RCTCameraRollManager
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
@synthesize bridge = _bridge;
|
|
|
|
NSString *const RCTErrorUnableToLoad = @"E_UNABLE_TO_LOAD";
|
|
NSString *const RCTErrorUnableToSave = @"E_UNABLE_TO_SAVE";
|
|
|
|
RCT_EXPORT_METHOD(saveToCameraRoll:(NSURLRequest *)request
|
|
type:(NSString *)type
|
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
reject:(RCTPromiseRejectBlock)reject)
|
|
{
|
|
if ([type isEqualToString:@"video"]) {
|
|
// It's unclear if writeVideoAtPathToSavedPhotosAlbum is thread-safe
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[self->_bridge.assetsLibrary writeVideoAtPathToSavedPhotosAlbum:request.URL completionBlock:^(NSURL *assetURL, NSError *saveError) {
|
|
if (saveError) {
|
|
reject(RCTErrorUnableToSave, nil, saveError);
|
|
} else {
|
|
resolve(assetURL.absoluteString);
|
|
}
|
|
}];
|
|
});
|
|
} else {
|
|
[_bridge.imageLoader loadImageWithURLRequest:request
|
|
callback:^(NSError *loadError, UIImage *loadedImage) {
|
|
if (loadError) {
|
|
reject(RCTErrorUnableToLoad, nil, loadError);
|
|
return;
|
|
}
|
|
// It's unclear if writeImageToSavedPhotosAlbum is thread-safe
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[self->_bridge.assetsLibrary writeImageToSavedPhotosAlbum:loadedImage.CGImage metadata:nil completionBlock:^(NSURL *assetURL, NSError *saveError) {
|
|
if (saveError) {
|
|
RCTLogWarn(@"Error saving cropped image: %@", saveError);
|
|
reject(RCTErrorUnableToSave, nil, saveError);
|
|
} else {
|
|
resolve(assetURL.absoluteString);
|
|
}
|
|
}];
|
|
});
|
|
}];
|
|
}
|
|
}
|
|
|
|
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:(NSDictionary *)params
|
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
reject:(RCTPromiseRejectBlock)reject)
|
|
{
|
|
checkPhotoLibraryConfig();
|
|
|
|
NSUInteger first = [RCTConvert NSInteger:params[@"first"]];
|
|
NSString *afterCursor = [RCTConvert NSString:params[@"after"]];
|
|
NSString *groupName = [RCTConvert NSString:params[@"groupName"]];
|
|
ALAssetsFilter *assetType = [RCTConvert ALAssetsFilter:params[@"assetType"]];
|
|
ALAssetsGroupType groupTypes = [RCTConvert ALAssetsGroupType:params[@"groupTypes"]];
|
|
|
|
BOOL __block foundAfter = NO;
|
|
BOOL __block hasNextPage = NO;
|
|
BOOL __block resolvedPromise = NO;
|
|
NSMutableArray<NSDictionary<NSString *, id> *> *assets = [NSMutableArray new];
|
|
|
|
[_bridge.assetsLibrary enumerateGroupsWithTypes:groupTypes usingBlock:^(ALAssetsGroup *group, BOOL *stopGroups) {
|
|
if (group && (groupName == nil || [groupName isEqualToString:[group valueForProperty:ALAssetsGroupPropertyName]])) {
|
|
|
|
[group setAssetsFilter:assetType];
|
|
[group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stopAssets) {
|
|
if (result) {
|
|
NSString *uri = ((NSURL *)[result valueForProperty:ALAssetPropertyAssetURL]).absoluteString;
|
|
if (afterCursor && !foundAfter) {
|
|
if ([afterCursor isEqualToString:uri]) {
|
|
foundAfter = YES;
|
|
}
|
|
return; // Skip until we get to the first one
|
|
}
|
|
if (first == assets.count) {
|
|
*stopAssets = YES;
|
|
*stopGroups = YES;
|
|
hasNextPage = YES;
|
|
RCTAssert(resolvedPromise == NO, @"Resolved the promise before we finished processing the results.");
|
|
RCTResolvePromise(resolve, assets, hasNextPage);
|
|
resolvedPromise = YES;
|
|
return;
|
|
}
|
|
CGSize dimensions = [result defaultRepresentation].dimensions;
|
|
CLLocation *loc = [result valueForProperty:ALAssetPropertyLocation];
|
|
NSDate *date = [result valueForProperty:ALAssetPropertyDate];
|
|
NSString *filename = [result defaultRepresentation].filename;
|
|
[assets addObject:@{
|
|
@"node": @{
|
|
@"type": [result valueForProperty:ALAssetPropertyType],
|
|
@"group_name": [group valueForProperty:ALAssetsGroupPropertyName],
|
|
@"image": @{
|
|
@"uri": uri,
|
|
@"filename" : filename,
|
|
@"height": @(dimensions.height),
|
|
@"width": @(dimensions.width),
|
|
@"isStored": @YES,
|
|
},
|
|
@"timestamp": @(date.timeIntervalSince1970),
|
|
@"location": loc ? @{
|
|
@"latitude": @(loc.coordinate.latitude),
|
|
@"longitude": @(loc.coordinate.longitude),
|
|
@"altitude": @(loc.altitude),
|
|
@"heading": @(loc.course),
|
|
@"speed": @(loc.speed),
|
|
} : @{},
|
|
}
|
|
}];
|
|
}
|
|
}];
|
|
}
|
|
|
|
if (!group) {
|
|
// Sometimes the enumeration continues even if we set stop above, so we guard against resolving the promise
|
|
// multiple times here.
|
|
if (!resolvedPromise) {
|
|
RCTResolvePromise(resolve, assets, hasNextPage);
|
|
resolvedPromise = YES;
|
|
}
|
|
}
|
|
} failureBlock:^(NSError *error) {
|
|
if (error.code != ALAssetsLibraryAccessUserDeniedError) {
|
|
RCTLogError(@"Failure while iterating through asset groups %@", error);
|
|
}
|
|
reject(RCTErrorUnableToLoad, nil, 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
|
|
}
|
|
|
|
@end
|