diff --git a/README.md b/README.md index 28ecd99..bde659e 100644 --- a/README.md +++ b/README.md @@ -103,10 +103,10 @@ var cameraApp = React.createClass({ this.setState(state); }, _takePicture() { - this.refs.cam.capture(function(err, data) { - console.log(err, data); - }); - } + this.refs.cam.capture().then( + data => console.log(data), + error => console.log(error) + ); }); @@ -252,9 +252,9 @@ By default, `onZoomChanged` is not defined and pinch-to-zoom is disabled. You can access component methods by adding a `ref` (ie. `ref="camera"`) prop to your `` element, then you can use `this.refs.camera.capture(cb)`, etc. inside your component. -#### `capture([options,] callback)` +#### `capture([options]): Promise` -Captures data from the camera. What is captured is based on the `captureMode` and `captureTarget` props. `captureMode` tells the camera whether you want a still image or video. `captureTarget` allows you to specify how you want the data to be captured and sent back to you. See `captureTarget` under Properties to see the available values. +Captures data from the camera. What is captured is based on the `captureMode` and `captureTarget` props. `captureMode` tells the camera whether you want a still image or video. `captureTarget` allows you to specify how you want the data to be captured and sent back to you. See `captureTarget` under Properties to see the available values. The promise will be fulfilled with the image data or file handle of the image on disk, depending on `target`. Supported options: @@ -269,9 +269,9 @@ Supported options: Ends the current capture session for video captures. Only applies when the current `captureMode` is `video`. -#### `checkDeviceAuthorizationStatus(callback(err, isAuthorized))` +#### `checkDeviceAuthorizationStatus(): Promise` -Exposes the native API for checking if the device has authorized access to the camera. Can be used to call before loading the Camera component to ensure proper UX. +Exposes the native API for checking if the device has authorized access to the camera. Can be used to call before loading the Camera component to ensure proper UX. The promise will be fulfilled with `true` or `false` depending on whether the device is authorized. ## Subviews This component supports subviews, so if you wish to use the camera view as a background or if you want to layout buttons/images/etc. inside the camera then you can do that. diff --git a/android/src/main/java/com/lwansbrough/RCTCamera/RCTCameraModule.java b/android/src/main/java/com/lwansbrough/RCTCamera/RCTCameraModule.java index 06ab65f..2850ca1 100644 --- a/android/src/main/java/com/lwansbrough/RCTCamera/RCTCameraModule.java +++ b/android/src/main/java/com/lwansbrough/RCTCamera/RCTCameraModule.java @@ -150,10 +150,10 @@ public class RCTCameraModule extends ReactContextBaseJavaModule { } @ReactMethod - public void capture(final ReadableMap options, final Callback callback) { + public void capture(final ReadableMap options, final Promise promise) { Camera camera = RCTCamera.getInstance().acquireCameraInstance(options.getInt("type")); if (null == camera) { - callback.invoke("No camera found.", null); + promise.reject("No camera found."); return; } camera.takePicture(null, null, new Camera.PictureCallback() { @@ -162,7 +162,7 @@ public class RCTCameraModule extends ReactContextBaseJavaModule { switch (options.getInt("target")) { case RCT_CAMERA_CAPTURE_TARGET_MEMORY: String encoded = Base64.encodeToString(data, Base64.DEFAULT); - callback.invoke(null, encoded); + promise.resolve(encoded); break; case RCT_CAMERA_CAPTURE_TARGET_CAMERA_ROLL: BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); @@ -171,12 +171,12 @@ public class RCTCameraModule extends ReactContextBaseJavaModule { _reactContext.getContentResolver(), bitmap, options.getString("title"), options.getString("description")); - callback.invoke(null, url); + promise.resolve(url); break; case RCT_CAMERA_CAPTURE_TARGET_DISK: File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE); if (pictureFile == null) { - callback.invoke("Error creating media file.", null); + promise.reject("Error creating media file."); return; } @@ -185,11 +185,11 @@ public class RCTCameraModule extends ReactContextBaseJavaModule { fos.write(data); fos.close(); } catch (FileNotFoundException e) { - callback.invoke("File not found: " + e.getMessage(), null); + promise.reject("File not found: " + e.getMessage()); } catch (IOException e) { - callback.invoke("Error accessing file: " + e.getMessage(), null); + promise.reject("Error accessing file: " + e.getMessage()); } - callback.invoke(null, Uri.fromFile(pictureFile).toString()); + promise.resolve(Uri.fromFile(pictureFile).toString()); break; case RCT_CAMERA_CAPTURE_TARGET_TEMP: File tempFile = getTempMediaFile(MEDIA_TYPE_IMAGE); @@ -216,7 +216,7 @@ public class RCTCameraModule extends ReactContextBaseJavaModule { } @ReactMethod - public void stopCapture(final ReadableMap options, final Callback callback) { + public void stopCapture(final ReadableMap options, final Promise promise) { // TODO: implement video capture } diff --git a/index.android.js b/index.android.js index 827c06e..fa61ad8 100644 --- a/index.android.js +++ b/index.android.js @@ -153,13 +153,7 @@ var Camera = React.createClass({ this.props.onBarCodeRead && this.props.onBarCodeRead(e); }, - capture(options, cb) { - - if (arguments.length == 1) { - cb = options; - options = {}; - } - + capture(options) { options = Object.assign({}, { audio: this.props.captureAudio, mode: this.props.captureMode, @@ -185,7 +179,7 @@ var Camera = React.createClass({ options.type = constants.Type[options.type]; } - NativeModules.CameraModule.capture(options, cb); + return NativeModules.CameraModule.capture(options); }, stopCapture() { diff --git a/index.ios.js b/index.ios.js index e85f160..9da00e6 100644 --- a/index.ios.js +++ b/index.ios.js @@ -75,10 +75,10 @@ var Camera = React.createClass({ }, componentWillMount() { - NativeModules.CameraManager.checkDeviceAuthorizationStatus((function(err, isAuthorized) { - this.state.isAuthorized = isAuthorized; - this.setState(this.state); - }).bind(this)); + NativeModules.CameraManager.checkDeviceAuthorizationStatus().then( + isAuthorized => this.setState({ isAuthorized }) + ); + this.cameraBarCodeReadListener = DeviceEventEmitter.addListener('CameraBarCodeRead', this._onBarCodeRead); }, @@ -153,13 +153,7 @@ var Camera = React.createClass({ this.props.onBarCodeRead && this.props.onBarCodeRead(e); }, - capture(options, cb) { - - if (arguments.length == 1) { - cb = options; - options = {}; - } - + capture(options) { options = Object.assign({}, { audio: this.props.captureAudio, mode: this.props.captureMode, @@ -180,7 +174,7 @@ var Camera = React.createClass({ options.target = constants.CaptureTarget[options.target]; } - NativeModules.CameraManager.capture(options, cb); + return NativeModules.CameraManager.capture(options); }, stopCapture() { diff --git a/ios/RCTCameraManager.h b/ios/RCTCameraManager.h index 6a1f2ab..28c9404 100644 --- a/ios/RCTCameraManager.h +++ b/ios/RCTCameraManager.h @@ -59,7 +59,8 @@ typedef NS_ENUM(NSInteger, RCTCameraTorchMode) { @property (nonatomic) NSInteger presetCamera; @property (nonatomic) AVCaptureVideoPreviewLayer *previewLayer; @property (nonatomic) NSInteger videoTarget; -@property (nonatomic, strong) RCTResponseSenderBlock videoCallback; +@property (nonatomic, strong) RCTPromiseResolveBlock videoResolve; +@property (nonatomic, strong) RCTPromiseRejectBlock videoReject; @property (nonatomic, strong) RCTCamera *camera; @@ -69,7 +70,7 @@ typedef NS_ENUM(NSInteger, RCTCameraTorchMode) { - (void)changeFlashMode:(NSInteger)flashMode; - (void)changeTorchMode:(NSInteger)torchMode; - (AVCaptureDevice *)deviceWithMediaType:(NSString *)mediaType preferringPosition:(AVCaptureDevicePosition)position; -- (void)capture:(NSDictionary*)options callback:(RCTResponseSenderBlock)callback; +- (void)capture:(NSDictionary*)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject; - (void)initializeCaptureSessionInput:(NSString*)type; - (void)stopCapture; - (void)startSession; diff --git a/ios/RCTCameraManager.m b/ios/RCTCameraManager.m index 40215e4..449d353 100644 --- a/ios/RCTCameraManager.m +++ b/ios/RCTCameraManager.m @@ -135,18 +135,18 @@ RCT_EXPORT_VIEW_PROPERTY(onZoomChanged, BOOL) return self; } -RCT_EXPORT_METHOD(checkDeviceAuthorizationStatus:(RCTResponseSenderBlock) callback) -{ +RCT_EXPORT_METHOD(checkDeviceAuthorizationStatus:(RCTPromiseResolveBlock)resolve + reject:(__unused RCTPromiseRejectBlock)reject) { __block NSString *mediaType = AVMediaTypeVideo; [AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) { if (!granted) { - callback(@[[NSNull null], @(granted)]); + resolve(@[@(granted)]); } else { mediaType = AVMediaTypeAudio; [AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) { - callback(@[[NSNull null], @(granted)]); + resolve(@[@(granted)]); }]; } }]; @@ -229,15 +229,17 @@ RCT_EXPORT_METHOD(changeTorchMode:(NSInteger)torchMode) { [device unlockForConfiguration]; } -RCT_EXPORT_METHOD(capture:(NSDictionary *)options callback:(RCTResponseSenderBlock)callback) { +RCT_EXPORT_METHOD(capture:(NSDictionary *)options + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { NSInteger captureMode = [[options valueForKey:@"mode"] intValue]; NSInteger captureTarget = [[options valueForKey:@"target"] intValue]; if (captureMode == RCTCameraCaptureModeStill) { - [self captureStill:captureTarget options:options callback:callback]; + [self captureStill:captureTarget options:options resolve:resolve reject:reject]; } else if (captureMode == RCTCameraCaptureModeVideo) { - [self captureVideo:captureTarget options:options callback:callback]; + [self captureVideo:captureTarget options:options resolve:resolve reject:reject]; } } @@ -396,7 +398,7 @@ RCT_EXPORT_METHOD(hasFlash:(RCTResponseSenderBlock) callback) { } -- (void)captureStill:(NSInteger)target options:(NSDictionary *)options callback:(RCTResponseSenderBlock)callback { +- (void)captureStill:(NSInteger)target options:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { dispatch_async(self.sessionQueue, ^{ #if TARGET_IPHONE_SIMULATOR CGSize size = CGSizeMake(720, 1280); @@ -422,7 +424,7 @@ RCT_EXPORT_METHOD(hasFlash:(RCTResponseSenderBlock) callback) { UIGraphicsEndImageContext(); NSData *imageData = UIImageJPEGRepresentation(image, 1.0); - [self saveImage:imageData target:target metadata:nil callback:callback]; + [self saveImage:imageData target:target metadata:nil resolve:resolve reject:reject]; #else [[self.stillImageOutput connectionWithMediaType:AVMediaTypeVideo] setVideoOrientation:self.previewLayer.connection.videoOrientation]; @@ -479,12 +481,12 @@ RCT_EXPORT_METHOD(hasFlash:(RCTResponseSenderBlock) callback) { CGImageDestinationFinalize(destination); CFRelease(destination); - [self saveImage:rotatedImageData target:target metadata:imageMetadata callback:callback]; + [self saveImage:rotatedImageData target:target metadata:imageMetadata resolve:resolve reject:reject]; CGImageRelease(rotatedCGImage); } else { - callback(@[RCTMakeError(error.description, nil, nil)]); + reject(RCTErrorUnspecified, nil, RCTErrorWithMessage(error.description)); } }]; #endif @@ -492,7 +494,7 @@ RCT_EXPORT_METHOD(hasFlash:(RCTResponseSenderBlock) callback) { } -- (void)saveImage:(NSData*)imageData target:(NSInteger)target metadata:(NSDictionary *)metadata callback:(RCTResponseSenderBlock)callback { +- (void)saveImage:(NSData*)imageData target:(NSInteger)target metadata:(NSDictionary *)metadata resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { NSString *responseString; if (target == RCTCameraCaptureTargetMemory) { @@ -521,15 +523,15 @@ RCT_EXPORT_METHOD(hasFlash:(RCTResponseSenderBlock) callback) { else if (target == RCTCameraCaptureTargetCameraRoll) { [[[ALAssetsLibrary alloc] init] writeImageDataToSavedPhotosAlbum:imageData metadata:metadata completionBlock:^(NSURL* url, NSError* error) { if (error == nil) { - callback(@[[NSNull null], [url absoluteString]]); + resolve(@[[url absoluteString]]); } else { - callback(@[RCTMakeError(error.description, nil, nil)]); + reject(RCTErrorUnspecified, nil, RCTErrorWithMessage(error.description)); } }]; return; } - callback(@[[NSNull null], responseString]); + resolve(@[responseString]); } - (CGImageRef)newCGImageRotatedByAngle:(CGImageRef)imgRef angle:(CGFloat)angle @@ -561,10 +563,10 @@ RCT_EXPORT_METHOD(hasFlash:(RCTResponseSenderBlock) callback) { return rotatedImage; } --(void)captureVideo:(NSInteger)target options:(NSDictionary *)options callback:(RCTResponseSenderBlock)callback { +-(void)captureVideo:(NSInteger)target options:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { if (self.movieFileOutput.recording) { - callback(@[RCTMakeError(@"Already Recording", nil, nil)]); + reject(RCTErrorUnspecified, nil, RCTErrorWithMessage(@"Already recording")); return; } @@ -589,7 +591,7 @@ RCT_EXPORT_METHOD(hasFlash:(RCTResponseSenderBlock) callback) { if ([fileManager fileExistsAtPath:outputPath]) { NSError *error; if ([fileManager removeItemAtPath:outputPath error:&error] == NO) { - callback(@[RCTMakeError(error.description, nil, nil)]); + reject(RCTErrorUnspecified, nil, RCTErrorWithMessage(error.description)); return; } } @@ -597,7 +599,8 @@ RCT_EXPORT_METHOD(hasFlash:(RCTResponseSenderBlock) callback) { //Start recording [self.movieFileOutput startRecordingToOutputFileURL:outputURL recordingDelegate:self]; - self.videoCallback = callback; + self.videoResolve = resolve; + self.videoReject = reject; self.videoTarget = target; }); } @@ -617,7 +620,7 @@ didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL } } if (!recordSuccess) { - self.videoCallback(@[RCTMakeError(@"Error while recording", nil, nil)]); + self.videoReject(RCTErrorUnspecified, nil, RCTErrorWithMessage(@"Error while recording")); return; } @@ -627,11 +630,10 @@ didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL [library writeVideoAtPathToSavedPhotosAlbum:outputFileURL completionBlock:^(NSURL *assetURL, NSError *error) { if (error) { - self.videoCallback(@[RCTMakeError(error.description, nil, nil)]); + self.videoReject(RCTErrorUnspecified, nil, RCTErrorWithMessage(error.description)); return; } - - self.videoCallback(@[[NSNull null], [assetURL absoluteString]]); + self.videoResolve([assetURL absoluteString]); }]; } } @@ -645,10 +647,10 @@ didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL //copying destination if (!([fileManager copyItemAtPath:[outputFileURL path] toPath:fullPath error:&error])) { - self.videoCallback(@[RCTMakeError(error.description, nil, nil)]); + self.videoReject(RCTErrorUnspecified, nil, RCTErrorWithMessage(error.description)); return; } - self.videoCallback(@[[NSNull null], fullPath]); + self.videoResolve(@[fullPath]); } else if (self.videoTarget == RCTCameraCaptureTargetTemp) { NSString *fileName = [[NSProcessInfo processInfo] globallyUniqueString]; @@ -659,13 +661,13 @@ didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL //copying destination if (!([fileManager copyItemAtPath:[outputFileURL path] toPath:fullPath error:&error])) { - self.videoCallback(@[RCTMakeError(error.description, nil, nil)]); + self.videoReject(RCTErrorUnspecified, nil, RCTErrorWithMessage(error.description)); return; } - self.videoCallback(@[[NSNull null], fullPath]); + self.videoResolve(@[fullPath]); } else { - self.videoCallback(@[RCTMakeError(@"Target not supported", nil, nil)]); + self.videoReject(RCTErrorUnspecified, nil, RCTErrorWithMessage(@"Target not supported")); } }