diff --git a/android/build.gradle b/android/build.gradle new file mode 100755 index 0000000..b5ed4e0 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,34 @@ +buildscript { + repositories { + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:1.2.3' + } +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.1" + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 22 + versionCode 1 + versionName "1.0" + } + lintOptions { + abortOnError false + } +} + +repositories { + mavenCentral() +} + +dependencies { + compile "com.facebook.react:react-native:0.17.+" +} diff --git a/android/AndroidManifest.xml b/android/src/main/AndroidManifest.xml similarity index 85% rename from android/AndroidManifest.xml rename to android/src/main/AndroidManifest.xml index 2c16dd6..c681469 100644 --- a/android/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ - + diff --git a/android/src/main/java/com/lwansbrough/RCTCamera/RCTCamera.java b/android/src/main/java/com/lwansbrough/RCTCamera/RCTCamera.java new file mode 100644 index 0000000..32bfe27 --- /dev/null +++ b/android/src/main/java/com/lwansbrough/RCTCamera/RCTCamera.java @@ -0,0 +1,201 @@ +/** + * Created by Fabrice Armisen (farmisen@gmail.com) on 1/4/16. + */ + +package com.lwansbrough.RCTCamera; + +import android.hardware.Camera; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class RCTCamera { + + private static final RCTCamera ourInstance = new RCTCamera(); + private final HashMap _cameraInfos; + private final HashMap _cameraTypeToIndex; + private final Map _cameras; + private int _orientation = -1; + private int _actualDeviceOrientation = 0; + + public static RCTCamera getInstance() { + return ourInstance; + } + + public Camera acquireCameraInstance(int type) { + if (null == _cameras.get(type) && null != _cameraTypeToIndex.get(type)) { + try { + Camera camera = Camera.open(_cameraTypeToIndex.get(type)); + _cameras.put(type, camera); + adjustPreviewLayout(type); + } catch (Exception e) { + System.console().printf("acquireCameraInstance: %s", e.getLocalizedMessage()); + } + } + return _cameras.get(type); + } + + public void releaseCameraInstance(int type) { + if (null != _cameras.get(type)) { + _cameras.get(type).release(); + _cameras.remove(type); + } + } + + public int getPreviewWidth(int type) { + CameraInfoWrapper cameraInfo = _cameraInfos.get(type); + if (null == cameraInfo) { + return 0; + } + return cameraInfo.previewWidth; + } + + public int getPreviewHeight(int type) { + CameraInfoWrapper cameraInfo = _cameraInfos.get(type); + if (null == cameraInfo) { + return 0; + } + return cameraInfo.previewHeight; + } + + public void setOrientation(int orientation) { + if (_orientation == orientation) { + return; + } + _orientation = orientation; + adjustPreviewLayout(RCTCameraModule.RCT_CAMERA_TYPE_FRONT); + adjustPreviewLayout(RCTCameraModule.RCT_CAMERA_TYPE_BACK); + } + + public void setActualDeviceOrientation(int actualDeviceOrientation) { + _actualDeviceOrientation = actualDeviceOrientation; + adjustPreviewLayout(RCTCameraModule.RCT_CAMERA_TYPE_FRONT); + adjustPreviewLayout(RCTCameraModule.RCT_CAMERA_TYPE_BACK); + } + + public void setTorchMode(int cameraType, int torchMode) { + Camera camera = _cameras.get(cameraType); + if (null == camera) { + return; + } + + Camera.Parameters parameters = camera.getParameters(); + String value = parameters.getFlashMode(); + switch (torchMode) { + case RCTCameraModule.RCT_CAMERA_TORCH_MODE_ON: + value = Camera.Parameters.FLASH_MODE_TORCH; + break; + case RCTCameraModule.RCT_CAMERA_TORCH_MODE_OFF: + value = Camera.Parameters.FLASH_MODE_OFF; + break; + } + + List flashModes = parameters.getSupportedFlashModes(); + if (flashModes != null && flashModes.contains(value)) { + parameters.setFlashMode(value); + camera.setParameters(parameters); + } + } + + public void setFlashMode(int cameraType, int flashMode) { + Camera camera = _cameras.get(cameraType); + if (null == camera) { + return; + } + + Camera.Parameters parameters = camera.getParameters(); + String value = parameters.getFlashMode(); + switch (flashMode) { + case RCTCameraModule.RCT_CAMERA_FLASH_MODE_AUTO: + value = Camera.Parameters.FLASH_MODE_AUTO; + break; + case RCTCameraModule.RCT_CAMERA_FLASH_MODE_ON: + value = Camera.Parameters.FLASH_MODE_ON; + break; + case RCTCameraModule.RCT_CAMERA_FLASH_MODE_OFF: + value = Camera.Parameters.FLASH_MODE_OFF; + break; + } + List flashModes = parameters.getSupportedFlashModes(); + if (flashModes != null && flashModes.contains(value)) { + parameters.setFlashMode(value); + camera.setParameters(parameters); + } + } + + private void adjustPreviewLayout(int type) { + Camera camera = _cameras.get(type); + if (null == camera) { + return; + } + + CameraInfoWrapper cameraInfo = _cameraInfos.get(type); + int rotation = cameraInfo.info.orientation; + if (cameraInfo.info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { + rotation = (720 - rotation - _actualDeviceOrientation * 90) % 360; + } else { + rotation = (rotation - _actualDeviceOrientation * 90 + 360) % 360; + } + cameraInfo.rotation = rotation; + // TODO: take in account the _orientation prop + + camera.setDisplayOrientation(cameraInfo.rotation); + + Camera.Parameters parameters = camera.getParameters(); + parameters.setRotation(cameraInfo.rotation); + + // set preview size + int width = parameters.getSupportedPreviewSizes().get(0).width; + int height = parameters.getSupportedPreviewSizes().get(0).height; + + parameters.setPreviewSize(width, height); + try { + camera.setParameters(parameters); + } catch (Exception e) { + e.printStackTrace(); + } + + if (cameraInfo.rotation == 0 || cameraInfo.rotation == 180) { + cameraInfo.previewWidth = width; + cameraInfo.previewHeight = height; + } else { + cameraInfo.previewWidth = height; + cameraInfo.previewHeight = width; + } + } + + private RCTCamera() { + _cameras = new HashMap<>(); + _cameraInfos = new HashMap<>(); + _cameraTypeToIndex = new HashMap<>(); + + // map camera types to camera indexes and collect cameras properties + for (int i = 0; i < Camera.getNumberOfCameras(); i++) { + Camera.CameraInfo info = new Camera.CameraInfo(); + Camera.getCameraInfo(i, info); + if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT && _cameraInfos.get(RCTCameraModule.RCT_CAMERA_TYPE_FRONT) == null) { + _cameraInfos.put(RCTCameraModule.RCT_CAMERA_TYPE_FRONT, new CameraInfoWrapper(info)); + _cameraTypeToIndex.put(RCTCameraModule.RCT_CAMERA_TYPE_FRONT, i); + acquireCameraInstance(RCTCameraModule.RCT_CAMERA_TYPE_FRONT); + releaseCameraInstance(RCTCameraModule.RCT_CAMERA_TYPE_FRONT); + } else if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK && _cameraInfos.get(RCTCameraModule.RCT_CAMERA_TYPE_BACK) == null) { + _cameraInfos.put(RCTCameraModule.RCT_CAMERA_TYPE_BACK, new CameraInfoWrapper(info)); + _cameraTypeToIndex.put(RCTCameraModule.RCT_CAMERA_TYPE_BACK, i); + acquireCameraInstance(RCTCameraModule.RCT_CAMERA_TYPE_BACK); + releaseCameraInstance(RCTCameraModule.RCT_CAMERA_TYPE_BACK); + } + } + } + + private class CameraInfoWrapper { + public final Camera.CameraInfo info; + public int rotation = 0; + public int previewWidth = -1; + public int previewHeight = -1; + + public CameraInfoWrapper(Camera.CameraInfo info) { + this.info = info; + } + } +} diff --git a/android/src/main/java/com/lwansbrough/RCTCamera/RCTCameraModule.java b/android/src/main/java/com/lwansbrough/RCTCamera/RCTCameraModule.java new file mode 100644 index 0000000..28c1ae0 --- /dev/null +++ b/android/src/main/java/com/lwansbrough/RCTCamera/RCTCameraModule.java @@ -0,0 +1,229 @@ +/** + * Created by Fabrice Armisen (farmisen@gmail.com) on 1/4/16. + */ + +package com.lwansbrough.RCTCamera; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.hardware.Camera; +import android.net.Uri; +import android.os.Environment; +import android.provider.MediaStore; +import android.util.Base64; +import android.util.Log; +import com.facebook.react.bridge.*; + +import javax.annotation.Nullable; +import java.io.*; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +public class RCTCameraModule extends ReactContextBaseJavaModule { + private static final String TAG = "RCTCameraModule"; + + public static final int RCT_CAMERA_ASPECT_FILL = 0; + public static final int RCT_CAMERA_ASPECT_FIT = 1; + public static final int RCT_CAMERA_ASPECT_STRETCH = 2; + public static final int RCT_CAMERA_CAPTURE_MODE_STILL = 0; + public static final int RCT_CAMERA_CAPTURE_MODE_VIDEO = 1; + public static final int RCT_CAMERA_CAPTURE_TARGET_MEMORY = 0; + public static final int RCT_CAMERA_CAPTURE_TARGET_DISK = 1; + public static final int RCT_CAMERA_CAPTURE_TARGET_CAMERA_ROLL = 2; + public static final int RCT_CAMERA_ORIENTATION_AUTO = 0; + public static final int RCT_CAMERA_ORIENTATION_LANDSCAPE_LEFT = 1; + public static final int RCT_CAMERA_ORIENTATION_LANDSCAPE_RIGHT = 2; + public static final int RCT_CAMERA_ORIENTATION_PORTRAIT = 3; + public static final int RCT_CAMERA_ORIENTATION_PORTRAIT_UPSIDE_DOWN = 4; + public static final int RCT_CAMERA_TYPE_FRONT = 1; + public static final int RCT_CAMERA_TYPE_BACK = 2; + public static final int RCT_CAMERA_FLASH_MODE_OFF = 0; + public static final int RCT_CAMERA_FLASH_MODE_ON = 1; + public static final int RCT_CAMERA_FLASH_MODE_AUTO = 2; + public static final int RCT_CAMERA_TORCH_MODE_OFF = 0; + public static final int RCT_CAMERA_TORCH_MODE_ON = 1; + public static final int RCT_CAMERA_TORCH_MODE_AUTO = 2; + public static final int MEDIA_TYPE_IMAGE = 1; + public static final int MEDIA_TYPE_VIDEO = 2; + + private final ReactApplicationContext _reactContext; + + public RCTCameraModule(ReactApplicationContext reactContext) { + super(reactContext); + _reactContext = reactContext; + } + + @Override + public String getName() { + return "RCTCameraModule"; + } + + @Nullable + @Override + public Map getConstants() { + return Collections.unmodifiableMap(new HashMap() { + { + put("Aspect", getAspectConstants()); + put("Type", getTypeConstants()); + put("CaptureMode", getCaptureModeConstants()); + put("CaptureTarget", getCaptureTargetConstants()); + put("Orientation", getOrientationConstants()); + put("FlashMode", getFlashModeConstants()); + put("TorchMode", getTorchModeConstants()); + } + + private Map getAspectConstants() { + return Collections.unmodifiableMap(new HashMap() { + { + put("stretch", RCT_CAMERA_ASPECT_STRETCH); + put("fit", RCT_CAMERA_ASPECT_FIT); + put("fill", RCT_CAMERA_ASPECT_FILL); + } + }); + } + + private Map getTypeConstants() { + return Collections.unmodifiableMap(new HashMap() { + { + put("front", RCT_CAMERA_TYPE_FRONT); + put("back", RCT_CAMERA_TYPE_BACK); + } + }); + } + + private Map getCaptureModeConstants() { + return Collections.unmodifiableMap(new HashMap() { + { + put("still", RCT_CAMERA_CAPTURE_MODE_STILL); + put("video", RCT_CAMERA_CAPTURE_MODE_VIDEO); + } + }); + } + + private Map getCaptureTargetConstants() { + return Collections.unmodifiableMap(new HashMap() { + { + put("memory", RCT_CAMERA_CAPTURE_TARGET_MEMORY); + put("disk", RCT_CAMERA_CAPTURE_TARGET_DISK); + put("cameraRoll", RCT_CAMERA_CAPTURE_TARGET_CAMERA_ROLL); + } + }); + } + + private Map getOrientationConstants() { + return Collections.unmodifiableMap(new HashMap() { + { + put("auto", RCT_CAMERA_ORIENTATION_AUTO); + put("landscapeLeft", RCT_CAMERA_ORIENTATION_LANDSCAPE_LEFT); + put("landscapeRight", RCT_CAMERA_ORIENTATION_LANDSCAPE_RIGHT); + put("portrait", RCT_CAMERA_ORIENTATION_PORTRAIT); + put("portraitUpsideDown", RCT_CAMERA_ORIENTATION_PORTRAIT_UPSIDE_DOWN); + } + }); + } + + private Map getFlashModeConstants() { + return Collections.unmodifiableMap(new HashMap() { + { + put("off", RCT_CAMERA_FLASH_MODE_OFF); + put("on", RCT_CAMERA_FLASH_MODE_ON); + put("auto", RCT_CAMERA_FLASH_MODE_AUTO); + } + }); + } + + private Map getTorchModeConstants() { + return Collections.unmodifiableMap(new HashMap() { + { + put("off", RCT_CAMERA_TORCH_MODE_OFF); + put("on", RCT_CAMERA_TORCH_MODE_ON); + put("auto", RCT_CAMERA_TORCH_MODE_AUTO); + } + }); + } + }); + } + + @ReactMethod + public void capture(final ReadableMap options, final Callback callback) { + Camera camera = RCTCamera.getInstance().acquireCameraInstance(options.getInt("type")); + if (null == camera) { + callback.invoke("No camera found.", null); + return; + } + camera.takePicture(null, null, new Camera.PictureCallback() { + @Override + public void onPictureTaken(byte[] data, Camera camera) { + switch (options.getInt("target")) { + case RCT_CAMERA_CAPTURE_TARGET_MEMORY: + String encoded = Base64.encodeToString(data, Base64.DEFAULT); + callback.invoke(null, encoded); + break; + case RCT_CAMERA_CAPTURE_TARGET_CAMERA_ROLL: + BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); + Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, bitmapOptions); + String url = MediaStore.Images.Media.insertImage( + _reactContext.getContentResolver(), + bitmap, options.getString("title"), + options.getString("description")); + callback.invoke(null, url); + break; + case RCT_CAMERA_CAPTURE_TARGET_DISK: + File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE); + if (pictureFile == null) { + callback.invoke("Error creating media file.", null); + return; + } + + try { + FileOutputStream fos = new FileOutputStream(pictureFile); + fos.write(data); + fos.close(); + } catch (FileNotFoundException e) { + callback.invoke("File not found: " + e.getMessage(), null); + } catch (IOException e) { + callback.invoke("Error accessing file: " + e.getMessage(), null); + } + callback.invoke(null, Uri.fromFile(pictureFile).toString()); + break; + } + } + }); + } + + @ReactMethod + public void stopCapture(final ReadableMap options, final Callback callback) { + // TODO: implement video capture + } + + private File getOutputMediaFile(int type) { + File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_PICTURES), "RCTCameraModule"); + + // Create the storage directory if it does not exist + if (!mediaStorageDir.exists()) { + if (!mediaStorageDir.mkdirs()) { + Log.e(TAG, "failed to create directory:" + mediaStorageDir.getAbsolutePath()); + return null; + } + } + + // Create a media file name + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); + File mediaFile; + if (type == MEDIA_TYPE_IMAGE) { + mediaFile = new File(mediaStorageDir.getPath() + File.separator + + "IMG_" + timeStamp + ".jpg"); + } else if (type == MEDIA_TYPE_VIDEO) { + mediaFile = new File(mediaStorageDir.getPath() + File.separator + + "VID_" + timeStamp + ".mp4"); + } else { + Log.e(TAG, "Unsupported media type:" + type); + return null; + } + return mediaFile; + } +} diff --git a/android/src/main/java/com/lwansbrough/RCTCamera/RCTCameraPackage.java b/android/src/main/java/com/lwansbrough/RCTCamera/RCTCameraPackage.java new file mode 100644 index 0000000..e75fce1 --- /dev/null +++ b/android/src/main/java/com/lwansbrough/RCTCamera/RCTCameraPackage.java @@ -0,0 +1,31 @@ +package com.lwansbrough.RCTCamera; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; +import com.facebook.react.bridge.JavaScriptModule; + +public class RCTCameraPackage implements ReactPackage { + + @Override + public List createNativeModules(ReactApplicationContext reactApplicationContext) { + return Collections.singletonList(new RCTCameraModule(reactApplicationContext)); + } + + @Override + public List> createJSModules() { + return Collections.emptyList(); + } + + @Override + public List createViewManagers(ReactApplicationContext reactApplicationContext) { + //noinspection ArraysAsListWithZeroOrOneArgument + return Collections.singletonList(new RCTCameraViewManager()); + } + +} diff --git a/android/src/main/java/com/lwansbrough/RCTCamera/RCTCameraView.java b/android/src/main/java/com/lwansbrough/RCTCamera/RCTCameraView.java new file mode 100644 index 0000000..e65953a --- /dev/null +++ b/android/src/main/java/com/lwansbrough/RCTCamera/RCTCameraView.java @@ -0,0 +1,149 @@ +/** + * Created by Fabrice Armisen (farmisen@gmail.com) on 1/3/16. + */ + +package com.lwansbrough.RCTCamera; + +import android.content.Context; +import android.graphics.*; +import android.hardware.SensorManager; +import android.view.OrientationEventListener; +import android.view.ViewGroup; +import android.view.WindowManager; + +public class RCTCameraView extends ViewGroup { + private final OrientationEventListener _orientationListener; + private final Context _context; + private RCTCameraViewFinder _viewFinder = null; + private int _actualDeviceOrientation = -1; + private int _aspect = RCTCameraModule.RCT_CAMERA_ASPECT_FIT; + private int _torchMode = -1; + private int _flashMode = -1; + + public RCTCameraView(Context context) { + super(context); + this._context = context; + setActualDeviceOrientation(context); + + _orientationListener = new OrientationEventListener(context, SensorManager.SENSOR_DELAY_NORMAL) { + @Override + public void onOrientationChanged(int orientation) { + if (setActualDeviceOrientation(_context)) { + layoutViewFinder(); + } + } + }; + + if (_orientationListener.canDetectOrientation()) { + _orientationListener.enable(); + } else { + _orientationListener.disable(); + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + layoutViewFinder(left, top, right, bottom); + } + + public void setAspect(int aspect) { + this._aspect = aspect; + layoutViewFinder(); + } + + public void setCameraType(final int type) { + if (null != this._viewFinder) { + this._viewFinder.setCameraType(type); + } else { + _viewFinder = new RCTCameraViewFinder(_context, type); + if (-1 != this._flashMode) { + _viewFinder.setFlashMode(this._flashMode); + } + if (-1 != this._torchMode) { + _viewFinder.setFlashMode(this._torchMode); + } + addView(_viewFinder); + } + } + + public void setTorchMode(int torchMode) { + this._torchMode = torchMode; + if (this._viewFinder != null) { + this._viewFinder.setTorchMode(torchMode); + } + } + + public void setFlashMode(int flashMode) { + this._flashMode = flashMode; + if (this._viewFinder != null) { + this._viewFinder.setFlashMode(flashMode); + } + } + + public void setOrientation(int orientation) { + RCTCamera.getInstance().setOrientation(orientation); + if (this._viewFinder != null) { + layoutViewFinder(); + } + } + + private boolean setActualDeviceOrientation(Context context) { + int actualDeviceOrientation = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getOrientation(); + if (_actualDeviceOrientation != actualDeviceOrientation) { + _actualDeviceOrientation = actualDeviceOrientation; + RCTCamera.getInstance().setActualDeviceOrientation(_actualDeviceOrientation); + return true; + } else { + return false; + } + } + + private void layoutViewFinder() { + layoutViewFinder(this.getLeft(), this.getTop(), this.getRight(), this.getBottom()); + } + + private void layoutViewFinder(int left, int top, int right, int bottom) { + if (null == _viewFinder) { + return; + } + float width = right - left; + float height = bottom - top; + int viewfinderWidth; + int viewfinderHeight; + double ratio; + switch (this._aspect) { + case RCTCameraModule.RCT_CAMERA_ASPECT_FIT: + ratio = this._viewFinder.getRatio(); + if (ratio * height > width) { + viewfinderHeight = (int) (width / ratio); + viewfinderWidth = (int) width; + } else { + viewfinderWidth = (int) (ratio * height); + viewfinderHeight = (int) height; + } + break; + case RCTCameraModule.RCT_CAMERA_ASPECT_FILL: + ratio = this._viewFinder.getRatio(); + if (ratio * height < width) { + viewfinderHeight = (int) (width / ratio); + viewfinderWidth = (int) width; + } else { + viewfinderWidth = (int) (ratio * height); + viewfinderHeight = (int) height; + } + break; + default: + viewfinderWidth = (int) width; + viewfinderHeight = (int) height; + } + + int viewFinderPaddingX = (int) ((width - viewfinderWidth) / 2); + int viewFinderPaddingY = (int) ((height - viewfinderHeight) / 2); + + this._viewFinder.layout(viewFinderPaddingX, viewFinderPaddingY, viewFinderPaddingX + viewfinderWidth, viewFinderPaddingY + viewfinderHeight); + this.postInvalidate(this.getLeft(), this.getTop(), this.getRight(), this.getBottom()); + } +} + + + diff --git a/android/src/main/java/com/lwansbrough/RCTCamera/RCTCameraViewFinder.java b/android/src/main/java/com/lwansbrough/RCTCamera/RCTCameraViewFinder.java new file mode 100644 index 0000000..79b8e02 --- /dev/null +++ b/android/src/main/java/com/lwansbrough/RCTCamera/RCTCameraViewFinder.java @@ -0,0 +1,125 @@ +/** + * Created by Fabrice Armisen (farmisen@gmail.com) on 1/3/16. + */ + +package com.lwansbrough.RCTCamera; + +import android.content.Context; +import android.graphics.SurfaceTexture; +import android.hardware.Camera; +import android.view.TextureView; + + +class RCTCameraViewFinder extends TextureView implements TextureView.SurfaceTextureListener { + private int _cameraType; + private SurfaceTexture _surfaceTexture; + private boolean _isStarting; + private boolean _isStopping; + private Camera _camera; + + public RCTCameraViewFinder(Context context, int type) { + super(context); + this.setSurfaceTextureListener(this); + this._cameraType = type; + } + + @Override + public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { + _surfaceTexture = surface; + startCamera(); + } + + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { + } + + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { + _surfaceTexture = null; + stopCamera(); + return true; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surface) { + } + + public double getRatio() { + int width = RCTCamera.getInstance().getPreviewWidth(this._cameraType); + int height = RCTCamera.getInstance().getPreviewHeight(this._cameraType); + return ((float) width) / ((float) height); + } + + public void setCameraType(final int type) { + if (this._cameraType == type) { + return; + } + new Thread(new Runnable() { + @Override + public void run() { + stopPreview(); + _cameraType = type; + startPreview(); + } + }).start(); + } + + public void setTorchMode(int torchMode) { + RCTCamera.getInstance().setTorchMode(_cameraType, torchMode); + } + + public void setFlashMode(int flashMode) { + RCTCamera.getInstance().setTorchMode(_cameraType, flashMode); + } + + private void startPreview() { + if (_surfaceTexture != null) { + startCamera(); + } + } + + private void stopPreview() { + if (_camera != null) { + stopCamera(); + } + } + + synchronized private void startCamera() { + if (!_isStarting) { + _isStarting = true; + try { + _camera = RCTCamera.getInstance().acquireCameraInstance(_cameraType); + _camera.setPreviewTexture(_surfaceTexture); + _camera.startPreview(); + } catch (NullPointerException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + stopCamera(); + } finally { + _isStarting = false; + } + } + } + + synchronized private void stopCamera() { + if (!_isStopping) { + _isStopping = true; + try { + if (_camera != null) { + _camera.stopPreview(); + RCTCamera.getInstance().releaseCameraInstance(_cameraType); + _camera = null; + } + + } catch (Exception e) { + e.printStackTrace(); + } finally { + _isStopping = false; + } + } + } +} + + + diff --git a/android/src/main/java/com/lwansbrough/RCTCamera/RCTCameraViewManager.java b/android/src/main/java/com/lwansbrough/RCTCamera/RCTCameraViewManager.java new file mode 100644 index 0000000..047156a --- /dev/null +++ b/android/src/main/java/com/lwansbrough/RCTCamera/RCTCameraViewManager.java @@ -0,0 +1,58 @@ +package com.lwansbrough.RCTCamera; + +import android.support.annotation.Nullable; +import com.facebook.react.uimanager.*; + +public class RCTCameraViewManager extends SimpleViewManager { + private static final String REACT_CLASS = "RCTCameraView"; + + @Override + public String getName() { + return REACT_CLASS; + } + + @Override + public RCTCameraView createViewInstance(ThemedReactContext context) { + return new RCTCameraView(context); + } + + @ReactProp(name = "aspect") + public void setAspect(RCTCameraView view, int aspect) { + view.setAspect(aspect); + } + + @ReactProp(name = "captureMode") + public void setCaptureMode(RCTCameraView view, int captureMode) { + // TODO - implement video mode + } + + @ReactProp(name = "captureTarget") + public void setCaptureTarget(RCTCameraView view, int captureTarget) { + // No reason to handle this props value here since it's passed again to the RCTCameraModule capture method + } + + @ReactProp(name = "type") + public void setType(RCTCameraView view, int type) { + view.setCameraType(type); + } + + @ReactProp(name = "torchMode") + public void setTorchMode(RCTCameraView view, int torchMode) { + view.setTorchMode(torchMode); + } + + @ReactProp(name = "flashMode") + public void setFlashMode(RCTCameraView view, int flashMode) { + view.setFlashMode(flashMode); + } + + @ReactProp(name = "orientation") + public void setOrientation(RCTCameraView view, int orientation) { + view.setOrientation(orientation); + } + + @ReactProp(name = "captureAudio") + public void setCaptureAudio(RCTCameraView view, boolean captureAudio) { + // TODO - implement video mode + } +} diff --git a/android/src/main/java/com/lwansbrough/ReactCamera/ReactCamera.java b/android/src/main/java/com/lwansbrough/ReactCamera/ReactCamera.java deleted file mode 100644 index adf0187..0000000 --- a/android/src/main/java/com/lwansbrough/ReactCamera/ReactCamera.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.lwansbrough.RCTCamera; - -import java.util.Arrays; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import com.facebook.react.ReactPackage; -import com.facebook.react.bridge.NativeModule; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.uimanager.ViewManager; -import com.facebook.react.bridge.JavaScriptModule; - -public class ReactCamera implements ReactPackage { - - @Override - public List createViewManagers(ReactApplicationContext reactContext) { - return Arrays.asList( - new ReactCameraManager() - ); - } - -} diff --git a/android/src/main/java/com/lwansbrough/ReactCamera/ReactCameraManager.java b/android/src/main/java/com/lwansbrough/ReactCamera/ReactCameraManager.java deleted file mode 100644 index 901cc78..0000000 --- a/android/src/main/java/com/lwansbrough/ReactCamera/ReactCameraManager.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.lwansbrough.RCTCamera; - -import java.util.Arrays; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import com.facebook.react.ReactPackage; -import com.facebook.react.bridge.NativeModule; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.uimanager.ViewManager; -import com.facebook.react.bridge.JavaScriptModule; - -public class ReactCameraManager extends SimpleViewManager { - - public static final String REACT_CLASS = "RCTCamera"; - - @UIProp(UIProp.Type.STRING) - public static final String PROP_SRC = "src"; - @UIProp(UIProp.Type.NUMBER) - public static final String PROP_BORDER_RADIUS = "borderRadius"; - @UIProp(UIProp.Type.STRING) - public static final String PROP_RESIZE_MODE = ViewProps.RESIZE_MODE; - - @Override - public String getName() { - return REACT_CLASS; - } - - @Override - public ReactCameraView createViewInstance(ThemedReactContext context) { - return new ReactCameraView(context, Fresco.newDraweeControllerBuilder(), mCallerContext); - } - - @Override - public void updateView(final ReactImageView view, final CatalystStylesDiffMap props) { - super.updateView(view, props); - - if (props.hasKey(PROP_RESIZE_MODE)) { - view.setScaleType( - ImageResizeMode.toScaleType(props.getString(PROP_RESIZE_MODE))); - } - if (props.hasKey(PROP_SRC)) { - view.setSource(props.getString(PROP_SRC)); - } - if (props.hasKey(PROP_BORDER_RADIUS)) { - view.setBorderRadius(props.getFloat(PROP_BORDER_RADIUS, 0.0f)); - } - view.maybeUpdateView(); - } -} diff --git a/index.android.js b/index.android.js index 6b67811..827c06e 100644 --- a/index.android.js +++ b/index.android.js @@ -1,12 +1,207 @@ -var { requireNativeComponent, PropTypes } = require('react-native'); +var React = require('react-native'); +var { View, StyleSheet, requireNativeComponent, PropTypes, NativeModules, DeviceEventEmitter } = React; -var iface = { - name: 'ImageView', - propTypes: { - src: PropTypes.string, - borderRadius: PropTypes.number, - resizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch']), - }, +var CAMERA_REF = 'camera'; + +var constants = { + Aspect: NativeModules.CameraModule.Aspect, + BarCodeType: NativeModules.CameraModule.BarCodeType, + Type: NativeModules.CameraModule.Type, + CaptureMode: NativeModules.CameraModule.CaptureMode, + CaptureTarget: NativeModules.CameraModule.CaptureTarget, + Orientation: NativeModules.CameraModule.Orientation, + FlashMode: NativeModules.CameraModule.FlashMode, + TorchMode: NativeModules.CameraModule.TorchMode }; -module.exports = requireNativeComponent('RCTCamera', iface); +var Camera = React.createClass({ + propTypes: { + aspect: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number + ]), + captureAudio: PropTypes.bool, + captureMode: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number + ]), + captureTarget: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number + ]), + type: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number + ]), + orientation: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number + ]), + flashMode: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number + ]), + torchMode: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number + ]), + defaultOnFocusComponent: PropTypes.bool, + onFocusChanged: PropTypes.func, + onZoomChanged: PropTypes.func, + ...View.propTypes + }, + + setNativeProps(props) { + this.refs[CAMERA_REF].setNativeProps(props); + }, + + getDefaultProps() { + return { + aspect: constants.Aspect.fill, + type: constants.Type.back, + orientation: constants.Orientation.auto, + captureAudio: true, + captureMode: constants.CaptureMode.still, + captureTarget: constants.CaptureTarget.cameraRoll, + flashMode: constants.FlashMode.off, + torchMode: constants.TorchMode.off + }; + }, + + getInitialState() { + return { + isAuthorized: false, + isRecording: false + }; + }, + + componentWillMount() { + //// TODO: handle properly Android 6 new permissions style + this.state.isAuthorized = true; + this.setState(this.state); + this.cameraBarCodeReadListener = DeviceEventEmitter.addListener('CameraBarCodeRead', this._onBarCodeRead); + }, + + componentWillUnmount() { + this.cameraBarCodeReadListener.remove(); + + if (this.state.isRecording) { + this.stopCapture(); + } + }, + + render() { + var style = [styles.base, this.props.style]; + + var aspect = this.props.aspect, + type = this.props.type, + orientation = this.props.orientation, + flashMode = this.props.flashMode, + torchMode = this.props.torchMode; + + var legacyProps = { + aspect: { + Fill: 'fill', + Fit: 'fit', + Stretch: 'stretch' + }, + orientation: { + LandscapeLeft: 'landscapeLeft', + LandscapeRight: 'landscapeRight', + Portrait: 'portrait', + PortraitUpsideDown: 'portraitUpsideDown' + }, + type: { + Front: 'front', + Back: 'back' + } + }; + + if (typeof aspect === 'string') { + aspect = constants.Aspect[aspect]; + } + + if (typeof flashMode === 'string') { + flashMode = constants.FlashMode[flashMode]; + } + + if (typeof orientation === 'string') { + orientation = constants.Orientation[orientation]; + } + + if (typeof torchMode === 'string') { + torchMode = constants.TorchMode[torchMode]; + } + + if (typeof type === 'string') { + type = constants.Type[type]; + } + + var nativeProps = Object.assign({}, this.props, { + style, + aspect: aspect, + type: type, + orientation: orientation, + flashMode: flashMode, + torchMode: torchMode + }); + + return ; + }, + + _onBarCodeRead(e) { + this.props.onBarCodeRead && this.props.onBarCodeRead(e); + }, + + capture(options, cb) { + + if (arguments.length == 1) { + cb = options; + options = {}; + } + + options = Object.assign({}, { + audio: this.props.captureAudio, + mode: this.props.captureMode, + target: this.props.captureTarget, + type: this.props.type + }, options); + + if (typeof options.mode === 'string') { + options.mode = constants.CaptureMode[options.mode]; + } + + if (options.mode === constants.CaptureMode.video) { + options.totalSeconds = (options.totalSeconds > -1 ? options.totalSeconds : -1); + options.preferredTimeScale = options.preferredTimeScale || 30; + this.setState({isRecording: true}); + } + + if (typeof options.target === 'string') { + options.target = constants.CaptureTarget[options.target]; + } + + if (typeof options.type === 'string') { + options.type = constants.Type[options.type]; + } + + NativeModules.CameraModule.capture(options, cb); + }, + + stopCapture() { + if (this.state.isRecording) { + NativeModules.CameraManager.stopCapture(); + this.setState({ isRecording: false }); + } + } + +}); + +var RCTCameraView = requireNativeComponent('RCTCameraView', Camera); + +var styles = StyleSheet.create({ + base: {}, +}); + +Camera.constants = constants; +module.exports = Camera;