- support capture/retake mode on the full screen camera
- make the shouldSaveToCameraRoll camera capture flag usable (on iOS for now) so it calls the completion if it’s not enabled - expose a new method saveImageURLToCameraRoll that is used in the retake mode so the image can be saved to the gallery
This commit is contained in:
parent
7ea04a2277
commit
9ca33bfdc8
|
@ -12,7 +12,7 @@
|
|||
#import "UIView+React.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "CKCameraOverlayView.h"
|
||||
|
||||
#import "CKGalleryManager.h"
|
||||
|
||||
static void * CapturingStillImageContext = &CapturingStillImageContext;
|
||||
static void * SessionRunningContext = &SessionRunningContext;
|
||||
|
@ -505,31 +505,23 @@ RCT_ENUM_CONVERTER(CKCameraZoomMode, (@{
|
|||
imageInfoDict[@"size"] = [NSNumber numberWithInteger:imageData.length];
|
||||
|
||||
if (shouldSaveToCameraRoll) {
|
||||
[self saveImageToCameraRoll:imageData temporaryFileURL:temporaryFileURL block:^(BOOL success) {
|
||||
[CKGalleryManager saveImageToCameraRoll:imageData temporaryFileURL:temporaryFileURL fetchOptions:self.fetchOptions block:^(BOOL success, NSString *localIdentifier) {
|
||||
if (success) {
|
||||
|
||||
PHFetchResult *fetchResult = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:self.fetchOptions];
|
||||
PHAsset *lastImageAsset = [fetchResult firstObject];
|
||||
|
||||
if (lastImageAsset.localIdentifier) {
|
||||
imageInfoDict[@"id"] = lastImageAsset.localIdentifier;
|
||||
if (localIdentifier) {
|
||||
imageInfoDict[@"id"] = localIdentifier;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (block) {
|
||||
block(imageInfoDict);
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
//NSLog( @"Could not save to camera roll");
|
||||
}
|
||||
}];
|
||||
} else if (block) {
|
||||
block(imageInfoDict);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
@ -614,54 +606,6 @@ RCT_ENUM_CONVERTER(CKCameraZoomMode, (@{
|
|||
} );
|
||||
}
|
||||
|
||||
|
||||
|
||||
-(void)saveImageToCameraRoll:(NSData*)imageData
|
||||
temporaryFileURL:(NSURL*)temporaryFileURL
|
||||
block:(CallbackBlock)block{
|
||||
|
||||
// To preserve the metadata, we create an asset from the JPEG NSData representation.
|
||||
// Note that creating an asset from a UIImage discards the metadata.
|
||||
// In iOS 9, we can use -[PHAssetCreationRequest addResourceWithType:data:options].
|
||||
// In iOS 8, we save the image to a temporary file and use +[PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:].
|
||||
if ( [PHAssetCreationRequest class] ) {
|
||||
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
|
||||
[[PHAssetCreationRequest creationRequestForAsset] addResourceWithType:PHAssetResourceTypePhoto data:imageData options:nil];
|
||||
|
||||
|
||||
|
||||
} completionHandler:^( BOOL success, NSError *error ) {
|
||||
if ( ! success ) {
|
||||
//NSLog( @"Error occurred while saving image to photo library: %@", error );
|
||||
block(NO);
|
||||
}
|
||||
else {
|
||||
block(YES);
|
||||
}
|
||||
}];
|
||||
}
|
||||
else {
|
||||
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
|
||||
NSError *error = nil;
|
||||
if ( error ) {
|
||||
//NSLog( @"Error occured while writing image data to a temporary file: %@", error );
|
||||
}
|
||||
else {
|
||||
[PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:temporaryFileURL];
|
||||
}
|
||||
} completionHandler:^( BOOL success, NSError *error ) {
|
||||
if ( ! success ) {
|
||||
//NSLog( @"Error occurred while saving image to photo library: %@", error );
|
||||
block(NO);
|
||||
}
|
||||
else {
|
||||
block(YES);
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-(NSURL*)saveToTmpFolder:(NSData*)data {
|
||||
NSString *temporaryFileName = [NSProcessInfo processInfo].globallyUniqueString;
|
||||
NSString *temporaryFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[temporaryFileName stringByAppendingPathExtension:@"jpg"]];
|
||||
|
|
|
@ -7,15 +7,17 @@
|
|||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Photos/Photos.h>
|
||||
#import "RCTBridgeModule.h"
|
||||
#import "CKCamera.h"
|
||||
|
||||
typedef void (^CallbackGalleryBlock)(BOOL success, NSString *encodeImage);
|
||||
typedef void (^CallbackGalleryAuthorizationStatus)(BOOL isAuthorized);
|
||||
typedef void (^SaveBlock)(BOOL success, NSString *localIdentifier);
|
||||
|
||||
@interface CKGalleryManager : NSObject <RCTBridgeModule>
|
||||
|
||||
|
||||
+(void)deviceGalleryAuthorizationStatus:(CallbackGalleryAuthorizationStatus)callback;
|
||||
|
||||
+(void)saveImageToCameraRoll:(NSData*)imageData temporaryFileURL:(NSURL*)temporaryFileURL fetchOptions:(PHFetchOptions*)fetchOptions block:(SaveBlock)block;
|
||||
|
||||
@end
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
#import "CKGalleryManager.h"
|
||||
#import <Photos/Photos.h>
|
||||
#import "RCTConvert.h"
|
||||
#import "CKGalleryViewManager.h"
|
||||
|
||||
|
@ -289,6 +288,66 @@ RCT_EXPORT_METHOD(requestDevicePhotosAuthorization:(RCTPromiseResolveBlock)resol
|
|||
return @NO;
|
||||
}
|
||||
|
||||
+(NSString*)getImageLocalIdentifierForFetchOptions:(PHFetchOptions*)fetchOption {
|
||||
PHFetchResult *fetchResult = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:fetchOption];
|
||||
PHAsset *lastImageAsset = [fetchResult firstObject];
|
||||
return lastImageAsset.localIdentifier;
|
||||
}
|
||||
|
||||
+(void)saveImageToCameraRoll:(NSData*)imageData temporaryFileURL:(NSURL*)temporaryFileURL fetchOptions:(PHFetchOptions*)fetchOptions block:(SaveBlock)block {
|
||||
// To preserve the metadata, we create an asset from the JPEG NSData representation.
|
||||
// Note that creating an asset from a UIImage discards the metadata.
|
||||
// In iOS 9, we can use -[PHAssetCreationRequest addResourceWithType:data:options].
|
||||
// In iOS 8, we save the image to a temporary file and use +[PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:].
|
||||
if ( [PHAssetCreationRequest class] && imageData) {
|
||||
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
|
||||
[[PHAssetCreationRequest creationRequestForAsset] addResourceWithType:PHAssetResourceTypePhoto data:imageData options:nil];
|
||||
} completionHandler:^( BOOL success, NSError *error ) {
|
||||
NSString *localIdentifier = [CKGalleryManager getImageLocalIdentifierForFetchOptions:fetchOptions];
|
||||
if ( ! success ) {
|
||||
block(NO, localIdentifier);
|
||||
}
|
||||
else {
|
||||
block(YES, localIdentifier);
|
||||
}
|
||||
}];
|
||||
}
|
||||
else {
|
||||
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
|
||||
[PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:temporaryFileURL];
|
||||
} completionHandler:^( BOOL success, NSError *error ) {
|
||||
NSString *localIdentifier = [CKGalleryManager getImageLocalIdentifierForFetchOptions:fetchOptions];
|
||||
if ( ! success ) {
|
||||
block(NO, localIdentifier);
|
||||
}
|
||||
else {
|
||||
block(YES, localIdentifier);
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
+(void)saveImageURLToCameraRoll:(NSString*)temporaryFileURL fetchOptions:(PHFetchOptions*)fetchOptions block:(SaveBlock)block {
|
||||
NSURL *imageURL = [NSURL URLWithString:temporaryFileURL];
|
||||
if(!imageURL) {
|
||||
block(NO, nil);
|
||||
return;
|
||||
}
|
||||
[CKGalleryManager saveImageToCameraRoll:nil temporaryFileURL:imageURL fetchOptions:fetchOptions block:block];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(saveImageURLToCameraRoll:(NSString*)imageURL
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject) {
|
||||
[CKGalleryManager saveImageURLToCameraRoll:imageURL fetchOptions:self.fetchOptions block:^(BOOL success, NSString *localIdentifier) {
|
||||
if (resolve) {
|
||||
NSMutableDictionary *result = [NSMutableDictionary dictionaryWithDictionary:@{@"success": @(success)}];
|
||||
if(localIdentifier) {
|
||||
result[@"id"] = localIdentifier;
|
||||
}
|
||||
resolve(result);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -1,24 +1,33 @@
|
|||
import React, { Component } from 'react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
TouchableOpacity,
|
||||
Image
|
||||
Image,
|
||||
NativeModules,
|
||||
Platform
|
||||
} from 'react-native';
|
||||
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
import CameraKitCamera from './../CameraKitCamera';
|
||||
|
||||
const IsIOS = Platform.OS === 'ios';
|
||||
const CKGallery = IsIOS ? NativeModules.CKGalleryManager : null;
|
||||
|
||||
const FLASH_MODE_AUTO = 'auto';
|
||||
const FLASH_MODE_ON = 'on';
|
||||
const FLASH_MODE_OFF = 'off';
|
||||
|
||||
export default class CameraScreenBase extends Component {
|
||||
|
||||
static propTypes = {
|
||||
allowCaptureRetake: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
allowCaptureRetake: false,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.currentFlashArrayPosition = 0;
|
||||
|
@ -46,7 +55,6 @@ export default class CameraScreenBase extends Component {
|
|||
};
|
||||
this.onSetFlash = this.onSetFlash.bind(this);
|
||||
this.onSwitchCameraPressed = this.onSwitchCameraPressed.bind(this);
|
||||
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -62,6 +70,10 @@ export default class CameraScreenBase extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
isCaptureRetakeMode() {
|
||||
return !!(this.props.allowCaptureRetake && !_.isUndefined(this.state.imageCaptured));
|
||||
}
|
||||
|
||||
getCameraOptions() {
|
||||
const cameraOptions = {
|
||||
flashMode: 'auto',
|
||||
|
@ -81,7 +93,7 @@ export default class CameraScreenBase extends Component {
|
|||
}
|
||||
|
||||
renderFlashButton() {
|
||||
return (
|
||||
return !this.isCaptureRetakeMode() &&
|
||||
<TouchableOpacity style={{ paddingHorizontal: 15 }} onPress={() => this.onSetFlash(FLASH_MODE_AUTO)}>
|
||||
<Image
|
||||
style={{ flex: 1, justifyContent: 'center' }}
|
||||
|
@ -89,25 +101,17 @@ export default class CameraScreenBase extends Component {
|
|||
resizeMode={Image.resizeMode.contain}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
renderSwitchCameraButton() {
|
||||
if (this.props.cameraFlipImage) {
|
||||
return (
|
||||
<TouchableOpacity style={{ paddingHorizontal: 15 }} onPress={this.onSwitchCameraPressed}>
|
||||
<Image
|
||||
style={{ flex: 1, justifyContent: 'center' }}
|
||||
source={this.props.cameraFlipImage}
|
||||
resizeMode={Image.resizeMode.contain}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
return (this.props.cameraFlipImage && !this.isCaptureRetakeMode()) &&
|
||||
<TouchableOpacity style={{ paddingHorizontal: 15 }} onPress={this.onSwitchCameraPressed}>
|
||||
<Image
|
||||
style={{ flex: 1, justifyContent: 'center' }}
|
||||
source={this.props.cameraFlipImage}
|
||||
resizeMode={Image.resizeMode.contain}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
|
||||
renderTopButtons() {
|
||||
|
@ -122,11 +126,18 @@ export default class CameraScreenBase extends Component {
|
|||
renderCamera() {
|
||||
return (
|
||||
<View style={styles.cameraContainer}>
|
||||
<CameraKitCamera
|
||||
ref={(cam) => this.camera = cam}
|
||||
style={{ flex: 1, justifyContent: 'flex-end' }}
|
||||
cameraOptions={this.state.cameraOptions}
|
||||
/>
|
||||
{
|
||||
this.isCaptureRetakeMode() ?
|
||||
<Image
|
||||
style={{ flex: 1, justifyContent: 'flex-end'}}
|
||||
source={{uri: this.state.imageCaptured.uri}}
|
||||
/> :
|
||||
<CameraKitCamera
|
||||
ref={(cam) => this.camera = cam}
|
||||
style={{ flex: 1, justifyContent: 'flex-end' }}
|
||||
cameraOptions={this.state.cameraOptions}
|
||||
/>
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
@ -142,39 +153,23 @@ export default class CameraScreenBase extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
renderCaptureButton() {
|
||||
if (this.props.captureButtonImage) {
|
||||
return (
|
||||
<View style={styles.captureButtonContainer}>
|
||||
<TouchableOpacity
|
||||
onPress={() => this.onCaptureImagePressed()}
|
||||
return (this.props.captureButtonImage && !this.isCaptureRetakeMode()) &&
|
||||
<View style={styles.captureButtonContainer}>
|
||||
<TouchableOpacity
|
||||
onPress={() => this.onCaptureImagePressed()}
|
||||
>
|
||||
<Image
|
||||
style={styles.captureButton}
|
||||
source={this.props.captureButtonImage}
|
||||
resizeMode={'contain'}
|
||||
>
|
||||
<Image
|
||||
style={styles.captureButton}
|
||||
source={this.props.captureButtonImage}
|
||||
resizeMode={'contain'}>
|
||||
|
||||
<Text style={styles.captureNumber}>
|
||||
{this.numberOfImagesTaken()}
|
||||
</Text>
|
||||
|
||||
</Image>
|
||||
</TouchableOpacity>
|
||||
</View >
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderBootomContainerGap() {
|
||||
return (
|
||||
<View
|
||||
style={styles.bottomContainerGap}
|
||||
>
|
||||
</View>
|
||||
);
|
||||
|
||||
<Text style={styles.captureNumber}>
|
||||
{this.numberOfImagesTaken()}
|
||||
</Text>
|
||||
</Image>
|
||||
</TouchableOpacity>
|
||||
</View >
|
||||
}
|
||||
|
||||
renderRatioStrip() {
|
||||
|
@ -196,26 +191,56 @@ export default class CameraScreenBase extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
onButtonPressed(type) {
|
||||
this.props.onBottomButtonPressed({ type, captureImages: this.state.captureImages })
|
||||
sendBottomButtonPressedAction(type, captureRetakeMode, image) {
|
||||
if(this.props.onBottomButtonPressed) {
|
||||
this.props.onBottomButtonPressed({ type, captureImages: this.state.captureImages, captureRetakeMode, image })
|
||||
}
|
||||
}
|
||||
|
||||
async onButtonPressed(type) {
|
||||
const captureRetakeMode = this.isCaptureRetakeMode();
|
||||
if (captureRetakeMode) {
|
||||
if(type === 'left') {
|
||||
this.setState({imageCaptured: undefined});
|
||||
}
|
||||
if(type === 'right') {
|
||||
if(CKGallery !== null) {
|
||||
const result = await CKGallery.saveImageURLToCameraRoll(this.state.imageCaptured.uri);
|
||||
const savedImage = {...this.state.imageCaptured, id: result.id};
|
||||
this.setState({imageCaptured: undefined, captureImages: _.concat(this.state.captureImages, savedImage)}, () => {
|
||||
this.sendBottomButtonPressedAction(type, captureRetakeMode);
|
||||
});
|
||||
} else {
|
||||
console.warn('Not implemented on Android yet');
|
||||
this.sendBottomButtonPressedAction(type, captureRetakeMode);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.sendBottomButtonPressedAction(type, captureRetakeMode);
|
||||
}
|
||||
}
|
||||
|
||||
renderBottomButton(type) {
|
||||
const buttonText = _(this.props).get(`actions.${type}ButtonText`)
|
||||
if (buttonText) {
|
||||
let showButton = true;
|
||||
if (type === 'right') {
|
||||
showButton = this.state.captureImages.length || this.isCaptureRetakeMode();
|
||||
}
|
||||
if (showButton) {
|
||||
const buttonNameSuffix = this.isCaptureRetakeMode() ? 'CaptureRetakeButtonText' : 'ButtonText';
|
||||
const buttonText = _(this.props).get(`actions.${type}${buttonNameSuffix}`)
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[styles.bottomButton, { justifyContent: type === 'left' ? 'flex-start' : 'flex-end' }]}
|
||||
onPress={() => this.onButtonPressed(type)}
|
||||
>
|
||||
<Text style={styles.textStyle}>{_.get(this.props, `actions.${type}ButtonText`, type)}</Text>
|
||||
<Text style={styles.textStyle}>{buttonText}</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<View style={styles.bottomContainerGap} />
|
||||
);
|
||||
}
|
||||
return (
|
||||
this.renderBootomContainerGap()
|
||||
);
|
||||
}
|
||||
|
||||
renderBottomButtons() {
|
||||
|
@ -228,7 +253,6 @@ export default class CameraScreenBase extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
onSwitchCameraPressed() {
|
||||
this.camera.changeCamera();
|
||||
}
|
||||
|
@ -241,16 +265,17 @@ export default class CameraScreenBase extends Component {
|
|||
}
|
||||
|
||||
async onCaptureImagePressed() {
|
||||
const image = await this.camera.capture(true);
|
||||
const shouldSaveToCameraRoll = !this.props.allowCaptureRetake;
|
||||
const image = await this.camera.capture(shouldSaveToCameraRoll);
|
||||
|
||||
if (image) {
|
||||
this.setState({ captured: true, imageCaptured: image, captureImages: _.concat(this.state.captureImages, image) });
|
||||
if (this.props.allowCaptureRetake) {
|
||||
this.setState({ imageCaptured: image });
|
||||
} else {
|
||||
if (image) {
|
||||
this.setState({ captured: true, imageCaptured: image, captureImages: _.concat(this.state.captureImages, image) });
|
||||
}
|
||||
this.sendBottomButtonPressedAction('capture', false, image);
|
||||
}
|
||||
|
||||
if (this.props.onBottomButtonPressed) {
|
||||
this.props.onBottomButtonPressed({ type: 'capture', image })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
onRatioButtonPressed() {
|
||||
|
|
Loading…
Reference in New Issue