diff --git a/.eslintrc.js b/.eslintrc.js index b8c7f86..76d6b9d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -50,7 +50,7 @@ module.exports = { 'import/ignore': ['node_modules/react-native/index\\.js$'], // https://github.com/facebook/react-native/issues/28549 'import/resolver': { node: { - extensions: ['.js', '.ts', '.android.js', '.ios.js'], + extensions: ['.js', '.ts', '.android.js', '.ios.js', '.android.tsx', '.ios.tsx'], }, }, }, diff --git a/README.md b/README.md index 75646a0..ac3fd26 100644 --- a/README.md +++ b/README.md @@ -48,19 +48,17 @@ cd ios && pod install && cd .. ## APIs -### CameraKitCamera - Camera component +### Camera - Base Camera component ```js -import { CameraKitCamera } from 'react-native-camera-kit'; +import { Camera } from 'react-native-camera-kit'; ``` ```jsx - (this.camera = cam)} - style={{ - flex: 1, - backgroundColor: 'white', - }} + this.camera = ref} + type={CameraType.Back} // front/back(default) + style={{ flex: 1 }} cameraOptions={{ flashMode: 'auto', // on/off/auto(default) focusMode: 'on', // off/on(default) @@ -68,11 +66,18 @@ import { CameraKitCamera } from 'react-native-camera-kit'; ratioOverlay: '1:1', // optional ratioOverlayColor: '#00000077', // optional }} + resetFocusTimeout={0} // optional + resetFocusWhenMotionDetected={true} // optional + saveToCameraRole={false} // optional + // Barcode Scanner Props + scanBarcode={false} // optional + showFrame={false} // Barcode only, optional + laserColor='red' // Barcode only, optional + frameColor='yellow' // Barcode only, optional + surfaceColor='blue' // Barcode only, optional onReadCode={( event, // optional ) => console.log(event.nativeEvent.codeStringValue)} - resetFocusTimeout={0} // optional - resetFocusWhenMotionDetected={true} // optional /> ``` @@ -94,12 +99,20 @@ import { CameraKitCamera } from 'react-native-camera-kit'; | `ratioOverlay` | `['int':'int', ...]` | Show a guiding overlay in the camera preview for the selected ratio. Does not crop image as of v9.0. Example: `['16:9', '1:1', '3:4']` | | `ratioOverlayColor` | Color | Any color with alpha (default is `'#ffffff77'`) | -### CameraKitCamera API +### Camera API -#### checkDeviceCameraAuthorizationStatus +#### capture({ ... }) - must have the wanted camera capture reference + +Capture image (`{ saveToCameraRoll: boolean }`). Using the camera roll is slower than using regular files stored in your app. On an iPhone X in debug mode, on a real phone, we measured around 100-150ms processing time to save to the camera roll. ```js -const isCameraAuthorized = await CameraKitCamera.checkDeviceCameraAuthorizationStatus(); +const image = await this.camera.capture(); +``` + +#### checkDeviceCameraAuthorizationStatus (iOS only) + +```js +const isCameraAuthorized = await Camera.checkDeviceCameraAuthorizationStatus(); ``` return values: @@ -110,54 +123,22 @@ return values: otherwise, returns `false` -#### requestDeviceCameraAuthorization +#### requestDeviceCameraAuthorization (iOS only) ```js -const isUserAuthorizedCamera = await CameraKitCamera.requestDeviceCameraAuthorization(); +const isUserAuthorizedCamera = await Camera.requestDeviceCameraAuthorization(); ``` `AVAuthorizationStatusAuthorized` returns `true` otherwise, returns `false` -#### capture({ ... }) - must have the wanted camera capture reference - -Capture image (`{ saveToCameraRoll: boolean }`). Using the camera roll is slower than using regular files stored in your app. On an iPhone X in debug mode, on a real phone, we measured around 100-150ms processing time to save to the camera roll. - -```js -const image = await this.camera.capture(); -``` - -#### setFlashMode - must have the wanted camera capture reference - -Set flash mode (`auto`/`on`/`off`) - -```js -const success = await this.camera.setFlashMode(newFlashData.mode); -``` - -#### setTorchMode - must have the wanted camera capture reference - -Set Torch mode (`on`/`off`) - -```js -const success = await this.camera.setTorchMode(newTorchMode); -``` - -#### changeCamera - must have the wanted camera capture reference - -Change to front/rear camera - -```js -const success = await this.camera.changeCamera(); -``` - ## QR Code ```js -import { CameraKitCameraScreen } from 'react-native-camera-kit'; +import { CameraScreen } from 'react-native-camera-kit'; - this.onBottomButtonPressed(event)} scanBarcode={true} diff --git a/android/build.gradle b/android/build.gradle index 4aa7c21..f47921d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' android { compileSdkVersion 28 @@ -21,4 +22,16 @@ dependencies { implementation 'com.facebook.react:react-native:+' implementation 'com.google.zxing:core:3.3.3' implementation group: 'com.drewnoakes', name: 'metadata-extractor', version: '2.12.0' + implementation "androidx.core:core-ktx:+" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + implementation "androidx.camera:camera-core:1.0.0-beta08" + implementation "androidx.camera:camera-camera2:1.0.0-beta08" + implementation "androidx.camera:camera-lifecycle:1.0.0-beta08" + implementation "androidx.camera:camera-view:1.0.0-alpha15" + + implementation 'com.google.mlkit:barcode-scanning:16.0.3' +} +repositories { + mavenCentral() } diff --git a/android/src/main/java/com/rncamerakit/CKCamera.kt b/android/src/main/java/com/rncamerakit/CKCamera.kt new file mode 100644 index 0000000..63ed546 --- /dev/null +++ b/android/src/main/java/com/rncamerakit/CKCamera.kt @@ -0,0 +1,420 @@ +package com.rncamerakit + +import android.Manifest +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.annotation.SuppressLint +import android.app.Activity +import android.content.ContentValues +import android.content.Context +import android.content.pm.PackageManager +import android.graphics.Color +import android.hardware.SensorManager +import android.media.AudioManager +import android.media.MediaActionSound +import android.provider.MediaStore +import android.util.Log +import android.view.* +import android.widget.FrameLayout +import android.widget.LinearLayout +import androidx.appcompat.app.AppCompatActivity +import androidx.camera.core.* +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.camera.view.PreviewView +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.OnLifecycleEvent +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.WritableMap +import com.facebook.react.common.ReactConstants.TAG +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.events.RCTEventEmitter +import java.io.File +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +@SuppressLint("ViewConstructor") // Extra constructors unused. Not using visual layout tools +class CKCamera(context: ThemedReactContext) : FrameLayout(context), LifecycleObserver { + private val currentContext: ThemedReactContext = context + private var preview: Preview? = null + private var imageCapture: ImageCapture? = null + private var qrCodeAnalyzer: ImageAnalysis? = null + private var cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA + private var camera: Camera? = null + private var orientationListener: OrientationEventListener? = null + private var viewFinder: PreviewView = PreviewView(context) + private var cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor() + private var scanBarcode: Boolean = false + private var lensType = CameraSelector.LENS_FACING_BACK + private var autoFocus = "on" + private var cameraProvider: ProcessCameraProvider? = null + private var outputPath: String? = null + private var shutterAnimationDuration: Int = 50 + private var effectLayer = View(context) + private var counter = 0 + + private fun getActivity() : Activity { + return currentContext.currentActivity!! + } + + init { + viewFinder.layoutParams = LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT + ) + installHierarchyFitter(viewFinder) + addView(viewFinder) + + effectLayer.alpha = 0F + effectLayer.setBackgroundColor(Color.BLACK) + addView(effectLayer) + + if (hasPermissions()) { + viewFinder.post { startCamera() } + } + + (getActivity() as AppCompatActivity).lifecycle.addObserver(this) + } + + @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) + fun onResume() { + Log.d(TAG, "onResume") + viewFinder.post { startCamera() } + } + + @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) + fun onPause() { + Log.d(TAG, "onPause") + stopCamera() + } + + @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) + fun onDestroy() { + Log.d(TAG, "onDestroy") + stopCamera() + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + stopCamera() + } + + private fun stopCamera() { + cameraExecutor.shutdown() + orientationListener?.disable() + cameraProvider?.unbindAll() + } + + // If this is not called correctly, view finder will be black/blank + // https://github.com/facebook/react-native/issues/17968#issuecomment-633308615 + private fun installHierarchyFitter(view: ViewGroup) { + Log.d(TAG, "CameraView looking for ThemedReactContext") + if (context is ThemedReactContext) { // only react-native setup + Log.d(TAG, "CameraView found ThemedReactContext") + view.setOnHierarchyChangeListener(object : OnHierarchyChangeListener { + override fun onChildViewRemoved(parent: View?, child: View?) = Unit + override fun onChildViewAdded(parent: View?, child: View?) { + parent?.measure( + MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY) + ) + parent?.layout(0, 0, parent.measuredWidth, parent.measuredHeight) + } + }) + } + } + + @SuppressLint("ClickableViewAccessibility") + private fun startCamera() { + val cameraProviderFuture = ProcessCameraProvider.getInstance(getActivity()) + + val onScaleGestureListener = object: ScaleGestureDetector.SimpleOnScaleGestureListener() { + override fun onScale(detector: ScaleGestureDetector?): Boolean { + val cameraControl = camera?.cameraControl ?: return true + val zoom = camera?.cameraInfo?.zoomState?.value?.zoomRatio ?: return true + val scaleFactor = detector?.scaleFactor ?: return true + val scale = zoom * scaleFactor + + cameraControl.setZoomRatio(scale) + return true + } + } + + cameraProviderFuture.addListener(Runnable { + // Used to bind the lifecycle of cameras to the lifecycle owner + cameraProvider = cameraProviderFuture.get() + val scaleDetector = ScaleGestureDetector(context, onScaleGestureListener) + + cameraSelector = CameraSelector.Builder().requireLensFacing(lensType).build() + preview = Preview.Builder().build().also { + it.setSurfaceProvider(viewFinder.createSurfaceProvider()) + } + imageCapture = ImageCapture.Builder().build() + + // Rotate the image according to device orientation, even when UI orientation is locked + orientationListener = object : OrientationEventListener(context, SensorManager.SENSOR_DELAY_UI) { + override fun onOrientationChanged(orientation: Int) { + val imageCapture = imageCapture ?: return + var newOrientation: Int = imageCapture.targetRotation + if (orientation >= 315 || orientation < 45) { + newOrientation = Surface.ROTATION_0 + } else if (orientation in 225..314) { + newOrientation = Surface.ROTATION_90 + } else if (orientation in 135..224) { + newOrientation = Surface.ROTATION_180 + } else if (orientation in 45..134) { + newOrientation = Surface.ROTATION_270 + } + if (newOrientation != imageCapture.targetRotation) { + imageCapture.targetRotation = newOrientation + onOrientationChange(newOrientation) + } + } + } + orientationListener!!.enable() + + // Contain camera feed image within component bounds, centered + viewFinder.scaleType = PreviewView.ScaleType.FIT_CENTER + + // Tap to focus + viewFinder.setOnTouchListener { _, event -> + if (event.action != MotionEvent.ACTION_UP) { + return@setOnTouchListener scaleDetector.onTouchEvent(event) + } + focusOnPoint(event.x, event.y) + return@setOnTouchListener true + } + + qrCodeAnalyzer = ImageAnalysis.Builder() + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .build() + + val analyzer = QRCodeAnalyzer { barcodes -> + if (barcodes.isNotEmpty()) { + onBarcodeRead(barcodes) + } + } + qrCodeAnalyzer!!.setAnalyzer(cameraExecutor, analyzer) + + val useCases = mutableListOf(preview, imageCapture) + if (scanBarcode) useCases.add(qrCodeAnalyzer!!) + + try { + // Unbind use cases before rebinding + cameraProvider!!.unbindAll() + + // Bind use cases to camera + camera = cameraProvider!!.bindToLifecycle( + getActivity() as AppCompatActivity, + cameraSelector, + *useCases.toTypedArray() + ) + preview!!.setSurfaceProvider(viewFinder.createSurfaceProvider()) + Log.d(TAG, "CameraView: Use cases bound") + } catch (exc: Exception) { + Log.e(TAG, "CameraView: Use cases binding failed", exc) + } + }, ContextCompat.getMainExecutor(getActivity())) + } + + private fun flashViewFinder() { + if (shutterAnimationDuration == 0) return + + effectLayer + .animate() + .alpha(1F) + .setDuration(shutterAnimationDuration.toLong()) + .setListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + effectLayer.animate().alpha(0F).duration = shutterAnimationDuration.toLong() + } + }).start() + } + + fun setShutterAnimationDuration(duration: Int) { + shutterAnimationDuration = duration + } + + fun capture(options: Map, promise: Promise) { + // Create output options object which contains file + metadata + val contentValues = ContentValues().apply { + put(MediaStore.MediaColumns.DISPLAY_NAME, "Untitled") + put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg") + } + + // Create the output file option to store the captured image in MediaStore + val outputOptions = when (outputPath) { + null -> ImageCapture.OutputFileOptions + .Builder( + context.contentResolver, + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + contentValues + ) + .build() + else -> ImageCapture.OutputFileOptions + .Builder(File(outputPath)) + .build() + } + + flashViewFinder() + + val audio = getActivity().getSystemService(Context.AUDIO_SERVICE) as AudioManager + if (audio.ringerMode == AudioManager.RINGER_MODE_NORMAL) { + MediaActionSound().play(MediaActionSound.SHUTTER_CLICK); + } + + // Setup image capture listener which is triggered after photo has + // been taken + imageCapture?.takePicture( + outputOptions, ContextCompat.getMainExecutor(getActivity()), object : ImageCapture.OnImageSavedCallback { + override fun onError(ex: ImageCaptureException) { + Log.e(TAG, "CameraView: Photo capture failed: ${ex.message}", ex) + promise.reject("E_CAPTURE_FAILED", "takePicture failed: ${ex.message}") + } + + override fun onImageSaved(output: ImageCapture.OutputFileResults) { + try { + val savedUri = output.savedUri.toString() + onPictureTaken(savedUri) + Log.d(TAG, "CameraView: Photo capture succeeded: $savedUri") + + val imageInfo = Arguments.createMap() + imageInfo.putString("uri", savedUri) + imageInfo.putString("id", output.savedUri?.path) + imageInfo.putString("name", output.savedUri?.lastPathSegment) + // imageInfo.putInt("size", null) + imageInfo.putInt("width", width) + imageInfo.putInt("height", height) + imageInfo.putString("path", output.savedUri?.path) + + promise.resolve(imageInfo) + } catch (ex: Exception) { + Log.e(TAG, "Error while saving or decoding saved photo: ${ex.message}", ex) + promise.reject("E_ON_IMG_SAVED", "Error while reading saved photo: ${ex.message}") + } + } + }) + } + + private fun focusOnPoint(x: Float?, y: Float?) { + if (x === null || y === null) { + camera?.cameraControl?.cancelFocusAndMetering() + return + } + val factory = viewFinder.meteringPointFactory + val builder = FocusMeteringAction.Builder(factory.createPoint(x, y)) + + // Auto-cancel will clear focus points (and engage AF) after a duration + if (autoFocus == "off") builder.disableAutoCancel() + + camera?.cameraControl?.startFocusAndMetering(builder.build()) + } + + private fun onBarcodeRead(barcodes: List) { + val event: WritableMap = Arguments.createMap() + event.putArray("barcodes", Arguments.makeNativeArray(barcodes)) + currentContext.getJSModule(RCTEventEmitter::class.java).receiveEvent( + id, + "onBarcodeRead", + event + ) + } + + private fun onOrientationChange(orientation: Int) { + val remappedOrientation = when (orientation) { + Surface.ROTATION_0 -> RNCameraKitModule.PORTRAIT + Surface.ROTATION_90 -> RNCameraKitModule.LANDSCAPE_LEFT + Surface.ROTATION_180 -> RNCameraKitModule.PORTRAIT_UPSIDE_DOWN + Surface.ROTATION_270 -> RNCameraKitModule.LANDSCAPE_RIGHT + else -> { + Log.e(TAG, "CameraView: Unknown device orientation detected: $orientation") + return + } + } + + val event: WritableMap = Arguments.createMap() + event.putInt("orientation", remappedOrientation) + currentContext.getJSModule(RCTEventEmitter::class.java).receiveEvent( + id, + "onOrientationChange", + event + ) + } + + private fun onPictureTaken(uri: String) { + val event: WritableMap = Arguments.createMap() + event.putString("uri", uri) + currentContext.getJSModule(RCTEventEmitter::class.java).receiveEvent( + id, + "onPictureTaken", + event + ) + } + + fun setFlashMode(mode: String?) { + val imageCapture = imageCapture ?: return + val camera = camera ?: return + when (mode) { + "torch" -> camera.cameraControl.enableTorch(true) + "on" -> { + camera.cameraControl.enableTorch(false) + imageCapture.flashMode = ImageCapture.FLASH_MODE_ON + } + "off" -> { + camera.cameraControl.enableTorch(false) + imageCapture.flashMode = ImageCapture.FLASH_MODE_OFF + } + else -> { // 'auto' and any wrong values + imageCapture.flashMode = ImageCapture.FLASH_MODE_AUTO + camera.cameraControl.enableTorch(false) + } + } + } + + fun setAutoFocus(mode: String = "on") { + autoFocus = mode + when(mode) { + // "cancel" clear AF points and engages continuous auto-focus + "on" -> camera?.cameraControl?.cancelFocusAndMetering() + // 'off': Handled when you tap to focus + } + } + + fun setScanBarcode(enabled: Boolean) { + val restartCamera = enabled != scanBarcode + scanBarcode = enabled + if (restartCamera) startCamera() + } + + fun setType(type: String = "back") { + val newLensType = when (type) { + "front" -> CameraSelector.LENS_FACING_FRONT + else -> CameraSelector.LENS_FACING_BACK + } + val restartCamera = lensType != newLensType + lensType = newLensType + if (restartCamera) startCamera() + } + + fun setOutputPath(path: String) { + outputPath = path + } + + private fun hasPermissions(): Boolean { + val requiredPermissions = arrayOf(Manifest.permission.CAMERA) + if (requiredPermissions.all { + ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED + }) { + return true + } + ActivityCompat.requestPermissions( + getActivity(), + requiredPermissions, + 42 // random callback identifier + ) + return false + } +} \ No newline at end of file diff --git a/android/src/main/java/com/rncamerakit/CKCameraManager.kt b/android/src/main/java/com/rncamerakit/CKCameraManager.kt new file mode 100644 index 0000000..82c2e26 --- /dev/null +++ b/android/src/main/java/com/rncamerakit/CKCameraManager.kt @@ -0,0 +1,79 @@ +package com.rncamerakit + +import android.util.Log +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.bridge.ReadableType +import com.facebook.react.common.MapBuilder +import com.facebook.react.common.ReactConstants.TAG +import com.facebook.react.uimanager.SimpleViewManager +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.annotations.ReactProp + +class CKCameraManager : SimpleViewManager() { + + override fun getName() : String { + return "CKCameraManager" + } + + override fun createViewInstance(context: ThemedReactContext): CKCamera { + return CKCamera(context) + } + + override fun receiveCommand(view: CKCamera, commandId: String?, args: ReadableArray?) { + var logCommand = "CameraManager received command $commandId(" + for (i in 0..(args?.size() ?: 0)) { + if (i > 0) { + logCommand += ", " + } + logCommand += when (args?.getType(0)) { + ReadableType.Null -> "Null" + ReadableType.Array -> "Array" + ReadableType.Boolean -> "Boolean" + ReadableType.Map -> "Map" + ReadableType.Number -> "Number" + ReadableType.String -> "String" + else -> "" + } + } + logCommand += ")" + Log.d(TAG, logCommand) + } + + override fun getExportedCustomDirectEventTypeConstants(): Map { + return MapBuilder.of( + "onOrientationChange", MapBuilder.of("registrationName", "onOrientationChange"), + "onBarcodeRead", MapBuilder.of("registrationName", "onBarcodeRead"), + "onPictureTaken", MapBuilder.of("registrationName", "onPictureTaken") + ) + } + + @ReactProp(name = "flashMode") + fun setFlashMode(view: CKCamera, mode: String?) { + view.setFlashMode(mode) + } + + @ReactProp(name = "scanBarcode") + fun setScanBarcode(view: CKCamera, enabled: Boolean) { + view.setScanBarcode(enabled) + } + + @ReactProp(name = "type") + fun setType(view: CKCamera, type: String) { + view.setType(type) + } + + @ReactProp(name = "autoFocus") + fun setAutoFocus(view: CKCamera, mode: String) { + view.setAutoFocus(mode) + } + + @ReactProp(name = "outputPath") + fun setOutputPath(view: CKCamera, path: String) { + view.setOutputPath(path) + } + + @ReactProp(name = "shutterAnimationDuration") + fun setShutterAnimationDuration(view: CKCamera, duration: Int) { + view.setShutterAnimationDuration(duration) + } +} \ No newline at end of file diff --git a/android/src/main/java/com/rncamerakit/DeviceUtils.java b/android/src/main/java/com/rncamerakit/DeviceUtils.java deleted file mode 100644 index 61c0c54..0000000 --- a/android/src/main/java/com/rncamerakit/DeviceUtils.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.rncamerakit; - -import android.os.Build; - -public class DeviceUtils { - public static boolean isGoogleDevice() { - return (Build.MANUFACTURER.toLowerCase().contains("google") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) || - (Build.MANUFACTURER.toLowerCase().contains("lge") && Build.BRAND.toLowerCase().equals("google")); - } -} diff --git a/android/src/main/java/com/rncamerakit/QRCodeAnalyzer.kt b/android/src/main/java/com/rncamerakit/QRCodeAnalyzer.kt new file mode 100644 index 0000000..216add8 --- /dev/null +++ b/android/src/main/java/com/rncamerakit/QRCodeAnalyzer.kt @@ -0,0 +1,29 @@ +package com.rncamerakit + +import android.annotation.SuppressLint +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageProxy +import com.google.mlkit.vision.barcode.BarcodeScanning +import com.google.mlkit.vision.common.InputImage + +class QRCodeAnalyzer ( + private val onQRCodesDetected: (qrCodes: List) -> Unit +) : ImageAnalysis.Analyzer { + @SuppressLint("UnsafeExperimentalUsageError") + override fun analyze(image: ImageProxy) { + val inputImage = InputImage.fromMediaImage(image.image!!, image.imageInfo.rotationDegrees) + + val scanner = BarcodeScanning.getClient() + scanner.process(inputImage) + .addOnSuccessListener { barcodes -> + val strBarcodes = mutableListOf() + barcodes.forEach { barcode -> + strBarcodes.add(barcode.rawValue ?: return@forEach) + } + onQRCodesDetected(strBarcodes) + } + .addOnCompleteListener{ + image.close() + } + } +} \ No newline at end of file diff --git a/android/src/main/java/com/rncamerakit/RNCameraKitModule.kt b/android/src/main/java/com/rncamerakit/RNCameraKitModule.kt new file mode 100644 index 0000000..2127f3d --- /dev/null +++ b/android/src/main/java/com/rncamerakit/RNCameraKitModule.kt @@ -0,0 +1,40 @@ +package com.rncamerakit + +import com.facebook.react.bridge.* +import com.facebook.react.uimanager.UIManagerModule + +class RNCameraKitModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { + + companion object { + // 0-indexed, rotates counter-clockwise + // Values map to CameraX's Surface.ROTATION_* constants + const val PORTRAIT = 0 // ⬆️ + const val LANDSCAPE_LEFT = 1 // ⬅️ + const val PORTRAIT_UPSIDE_DOWN = 2 // ⬇️ + const val LANDSCAPE_RIGHT = 3 // ➡️ + } + + override fun getName(): String { + return "RNCameraKitModule" + } + + override fun getConstants(): Map { + return hashMapOf( + "PORTRAIT" to PORTRAIT, + "PORTRAIT_UPSIDE_DOWN" to PORTRAIT_UPSIDE_DOWN, + "LANDSCAPE_LEFT" to LANDSCAPE_LEFT, + "LANDSCAPE_RIGHT" to LANDSCAPE_RIGHT + ) + } + + @ReactMethod + fun capture(options: ReadableMap, viewTag: Int, promise: Promise) { + // CameraManager does not allow us to return values + val context = reactContext + val uiManager = context.getNativeModule(UIManagerModule::class.java) + context.runOnUiQueueThread { + val view = uiManager.resolveView(viewTag) as CKCamera + view.capture(options.toHashMap(), promise) + } + } +} \ No newline at end of file diff --git a/android/src/main/java/com/rncamerakit/RNCameraKitPackage.java b/android/src/main/java/com/rncamerakit/RNCameraKitPackage.java deleted file mode 100644 index 709d602..0000000 --- a/android/src/main/java/com/rncamerakit/RNCameraKitPackage.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.rncamerakit; - -import androidx.annotation.Nullable; - -import com.facebook.react.ReactPackage; -import com.facebook.react.bridge.JavaScriptModule; -import com.facebook.react.bridge.NativeModule; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.uimanager.ViewManager; -import com.rncamerakit.camera.CameraModule; -import com.rncamerakit.camera.CameraViewManager; -import com.rncamerakit.camera.permission.CameraPermissionRequestCallback; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class RNCameraKitPackage implements ReactPackage { - - @Nullable private CameraPermissionRequestCallback cameraPermissionRequestCallback; - - public RNCameraKitPackage() { - - } - - public RNCameraKitPackage(CameraPermissionRequestCallback cameraPermissionRequestCallback) { - this.cameraPermissionRequestCallback = cameraPermissionRequestCallback; - } - - @Override - public List createNativeModules(ReactApplicationContext reactContext) { - List modules = new ArrayList<>(); - - CameraModule cameraModule = new CameraModule(reactContext); - if (cameraPermissionRequestCallback != null) { - cameraPermissionRequestCallback.setCameraModule(cameraModule); - } - modules.add(cameraModule); - - return modules; - } - - // Deprecated RN 0.47 - public List> createJSModules() { - return Collections.emptyList(); - } - - @Override - public List createViewManagers(ReactApplicationContext reactContext) { - List viewManagers = new ArrayList<>(); - viewManagers.add(new CameraViewManager()); - return viewManagers; - } - -} diff --git a/android/src/main/java/com/rncamerakit/RNCameraKitPackage.kt b/android/src/main/java/com/rncamerakit/RNCameraKitPackage.kt new file mode 100644 index 0000000..fc14225 --- /dev/null +++ b/android/src/main/java/com/rncamerakit/RNCameraKitPackage.kt @@ -0,0 +1,22 @@ +package com.rncamerakit + +import com.facebook.react.ReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ViewManager +import java.util.* + +class RNCameraKitPackage : ReactPackage { + override fun createNativeModules(reactContext: ReactApplicationContext): List { + val modules: MutableList = ArrayList() + val cameraModule = RNCameraKitModule(reactContext) + modules.add(cameraModule) + return modules + } + + override fun createViewManagers(reactContext: ReactApplicationContext): List> { + val viewManagers: MutableList> = ArrayList() + viewManagers.add(CKCameraManager()) + return viewManagers + } +} \ No newline at end of file diff --git a/android/src/main/java/com/rncamerakit/SaveImageTask.java b/android/src/main/java/com/rncamerakit/SaveImageTask.java deleted file mode 100644 index ed404ac..0000000 --- a/android/src/main/java/com/rncamerakit/SaveImageTask.java +++ /dev/null @@ -1,261 +0,0 @@ -package com.rncamerakit; - -import android.content.Context; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Matrix; -import android.hardware.Camera; -import android.net.Uri; -import android.os.AsyncTask; -import android.provider.MediaStore; -import android.util.Log; -import android.util.Patterns; - -import androidx.annotation.Nullable; - -import com.drew.imaging.ImageMetadataReader; -import com.drew.imaging.ImageProcessingException; -import com.drew.metadata.Metadata; -import com.drew.metadata.MetadataException; -import com.drew.metadata.exif.ExifIFD0Directory; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.WritableMap; -import com.rncamerakit.camera.CameraViewManager; - -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.net.URL; - -import static com.facebook.react.common.ReactConstants.TAG; - -public class SaveImageTask extends AsyncTask { - - private final Context context; - private final Promise promise; - private boolean saveToCameraRoll; - private String bitmapUrl = null; - - public SaveImageTask(Context context, Promise promise, boolean saveToCameraRoll) { - this.context = context; - this.promise = promise; - this.saveToCameraRoll = saveToCameraRoll; - } - - public SaveImageTask(String bitmapUrl, Context context, Promise promise, boolean saveToCameraRoll) { - this(context, promise, saveToCameraRoll); - this.bitmapUrl = bitmapUrl; - if (this.bitmapUrl != null) { - this.bitmapUrl = this.bitmapUrl.replace("file://",""); - } - } - - private Bitmap getImageBitmapFromRemoteImageFile() { - Bitmap image; - try { - URL url = new URL(bitmapUrl); - image = BitmapFactory.decodeStream(url.openStream()); - } catch (IOException e) { - image = null; - } - return image; - } - - private Bitmap getImageBitmapFromLocalImageFile() { - Bitmap image; - FileInputStream fis; - File imageFile = new File(bitmapUrl); - try { - fis = new FileInputStream(imageFile); - image = BitmapFactory.decodeStream(fis); - fis.close(); - } catch (IOException e) { - e.printStackTrace(); - image = null; - } - - if (imageFile.exists()) { - imageFile.delete(); - } - return image; - } - - private Bitmap getImageBitmap(byte[]... data) { - Bitmap image; - if (bitmapUrl != null) { - if (Patterns.WEB_URL.matcher(bitmapUrl.toLowerCase()).matches()) { - image = getImageBitmapFromRemoteImageFile(); - } else { - image = getImageBitmapFromLocalImageFile(); - } - } - else { - byte[] rawImageData = data[0]; - image = decodeAndRotateIfNeeded(rawImageData); - } - return image; - } - - @Override - protected Void doInBackground(byte[]... data) { - Bitmap image = getImageBitmap(data); - if (image == null) { - promise.reject("CameraKit", "failed to get Bitmap image"); - return null; - } - - WritableMap imageInfo = saveToCameraRoll ? saveToMediaStore(image) : saveTempImageFile(image); - if (imageInfo == null) - promise.reject("CameraKit", "failed to save image to MediaStore"); - else { - promise.resolve(imageInfo); - CameraViewManager.reconnect(); - } - return null; - } - - private WritableMap createImageInfo(String fileUri, String id, String fileName, long fileSize, int width, int height) { - WritableMap imageInfo = Arguments.createMap(); - imageInfo.putString("uri", fileUri); - imageInfo.putString("id", id); - imageInfo.putString("name", fileName); - imageInfo.putInt("size", (int) fileSize); - imageInfo.putInt("width", width); - imageInfo.putInt("height", height); - return imageInfo; - } - - private WritableMap saveToMediaStore(Bitmap image) { - try { - String fileUri = MediaStore.Images.Media.insertImage(context.getContentResolver(), image, System.currentTimeMillis() + "", ""); - Cursor cursor = context.getContentResolver().query(Uri.parse(fileUri), new String[]{ - MediaStore.Images.ImageColumns.DATA, - MediaStore.Images.ImageColumns.DISPLAY_NAME - }, null, null, null); - cursor.moveToFirst(); - int pathIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATA); - int nameIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DISPLAY_NAME); - String filePath = cursor.getString(pathIndex); - String fileName = cursor.getString(nameIndex); - long fileSize = new File(filePath).length(); - cursor.close(); - - return createImageInfo(fileUri, filePath, fileName, fileSize, image.getWidth(), image.getHeight()); - } catch (Exception e) { - return null; - } - } - - private Bitmap decodeAndRotateIfNeeded(byte[] rawImageData) { - Matrix bitmapMatrix = getRotationMatrix(rawImageData); - Bitmap image = BitmapFactory.decodeByteArray(rawImageData, 0, rawImageData.length); - if (bitmapMatrix.isIdentity()) - return image; - else - return rotateImage(image, bitmapMatrix); - } - - private Bitmap rotateImage(Bitmap image, Matrix bitmapMatrix) { - return Bitmap.createBitmap(image, 0, 0, image.getWidth(), image.getHeight(), bitmapMatrix, false); - } - - private Matrix getRotationMatrix(byte[] rawImageData) { - try { - return tryGetRotationMatrix(rawImageData); - } catch (Exception e) { - return new Matrix(); - } - } - - private Matrix tryGetRotationMatrix(byte[] rawImageData) throws ImageProcessingException, IOException, MetadataException { - Matrix matrix = new Matrix(); - Metadata metadata = readMetadata(rawImageData); - final ExifIFD0Directory exifIFD0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class); - boolean hasOrientation = exifIFD0Directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION); - if (hasOrientation) { - final int exifOrientation = exifIFD0Directory.getInt(ExifIFD0Directory.TAG_ORIENTATION); - boolean isFacingFront = CameraViewManager.getCameraInfo().facing == Camera.CameraInfo.CAMERA_FACING_FRONT; - convertExifOrientationToMatrix(matrix, exifOrientation, isFacingFront); - } - return matrix; - } - - private void convertExifOrientationToMatrix(Matrix matrix, int exifOrientation, boolean isCameraFacingFront) { - switch (exifOrientation) { - case 1: - break; // top left - case 2: - matrix.postScale(-1, 1); - break; // top right - case 3: - matrix.postRotate(180); - break; // bottom right - case 4: - matrix.postRotate(180); - matrix.postScale(-1, 1); - break; // bottom left - case 5: - matrix.postRotate(90); - matrix.postScale(-1, 1); - break; // left top - case 6: - matrix.postRotate(90); - break; // right top - case 7: - matrix.postRotate(270); - matrix.postScale(-1, 1); - break; // right bottom - case 8: - matrix.postRotate(270); - break; // left bottom - default: - break; // Unknown - } - if (isCameraFacingFront) { - matrix.postRotate(180); - } - } - - private Metadata readMetadata(byte[] rawImageData) throws ImageProcessingException, IOException { - Metadata metadata = null; - ByteArrayInputStream inputStream = null; - BufferedInputStream bufferedInputStream = null; - try { - inputStream = new ByteArrayInputStream(rawImageData); - bufferedInputStream = new BufferedInputStream(inputStream); - metadata = ImageMetadataReader.readMetadata(bufferedInputStream, rawImageData.length); - } finally { - if (bufferedInputStream != null) bufferedInputStream.close(); - if (inputStream != null) inputStream.close(); - } - return metadata; - } - - @Nullable - private WritableMap saveTempImageFile(Bitmap image) { - File imageFile; - FileOutputStream outputStream; - - Long tsLong = System.currentTimeMillis()/1000; - String fileName = "temp_Image_" + tsLong.toString() + ".jpg"; - - try { - imageFile = new File(context.getCacheDir(), fileName); - if (imageFile.exists()) { - imageFile.delete(); - } - outputStream = new FileOutputStream(imageFile); - image.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); - outputStream.close(); - } catch (IOException e) { - Log.d(TAG, "Error accessing file: " + e.getMessage()); - imageFile = null; - } - return (imageFile != null) ? createImageInfo(Uri.fromFile(imageFile).toString(), imageFile.getAbsolutePath(), fileName, imageFile.length(), image.getWidth(), image.getHeight()) : null; - } -} diff --git a/android/src/main/java/com/rncamerakit/SharedPrefs.java b/android/src/main/java/com/rncamerakit/SharedPrefs.java deleted file mode 100644 index b3ed167..0000000 --- a/android/src/main/java/com/rncamerakit/SharedPrefs.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.rncamerakit; - -import android.content.Context; -import android.content.SharedPreferences; - -// We're saving in shared preferences if a permission was requested since for some unknown reason, -// activitycompat.shouldshowrequestpermissionrationale always returned false -public class SharedPrefs { - public static boolean getBoolean(Context context, String key) { - return prefs(context).getBoolean(key, false); - } - - public static void putBoolean(Context context, String key, boolean value) { - prefs(context).edit().putBoolean(key, value).apply(); - } - - private static SharedPreferences prefs(Context context) { - return context.getSharedPreferences("RN_CAMERA_KIT", Context.MODE_PRIVATE); - } - - -} diff --git a/android/src/main/java/com/rncamerakit/Utils.java b/android/src/main/java/com/rncamerakit/Utils.java deleted file mode 100644 index e5cdce4..0000000 --- a/android/src/main/java/com/rncamerakit/Utils.java +++ /dev/null @@ -1,224 +0,0 @@ -package com.rncamerakit; - -import android.content.ContentResolver; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.net.Uri; -import androidx.annotation.NonNull; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.WritableMap; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Date; - -import javax.annotation.Nullable; - -public class Utils { - - private final static String CONTENT_PREFIX = "content://"; - public final static String FILE_PREFIX = "file://"; - private static final int MAX_SAMPLE_SIZE = 8; - private static final float MAX_SCREEN_RATIO = 16 / 9f; - - @Nullable - public static String getStringSafe(ReadableMap map, String key) { - if (map.hasKey(key)) { - return map.getString(key); - } - return null; - } - - public static @Nullable Integer getIntSafe(ReadableMap map, String key) { - if (map.hasKey(key)) { - return map.getInt(key); - } - return null; - } - - public static @Nullable Boolean getBooleanSafe(ReadableMap map, String key) { - if (map.hasKey(key)) { - return map.getBoolean(key); - } - return null; - } - - public static @NonNull ArrayList readableArrayToList(ReadableArray items) { - ArrayList list = new ArrayList<>(); - for(int i = 0; i < items.size(); i++) { - list.add(items.getString(i)); - } - return list; - } - - - @NonNull - public static WritableMap resizeImage(Context context, ReadableMap image, String imageUrlString, int maxResolution, int compressionQuality) throws IOException { - Bitmap sourceImage; - sourceImage = Utils.loadBitmapFromFile(context, imageUrlString, maxResolution, maxResolution); - - if (sourceImage == null) { - throw new IOException("Unable to load source image from path"); - } - - Bitmap scaledImage = Utils.resizeImage(sourceImage, maxResolution, maxResolution); - if (sourceImage != scaledImage) { - sourceImage.recycle(); - } - - // Save the resulting image - File path = context.getCacheDir(); - String resizedImagePath = Utils.saveImage(scaledImage, path, Long.toString(new Date().getTime()), Bitmap.CompressFormat.JPEG, compressionQuality); - - // Clean up remaining image - scaledImage.recycle(); - - WritableMap ans = Arguments.createMap(); - ans.merge(image); - ans.putString("uri", FILE_PREFIX+resizedImagePath); - ans.putInt("size", (int)new File(resizedImagePath).length()); - ans.putInt("width", scaledImage.getWidth()); - ans.putInt("height", scaledImage.getHeight()); - return ans; - } - - - /** - * Resize the specified bitmap, keeping its aspect ratio. - */ - private static Bitmap resizeImage(Bitmap image, int maxWidth, int maxHeight) { - Bitmap newImage = null; - if (image == null) { - return null; // Can't load the image from the given path. - } - - if (maxHeight > 0 && maxWidth > 0) { - float width = image.getWidth(); - float height = image.getHeight(); - - float ratio = Math.min((float)maxWidth / width, (float)maxHeight / height); - - int finalWidth = (int) (width * ratio); - int finalHeight = (int) (height * ratio); - newImage = Bitmap.createScaledBitmap(image, finalWidth, finalHeight, true); - } - - return newImage; - } - - - /** - * Compute the inSampleSize value to use to load a bitmap. - * Adapted from https://developer.android.com/training/displaying-bitmaps/load-bitmap.html - */ - public static int calculateInSampleSize(int width, int height, int reqWidth, int reqHeight) { - - if (reqHeight == 0 || reqWidth == 0) { - return 1; - } - - int inSampleSize = 1; - - if (height > reqHeight || width > reqWidth) { - - final int halfHeight = height / 2; - final int halfWidth = width / 2; - - while (inSampleSize <= MAX_SAMPLE_SIZE - && (halfHeight / inSampleSize) >= reqHeight - && (halfWidth / inSampleSize) >= reqWidth) { - inSampleSize *= 2; - } - } - - return inSampleSize; - } - - private static Bitmap loadBitmap(Context context, String imagePath, BitmapFactory.Options options) throws IOException { - Bitmap sourceImage = null; - if (!imagePath.startsWith(CONTENT_PREFIX)) { - try { - sourceImage = BitmapFactory.decodeFile(imagePath, options); - } catch (Exception e) { - e.printStackTrace(); - throw new IOException("Error decoding image file"); - } - } else { - ContentResolver cr = context.getContentResolver(); - InputStream input = cr.openInputStream(Uri.parse(imagePath)); - if (input != null) { - sourceImage = BitmapFactory.decodeStream(input, null, options); - input.close(); - } - } - return sourceImage; - } - - /** - * Loads the bitmap resource from the file specified in imagePath. - */ - private static Bitmap loadBitmapFromFile(Context context, String imagePath, int newWidth, - int newHeight) throws IOException { - // Decode the image bounds to find the size of the source image. - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - loadBitmap(context, imagePath, options); - - // Set a sample size according to the image size to lower memory usage. - options.inSampleSize = calculateInSampleSize(options.outWidth, options.outHeight , newWidth, newHeight); - options.inJustDecodeBounds = false; - return loadBitmap(context, imagePath, options); - } - - - /** - * Save the given bitmap in a directory. Extension is automatically generated using the bitmap format. - */ - private static String saveImage(Bitmap bitmap, File saveDirectory, String fileName, - Bitmap.CompressFormat compressFormat, int quality) - throws IOException { - if (bitmap == null) { - throw new IOException("The bitmap couldn't be resized"); - } - - File newFile = new File(saveDirectory, fileName + "." + compressFormat.name()); - if(!newFile.createNewFile()) { - throw new IOException("The file already exists"); - } - - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - bitmap.compress(compressFormat, quality, outputStream); - byte[] bitmapData = outputStream.toByteArray(); - - outputStream.flush(); - outputStream.close(); - - FileOutputStream fos = new FileOutputStream(newFile); - fos.write(bitmapData); - fos.flush(); - fos.close(); - - return newFile.getAbsolutePath(); - } - - /** - * Since Camera API 1 doesn't support the new 18:9 and 18.5:9 screen aspect ratio, we convert to the - * max supported aspect ratio - 16:9 - */ - public static int convertDeviceHeightToSupportedAspectRatio(float actualWidth, float actualHeight) { - return (int) (actualHeight / actualWidth > MAX_SCREEN_RATIO ? actualWidth * MAX_SCREEN_RATIO : actualHeight); - } - - - public static void runOnWorkerThread(Runnable runnable) { - new Thread(runnable).start(); - } -} diff --git a/android/src/main/java/com/rncamerakit/camera/CameraModule.java b/android/src/main/java/com/rncamerakit/camera/CameraModule.java deleted file mode 100644 index fd50590..0000000 --- a/android/src/main/java/com/rncamerakit/camera/CameraModule.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.rncamerakit.camera; - -import android.hardware.Camera; - -import com.facebook.react.bridge.LifecycleEventListener; -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; -import com.rncamerakit.camera.commands.Capture; -import com.rncamerakit.camera.permission.CameraPermission; - - -public class CameraModule extends ReactContextBaseJavaModule { - - private final CameraPermission cameraPermission; - private Promise checkPermissionStatusPromise; - - public CameraModule(ReactApplicationContext reactContext) { - super(reactContext); - cameraPermission = new CameraPermission(); - checkPermissionWhenActivityIsAvailable(); - } - - private void checkPermissionWhenActivityIsAvailable() { - getReactApplicationContext().addLifecycleEventListener(new LifecycleEventListener() { - @Override - public void onHostResume() { - if (checkPermissionStatusPromise != null && getCurrentActivity() != null) { - getCurrentActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - checkPermissionStatusPromise.resolve(cameraPermission.checkAuthorizationStatus(getCurrentActivity())); - checkPermissionStatusPromise = null; - } - }); - } - } - - @Override - public void onHostPause() { - - } - - @Override - public void onHostDestroy() { - - } - }); - } - - @Override - public String getName() { - return "RNKitCameraModule"; - } - - @ReactMethod - public void checkDeviceCameraAuthorizationStatus(Promise promise) { - if (getCurrentActivity() == null) { - checkPermissionStatusPromise = promise; - } else { - promise.resolve(cameraPermission.checkAuthorizationStatus(getCurrentActivity())); - } - } - - @ReactMethod - public void requestDeviceCameraAuthorization(Promise promise) { - cameraPermission.requestAccess(getCurrentActivity(), promise); - } - - @ReactMethod - public void hasFrontCamera(Promise promise) { - - int numCameras = Camera.getNumberOfCameras(); - for (int i = 0; i < numCameras; i++) { - Camera.CameraInfo info = new Camera.CameraInfo(); - Camera.getCameraInfo(i, info); - if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { - promise.resolve(true); - return; - } - } - promise.resolve(false); - } - - @ReactMethod - public void hasFlashForCurrentCamera(Promise promise) { - Camera camera = CameraViewManager.getCamera(); - promise.resolve(camera.getParameters().getSupportedFlashModes() != null); - } - - @ReactMethod - public void changeCamera(Promise promise) { - promise.resolve(CameraViewManager.changeCamera()); - } - - @ReactMethod - public void setFlashMode(String mode, Promise promise) { - promise.resolve(CameraViewManager.setFlashMode(mode)); - } - - @ReactMethod - public void getFlashMode(Promise promise) { - Camera camera = CameraViewManager.getCamera(); - promise.resolve(camera.getParameters().getFlashMode()); - } - - @ReactMethod - public void capture(boolean saveToCameraRoll, final Promise promise) { - new Capture(getReactApplicationContext(), saveToCameraRoll).execute(promise); - } - - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - cameraPermission.onRequestPermissionsResult(requestCode, permissions, grantResults); - } -} diff --git a/android/src/main/java/com/rncamerakit/camera/CameraView.java b/android/src/main/java/com/rncamerakit/camera/CameraView.java deleted file mode 100644 index d775e39..0000000 --- a/android/src/main/java/com/rncamerakit/camera/CameraView.java +++ /dev/null @@ -1,141 +0,0 @@ -package com.rncamerakit.camera; - -import android.graphics.Color; -import android.graphics.Rect; -import androidx.annotation.ColorInt; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.View; -import android.widget.FrameLayout; - -import com.facebook.react.uimanager.ThemedReactContext; -import com.rncamerakit.Utils; -import com.rncamerakit.camera.barcode.BarcodeFrame; - -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; - -public class CameraView extends FrameLayout implements SurfaceHolder.Callback { - private SurfaceView surface; - - private boolean showFrame; - private Rect frameRect; - private BarcodeFrame barcodeFrame; - @ColorInt private int frameColor = Color.GREEN; - @ColorInt private int laserColor = Color.RED; - - public CameraView(ThemedReactContext context) { - super(context); - surface = new SurfaceView(context); - setBackgroundColor(Color.BLACK); - addView(surface, MATCH_PARENT, MATCH_PARENT); - surface.getHolder().addCallback(this); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - int actualPreviewWidth = getResources().getDisplayMetrics().widthPixels; - int actualPreviewHeight = getResources().getDisplayMetrics().heightPixels; - int height = Utils.convertDeviceHeightToSupportedAspectRatio(actualPreviewWidth, actualPreviewHeight); - surface.layout(0, 0, actualPreviewWidth, height); - if (barcodeFrame != null) { - ((View) barcodeFrame).layout(0, 0, actualPreviewWidth, height); - } - } - - @Override - public void surfaceCreated(SurfaceHolder holder) { - CameraViewManager.setCameraView(this); - } - - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - CameraViewManager.setCameraView(this); - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - CameraViewManager.removeCameraView(); - } - - - public SurfaceHolder getHolder() { - return surface.getHolder(); - } - - private final Runnable measureAndLayout = new Runnable() { - @Override - public void run() { - measure( - MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY)); - layout(getLeft(), getTop(), getRight(), getBottom()); - } - }; - - @Override - public void requestLayout() { - super.requestLayout(); - post(measureAndLayout); - } - - public void setShowFrame(boolean showFrame) { - this.showFrame = showFrame; - } - - public void showFrame() { - if (showFrame) { - barcodeFrame = new BarcodeFrame(getContext()); - barcodeFrame.setFrameColor(frameColor); - barcodeFrame.setLaserColor(laserColor); - addView(barcodeFrame); - requestLayout(); - } - } - - public Rect getFramingRectInPreview(int previewWidth, int previewHeight) { - if (frameRect == null) { - if (barcodeFrame != null) { - Rect framingRect = new Rect(barcodeFrame.getFrameRect()); - int frameWidth = barcodeFrame.getWidth(); - int frameHeight = barcodeFrame.getHeight(); - - if (previewWidth < frameWidth) { - framingRect.left = framingRect.left * previewWidth / frameWidth; - framingRect.right = framingRect.right * previewWidth / frameWidth; - } - if (previewHeight < frameHeight) { - framingRect.top = framingRect.top * previewHeight / frameHeight; - framingRect.bottom = framingRect.bottom * previewHeight / frameHeight; - } - - frameRect = framingRect; - } else { - frameRect = new Rect(0, 0, previewWidth, previewHeight); - } - } - return frameRect; - } - - public void setFrameColor(@ColorInt int color) { - this.frameColor = color; - if (barcodeFrame != null) { - barcodeFrame.setFrameColor(color); - } - } - - public void setLaserColor(@ColorInt int color) { - this.laserColor = color; - if (barcodeFrame != null) { - barcodeFrame.setLaserColor(laserColor); - } - } - - /** - * Set background color for Surface view on the period, while camera is not loaded yet. - * Provides opportunity for user to hide period while camera is loading - * @param color - color of the surfaceview - */ - public void setSurfaceBgColor(@ColorInt int color) { - surface.setBackgroundColor(color); - } -} diff --git a/android/src/main/java/com/rncamerakit/camera/CameraViewManager.java b/android/src/main/java/com/rncamerakit/camera/CameraViewManager.java deleted file mode 100644 index 16d801c..0000000 --- a/android/src/main/java/com/rncamerakit/camera/CameraViewManager.java +++ /dev/null @@ -1,339 +0,0 @@ -package com.rncamerakit.camera; - -import android.content.Context; -import android.graphics.Color; -import android.graphics.PixelFormat; -import android.graphics.Point; -import android.graphics.Rect; -import android.hardware.Camera; -import android.hardware.SensorManager; -import androidx.annotation.ColorInt; -import androidx.annotation.IntRange; - -import android.view.Display; -import android.view.OrientationEventListener; -import android.view.WindowManager; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.common.MapBuilder; -import com.facebook.react.uimanager.SimpleViewManager; -import com.facebook.react.uimanager.ThemedReactContext; -import com.facebook.react.uimanager.annotations.ReactProp; -import com.facebook.react.uimanager.events.RCTEventEmitter; -import com.google.zxing.Result; -import com.rncamerakit.Utils; -import com.rncamerakit.camera.barcode.BarcodeScanner; - -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.Stack; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.annotation.Nullable; - -import static com.rncamerakit.camera.Orientation.getSupportedRotation; - -@SuppressWarnings("MagicNumber deprecation") -// We're still using Camera API 1, everything is deprecated -public class CameraViewManager extends SimpleViewManager { - - private static Camera camera = null; - private static int currentCamera = 0; - private static String flashMode = Camera.Parameters.FLASH_MODE_AUTO; - private static Stack cameraViews = new Stack<>(); - private static ThemedReactContext reactContext; - private static OrientationEventListener orientationListener; - private static int currentRotation = 0; - private static AtomicBoolean cameraReleased = new AtomicBoolean(false); - - private static boolean shouldScan = false; - - private static BarcodeScanner scanner; - private static Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() { - @Override - public void onPreviewFrame(final byte[] data, final Camera camera) { - Utils.runOnWorkerThread(new Runnable() { - @Override - public void run() { - if (scanner != null) { - scanner.onPreviewFrame(data, camera); - } - } - }); - } - }; - - public static Camera getCamera() { - return camera; - } - - @Override - public String getName() { - return "CameraView"; - } - - @Override - protected CameraView createViewInstance(ThemedReactContext reactContext) { - CameraViewManager.reactContext = reactContext; - return new CameraView(reactContext); - } - - static void setCameraView(CameraView cameraView) { - if (!cameraViews.isEmpty() && cameraViews.peek() == cameraView) return; - CameraViewManager.cameraViews.push(cameraView); - connectHolder(); - createOrientationListener(); - } - - private static void createOrientationListener() { - if (orientationListener != null) return; - orientationListener = new OrientationEventListener(reactContext, SensorManager.SENSOR_DELAY_NORMAL) { - @Override - public void onOrientationChanged(@IntRange(from = -1, to = 359) int angle) { - if (angle == OrientationEventListener.ORIENTATION_UNKNOWN) return; - setCameraRotation(359 - angle, false); - } - }; - orientationListener.enable(); - } - - static boolean setFlashMode(String mode) { - if (camera == null) { - return false; - } - Camera.Parameters parameters = camera.getParameters(); - List supportedModes = parameters.getSupportedFlashModes(); - if (supportedModes != null && supportedModes.contains(mode)) { - flashMode = mode; - parameters.setFlashMode(flashMode); - camera.setParameters(parameters); - camera.startPreview(); - return true; - } else { - return false; - } - } - - static boolean changeCamera() { - if (Camera.getNumberOfCameras() == 1) { - return false; - } - currentCamera++; - currentCamera = currentCamera % Camera.getNumberOfCameras(); - initCamera(); - connectHolder(); - - return true; - } - - private static void initCamera() { - if (camera != null) { - releaseCamera(); - } - try { - camera = Camera.open(currentCamera); - updateCameraSize(); - cameraReleased.set(false); - setCameraRotation(currentRotation, true); - } catch (RuntimeException e) { - e.printStackTrace(); - } - setBarcodeScanner(); - } - - private static void releaseCamera() { - camera.setOneShotPreviewCallback(null); - cameraReleased.set(true); - camera.release(); - } - - private static void connectHolder() { - if (cameraViews.isEmpty() || cameraViews.peek().getHolder() == null) return; - - new Thread(new Runnable() { - @Override - public void run() { - if (camera == null) { - initCamera(); - } - - if (cameraViews.isEmpty()) { - return; - } - - cameraViews.peek().post(new Runnable() { - @Override - public void run() { - try { - camera.stopPreview(); - camera.setPreviewDisplay(cameraViews.peek().getHolder()); - camera.startPreview(); - if (shouldScan) { - camera.setOneShotPreviewCallback(previewCallback); - } - cameraViews.peek().setSurfaceBgColor(Color.TRANSPARENT); - cameraViews.peek().showFrame(); - } catch (IOException | RuntimeException e) { - e.printStackTrace(); - } - } - }); - } - }).start(); - } - - static void removeCameraView() { - if (!cameraViews.isEmpty()) { - cameraViews.pop(); - } - if (!cameraViews.isEmpty()) { - connectHolder(); - } else if (camera != null) { - releaseCamera(); - camera = null; - } - if (cameraViews.isEmpty()) { - clearOrientationListener(); - } - } - - private static void clearOrientationListener() { - if (orientationListener != null) { - orientationListener.disable(); - orientationListener = null; - } - } - - private static void setCameraRotation(int rotation, boolean force) { - if (camera == null) return; - int supportedRotation = getSupportedRotation(rotation); - if (supportedRotation == currentRotation && !force) return; - currentRotation = supportedRotation; - - if (cameraReleased.get()) return; - Camera.Parameters parameters = camera.getParameters(); - parameters.setRotation(supportedRotation); - parameters.setPictureFormat(PixelFormat.JPEG); - camera.setDisplayOrientation(Orientation.getDeviceOrientation(reactContext.getCurrentActivity())); - camera.setParameters(parameters); - } - - public static Camera.CameraInfo getCameraInfo() { - Camera.CameraInfo info = new Camera.CameraInfo(); - Camera.getCameraInfo(currentCamera, info); - return info; - } - - private static Camera.Size getOptimalPreviewSize(List sizes, int w, int h) { - final double ASPECT_TOLERANCE = 0.15; - double targetRatio = (double) h / w; - if (sizes == null) return null; - Camera.Size optimalSize = null; - double minDiff = Double.MAX_VALUE; - for (Camera.Size size : sizes) { - double ratio = (double) size.width / size.height; - if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue; - if (Math.abs(size.height - h) < minDiff) { - optimalSize = size; - minDiff = Math.abs(size.height - h); - } - } - if (optimalSize == null) { - minDiff = Double.MAX_VALUE; - for (Camera.Size size : sizes) { - if (Math.abs(size.height - h) < minDiff) { - optimalSize = size; - minDiff = Math.abs(size.height - h); - } - } - } - return optimalSize; - } - - private static void updateCameraSize() { - try { - Camera camera = CameraViewManager.getCamera(); - - WindowManager wm = (WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE); - Display display = wm.getDefaultDisplay(); - Point size = new Point(); - display.getSize(size); - size.y = Utils.convertDeviceHeightToSupportedAspectRatio(size.x, size.y); - if (camera == null) return; - List supportedPreviewSizes = camera.getParameters().getSupportedPreviewSizes(); - List supportedPictureSizes = camera.getParameters().getSupportedPictureSizes(); - Camera.Size optimalSize = getOptimalPreviewSize(supportedPreviewSizes, size.x, size.y); - Camera.Size optimalPictureSize = getOptimalPreviewSize(supportedPictureSizes, size.x, size.y); - Camera.Parameters parameters = camera.getParameters(); - parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); - parameters.setPreviewSize(optimalSize.width, optimalSize.height); - parameters.setPictureSize(optimalPictureSize.width, optimalPictureSize.height); - parameters.setFlashMode(flashMode); - camera.setParameters(parameters); - } catch (RuntimeException ignored) { - } - } - - public static void reconnect() { - connectHolder(); - } - - public static int getRotationCount() { - return currentRotation / 90; - } - - public static void setBarcodeScanner() { - scanner = new BarcodeScanner(previewCallback, new BarcodeScanner.ResultHandler() { - @Override - public void handleResult(Result result) { - WritableMap event = Arguments.createMap(); - event.putString("codeStringValue", result.getText()); - if (!cameraViews.empty()) - reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(cameraViews.peek().getId(), "onReadCode", event); - } - }); - } - - @Nullable - @Override - public Map getExportedCustomDirectEventTypeConstants() { - return MapBuilder.builder() - .put("onReadCode", - MapBuilder.of("registrationName", "onReadCode")) - .build(); - } - - @ReactProp(name = "scanBarcode") - public void setShouldScan(CameraView view, boolean scanBarcode) { - shouldScan = scanBarcode; - if (shouldScan && camera != null) { - camera.setOneShotPreviewCallback(previewCallback); - } - } - - @ReactProp(name = "showFrame", defaultBoolean = false) - public void setFrame(CameraView view, boolean show) { - view.setShowFrame(show); - } - - @ReactProp(name = "frameColor", defaultInt = Color.GREEN) - public void setFrameColor(CameraView view, @ColorInt int color) { - view.setFrameColor(color); - } - - @ReactProp(name = "laserColor", defaultInt = Color.RED) - public void setLaserColor(CameraView view, @ColorInt int color) { - view.setLaserColor(color); - } - - @ReactProp(name = "surfaceColor") - public void setSurfaceBackground(CameraView view, @ColorInt int color) { - view.setSurfaceBgColor(color); - } - - public static synchronized Rect getFramingRectInPreview(int previewWidth, int previewHeight) { - return cameraViews.peek().getFramingRectInPreview(previewWidth, previewHeight); - } -} diff --git a/android/src/main/java/com/rncamerakit/camera/Orientation.java b/android/src/main/java/com/rncamerakit/camera/Orientation.java deleted file mode 100644 index cae53b7..0000000 --- a/android/src/main/java/com/rncamerakit/camera/Orientation.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.rncamerakit.camera; - -import android.app.Activity; -import android.hardware.Camera; -import android.view.Surface; - -import com.rncamerakit.DeviceUtils; - -import static com.rncamerakit.camera.CameraViewManager.getCameraInfo; - -@SuppressWarnings({"MagicNumber", "deprecation"}) -class Orientation { - private static final int PORTRAIT_ROTATION = 90; - - static int getDeviceOrientation(Activity activity) { - if (activity == null) return PORTRAIT_ROTATION; - int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); - Camera.CameraInfo info = getCameraInfo(); - int degrees = 0; - switch (rotation) { - case Surface.ROTATION_0: degrees = 0; break; - case Surface.ROTATION_90: degrees = 90; break; - case Surface.ROTATION_180: degrees = 180; break; - case Surface.ROTATION_270: degrees = 270; break; - } - - int result; - if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { - result = (info.orientation + degrees) % 360; - result = (360 - result) % 360; // compensate the mirror - } else { // back-facing - result = (info.orientation - degrees + 360) % 360; - } - return result; - } - - static int getSupportedRotation(int rotation) { - int degrees = convertRotationToSupportedAxis(rotation); - return isFrontFacingCamera() ? adaptFrontCamera(degrees) : adaptBackCamera(degrees); - } - - private static int convertRotationToSupportedAxis(int rotation) { - if (rotation < 45) { - return 0; - } else if (rotation < 135) { - return 90; - } else if (rotation < 225) { - return 180; - } else if (rotation < 315){ - return 270; - } - return 0; - } - - private static boolean isFrontFacingCamera() { - return getCameraInfo().facing == Camera.CameraInfo.CAMERA_FACING_FRONT; - } - - private static int adaptBackCamera(int degrees) { - return (getCameraInfo().orientation - degrees + 360) % 360; - } - - private static int adaptFrontCamera(int degrees) { - if (DeviceUtils.isGoogleDevice()) { - int result = (getCameraInfo().orientation + degrees) % 360; - result = (result) % 360; // compensate the mirror - return result; - } else { - int result = (getCameraInfo().orientation + degrees + 180) % 360; - result = (result) % 360; // compensate the mirror - return result; - } - } -} diff --git a/android/src/main/java/com/rncamerakit/camera/barcode/BarcodeFrame.java b/android/src/main/java/com/rncamerakit/camera/barcode/BarcodeFrame.java deleted file mode 100644 index 52c9de0..0000000 --- a/android/src/main/java/com/rncamerakit/camera/barcode/BarcodeFrame.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.rncamerakit.camera.barcode; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.Rect; -import androidx.annotation.ColorInt; -import android.view.View; - - -import com.rncamerakit.R; - -public class BarcodeFrame extends View { - - private static final int STROKE_WIDTH = 5; - private static final int ANIMATION_SPEED = 8; - private static final int WIDTH_SCALE = 7; - private static final double HEIGHT_SCALE = 2.75; - - private Paint dimPaint; - private Paint framePaint; - private Paint borderPaint; - private Paint laserPaint; - private Rect frameRect; - private int width; - private int height; - private int borderMargin; - - private long previousFrameTime = System.currentTimeMillis(); - private int laserY; - - public BarcodeFrame(Context context) { - super(context); - init(context); - } - - private void init(Context context) { - framePaint = new Paint(); - framePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); - dimPaint = new Paint(); - dimPaint.setStyle(Paint.Style.FILL); - dimPaint.setColor(context.getResources().getColor(R.color.bg_dark)); - borderPaint = new Paint(); - borderPaint.setStyle(Paint.Style.STROKE); - borderPaint.setStrokeWidth(STROKE_WIDTH); - laserPaint = new Paint(); - laserPaint.setStyle(Paint.Style.STROKE); - laserPaint.setStrokeWidth(STROKE_WIDTH); - - frameRect = new Rect(); - borderMargin = context.getResources().getDimensionPixelSize(R.dimen.border_length); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - width = getMeasuredWidth(); - height = getMeasuredHeight(); - int marginWidth = width / WIDTH_SCALE; - int marginHeight = (int) (height / HEIGHT_SCALE); - - frameRect.left = marginWidth; - frameRect.right = width - marginWidth; - frameRect.top = marginHeight; - frameRect.bottom = height - marginHeight; - } - - @Override - protected void onDraw(Canvas canvas) { - long timeElapsed = (System.currentTimeMillis() - previousFrameTime); - super.onDraw(canvas); - canvas.drawRect(0, 0, width, height, dimPaint); - canvas.drawRect(frameRect, framePaint); - drawBorder(canvas); - drawLaser(canvas, timeElapsed); - previousFrameTime = System.currentTimeMillis(); - this.invalidate(frameRect); - } - - private void drawBorder(Canvas canvas) { - canvas.drawLine(frameRect.left, frameRect.top, frameRect.left, frameRect.top + borderMargin, borderPaint); - canvas.drawLine(frameRect.left, frameRect.top, frameRect.left + borderMargin, frameRect.top, borderPaint); - canvas.drawLine(frameRect.left, frameRect.bottom, frameRect.left, frameRect.bottom - borderMargin, borderPaint); - canvas.drawLine(frameRect.left, frameRect.bottom, frameRect.left + borderMargin, frameRect.bottom, borderPaint); - canvas.drawLine(frameRect.right, frameRect.top, frameRect.right - borderMargin, frameRect.top, borderPaint); - canvas.drawLine(frameRect.right, frameRect.top, frameRect.right, frameRect.top + borderMargin, borderPaint); - canvas.drawLine(frameRect.right, frameRect.bottom, frameRect.right, frameRect.bottom - borderMargin, borderPaint); - canvas.drawLine(frameRect.right, frameRect.bottom, frameRect.right - borderMargin, frameRect.bottom, borderPaint); - } - - private void drawLaser(Canvas canvas, long timeElapsed) { - if (laserY > frameRect.bottom || laserY < frameRect.top) laserY = frameRect.top; - canvas.drawLine(frameRect.left + STROKE_WIDTH, laserY, frameRect.right - STROKE_WIDTH, laserY, laserPaint); - laserY += (timeElapsed) / ANIMATION_SPEED; - } - - public Rect getFrameRect() { - return frameRect; - } - - public void setFrameColor(@ColorInt int borderColor) { - borderPaint.setColor(borderColor); - } - - public void setLaserColor(@ColorInt int laserColor) { - laserPaint.setColor(laserColor); - } -} diff --git a/android/src/main/java/com/rncamerakit/camera/barcode/BarcodeScanner.java b/android/src/main/java/com/rncamerakit/camera/barcode/BarcodeScanner.java deleted file mode 100644 index 21d62ec..0000000 --- a/android/src/main/java/com/rncamerakit/camera/barcode/BarcodeScanner.java +++ /dev/null @@ -1,145 +0,0 @@ -package com.rncamerakit.camera.barcode; - - -import android.graphics.Rect; -import android.hardware.Camera; -import android.os.Handler; -import android.os.Looper; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.util.Log; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.BinaryBitmap; -import com.google.zxing.DecodeHintType; -import com.google.zxing.LuminanceSource; -import com.google.zxing.MultiFormatReader; -import com.google.zxing.ReaderException; -import com.google.zxing.Result; -import com.google.zxing.common.HybridBinarizer; -import com.rncamerakit.camera.CameraViewManager; - -import java.util.ArrayList; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; - -public class BarcodeScanner { - - public interface ResultHandler { - void handleResult(Result result); - } - - private MultiFormatReader mMultiFormatReader; - private static final List ALL_FORMATS = new ArrayList<>(); - private ResultHandler resultHandler; - - private Camera.PreviewCallback previewCallback; - - static { - ALL_FORMATS.add(BarcodeFormat.AZTEC); - ALL_FORMATS.add(BarcodeFormat.CODABAR); - ALL_FORMATS.add(BarcodeFormat.CODE_39); - ALL_FORMATS.add(BarcodeFormat.CODE_93); - ALL_FORMATS.add(BarcodeFormat.CODE_128); - ALL_FORMATS.add(BarcodeFormat.DATA_MATRIX); - ALL_FORMATS.add(BarcodeFormat.EAN_8); - ALL_FORMATS.add(BarcodeFormat.EAN_13); - ALL_FORMATS.add(BarcodeFormat.ITF); - ALL_FORMATS.add(BarcodeFormat.MAXICODE); - ALL_FORMATS.add(BarcodeFormat.PDF_417); - ALL_FORMATS.add(BarcodeFormat.QR_CODE); - ALL_FORMATS.add(BarcodeFormat.RSS_14); - ALL_FORMATS.add(BarcodeFormat.RSS_EXPANDED); - ALL_FORMATS.add(BarcodeFormat.UPC_A); - ALL_FORMATS.add(BarcodeFormat.UPC_E); - ALL_FORMATS.add(BarcodeFormat.UPC_EAN_EXTENSION); - } - - public BarcodeScanner(@NonNull Camera.PreviewCallback previewCallback, @NonNull ResultHandler resultHandler) { - Map hints = new EnumMap<>(DecodeHintType.class); - hints.put(DecodeHintType.POSSIBLE_FORMATS, ALL_FORMATS); - mMultiFormatReader = new MultiFormatReader(); - mMultiFormatReader.setHints(hints); - - this.previewCallback = previewCallback; - this.resultHandler = resultHandler; - } - - public void onPreviewFrame(byte[] data, final Camera camera) { - try { - Camera.Size size = camera.getParameters().getPreviewSize(); - int width = size.width; - int height = size.height; - - int tmp = width; - width = height; - height = tmp; - data = getRotatedData(data, camera); - - final Result result = decodeResult(getLuminanceSource(data, width, height)); - - if (result != null) { - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - resultHandler.handleResult(result); - } - }); - } - camera.setOneShotPreviewCallback(previewCallback); - } catch (RuntimeException e) { - Log.w("CameraKit", e.toString()); - } - } - - @Nullable - private Result decodeResult(LuminanceSource source) { - Result rawResult = null; - if (source != null) { - BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); - try { - rawResult = mMultiFormatReader.decodeWithState(bitmap); - } catch (ReaderException ignored) { - } finally { - mMultiFormatReader.reset(); - } - - if (rawResult == null && source.isRotateSupported()) { - LuminanceSource rotatedSource = source.rotateCounterClockwise(); - bitmap = new BinaryBitmap(new HybridBinarizer(rotatedSource)); - try { - rawResult = mMultiFormatReader.decodeWithState(bitmap); - } catch (ReaderException ignored) { - } finally { - mMultiFormatReader.reset(); - } - } - } - return rawResult; - } - - private LuminanceSource getLuminanceSource(byte[] data, int width, int height) { - Rect rect = CameraViewManager.getFramingRectInPreview(width, height); - try { - return new RotateLuminanceSource(data, width, height, rect.left, rect.top, - rect.width(), rect.height(), false); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - private byte[] getRotatedData(byte[] data, Camera camera) { - Camera.Size size = camera.getParameters().getPreviewSize(); - int width = size.width; - int height = size.height; - - byte[] rotatedData = new byte[data.length]; - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) - rotatedData[x * height + height - y - 1] = data[x + y * width]; - } - return rotatedData; - } -} diff --git a/android/src/main/java/com/rncamerakit/camera/barcode/RotateLuminanceSource.java b/android/src/main/java/com/rncamerakit/camera/barcode/RotateLuminanceSource.java deleted file mode 100644 index 84b880c..0000000 --- a/android/src/main/java/com/rncamerakit/camera/barcode/RotateLuminanceSource.java +++ /dev/null @@ -1,164 +0,0 @@ -package com.rncamerakit.camera.barcode; - -import com.google.zxing.LuminanceSource; - -/** - * This class is mostly copy of {@link com.google.zxing.PlanarYUVLuminanceSource} class. - * The only difference is adding {@code rotateCounterClockwise()} method - * It was copied due to PlanarYUVLuminanceSource being final, so it can not be extended - * Zxing license - {https://github.com/zxing/zxing/blob/master/LICENSE} - */ -public class RotateLuminanceSource extends LuminanceSource { - - private static final int THUMBNAIL_SCALE_FACTOR = 2; - - private final byte[] yuvData; - private final int dataWidth; - private final int dataHeight; - private final int left; - private final int top; - - RotateLuminanceSource(byte[] yuvData, - int dataWidth, - int dataHeight, - int left, - int top, - int width, - int height, - boolean reverseHorizontal) { - super(width, height); - - if (left + width > dataWidth || top + height > dataHeight) { - throw new IllegalArgumentException("Crop rectangle does not fit within image data."); - } - - this.yuvData = yuvData; - this.dataWidth = dataWidth; - this.dataHeight = dataHeight; - this.left = left; - this.top = top; - if (reverseHorizontal) { - reverseHorizontal(width, height); - } - } - - @Override - public byte[] getRow(int y, byte[] row) { - if (y < 0 || y >= getHeight()) { - throw new IllegalArgumentException("Requested row is outside the image: " + y); - } - int width = getWidth(); - if (row == null || row.length < width) { - row = new byte[width]; - } - int offset = (y + top) * dataWidth + left; - System.arraycopy(yuvData, offset, row, 0, width); - return row; - } - - @Override - public byte[] getMatrix() { - int width = getWidth(); - int height = getHeight(); - - // If the caller asks for the entire underlying image, save the copy and give them the - // original data. The docs specifically warn that result.length must be ignored. - if (width == dataWidth && height == dataHeight) { - return yuvData; - } - - int area = width * height; - byte[] matrix = new byte[area]; - int inputOffset = top * dataWidth + left; - - // If the width matches the full width of the underlying data, perform a single copy. - if (width == dataWidth) { - System.arraycopy(yuvData, inputOffset, matrix, 0, area); - return matrix; - } - - // Otherwise copy one cropped row at a time. - for (int y = 0; y < height; y++) { - int outputOffset = y * width; - System.arraycopy(yuvData, inputOffset, matrix, outputOffset, width); - inputOffset += dataWidth; - } - return matrix; - } - - @Override - public boolean isCropSupported() { - return true; - } - - @Override - public LuminanceSource crop(int left, int top, int width, int height) { - return new RotateLuminanceSource(yuvData, - dataWidth, - dataHeight, - this.left + left, - this.top + top, - width, - height, - false); - } - - public int[] renderThumbnail() { - int width = getWidth() / THUMBNAIL_SCALE_FACTOR; - int height = getHeight() / THUMBNAIL_SCALE_FACTOR; - int[] pixels = new int[width * height]; - byte[] yuv = yuvData; - int inputOffset = top * dataWidth + left; - - for (int y = 0; y < height; y++) { - int outputOffset = y * width; - for (int x = 0; x < width; x++) { - int grey = yuv[inputOffset + x * THUMBNAIL_SCALE_FACTOR] & 0xff; - pixels[outputOffset + x] = 0xFF000000 | (grey * 0x00010101); - } - inputOffset += dataWidth * THUMBNAIL_SCALE_FACTOR; - } - return pixels; - } - - /** - * @return width of image from {@link #renderThumbnail()} - */ - public int getThumbnailWidth() { - return getWidth() / THUMBNAIL_SCALE_FACTOR; - } - - /** - * @return height of image from {@link #renderThumbnail()} - */ - public int getThumbnailHeight() { - return getHeight() / THUMBNAIL_SCALE_FACTOR; - } - - private void reverseHorizontal(int width, int height) { - byte[] yuvData = this.yuvData; - for (int y = 0, rowStart = top * dataWidth + left; y < height; y++, rowStart += dataWidth) { - int middle = rowStart + width / 2; - for (int x1 = rowStart, x2 = rowStart + width - 1; x1 < middle; x1++, x2--) { - byte temp = yuvData[x1]; - yuvData[x1] = yuvData[x2]; - yuvData[x2] = temp; - } - } - } - - @Override - public boolean isRotateSupported() { - return true; - } - - @Override - public LuminanceSource rotateCounterClockwise() { - byte[] rotatedData = new byte[yuvData.length]; - for (int y = 0; y < dataHeight; y++) { - for (int x = 0; x < dataWidth; x++) - rotatedData[x * dataHeight + dataHeight - y - 1] = yuvData[x + y * dataWidth]; - } - return new RotateLuminanceSource(rotatedData, dataHeight, dataWidth, top, (dataWidth - left - getWidth()), getHeight(), getWidth(), false); - } -} diff --git a/android/src/main/java/com/rncamerakit/camera/commands/Capture.java b/android/src/main/java/com/rncamerakit/camera/commands/Capture.java deleted file mode 100644 index e2098a8..0000000 --- a/android/src/main/java/com/rncamerakit/camera/commands/Capture.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.rncamerakit.camera.commands; - -import android.content.Context; -import android.hardware.Camera; - -import com.facebook.react.bridge.Promise; - -import com.rncamerakit.camera.CameraViewManager; -import com.rncamerakit.SaveImageTask; -import com.rncamerakit.camera.commands.Command; - -public class Capture implements Command { - - private final Context context; - private boolean saveToCameraRoll; - - public Capture(Context context, boolean saveToCameraRoll) { - this.context = context; - this.saveToCameraRoll = saveToCameraRoll; - } - - @Override - public void execute(final Promise promise) { - try { - tryTakePicture(promise); - } catch (Exception e) { - e.printStackTrace(); - } - } - - private void tryTakePicture(final Promise promise) throws Exception { - CameraViewManager.getCamera().takePicture(null, null, new Camera.PictureCallback() { - @Override - public void onPictureTaken(byte[] data, Camera camera) { - camera.stopPreview(); - new SaveImageTask(context, promise, saveToCameraRoll).execute(data); - } - }); - } -} diff --git a/android/src/main/java/com/rncamerakit/camera/commands/Command.java b/android/src/main/java/com/rncamerakit/camera/commands/Command.java deleted file mode 100644 index 06418a9..0000000 --- a/android/src/main/java/com/rncamerakit/camera/commands/Command.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.rncamerakit.camera.commands; - -import com.facebook.react.bridge.Promise; - -public interface Command { - void execute(Promise promise); -} diff --git a/android/src/main/java/com/rncamerakit/camera/permission/CameraPermission.java b/android/src/main/java/com/rncamerakit/camera/permission/CameraPermission.java deleted file mode 100644 index 5601ff2..0000000 --- a/android/src/main/java/com/rncamerakit/camera/permission/CameraPermission.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.rncamerakit.camera.permission; - -import android.Manifest; -import android.app.Activity; -import androidx.core.app.ActivityCompat; -import androidx.core.content.PermissionChecker; - -import com.facebook.react.bridge.Promise; -import com.rncamerakit.SharedPrefs; - -public class CameraPermission { - private static final int CAMERA_PERMISSION_REQUEST_CODE = 1002; - private static final int PERMISSION_GRANTED = 1; - private static final int PERMISSION_NOT_DETERMINED = -1; - private static final int PERMISSION_DENIED = 0; - - private Promise requestAccessPromise; - - public void requestAccess(Activity activity, Promise promise) { - if (isPermissionGranted(activity)) { - promise.resolve(true); - } - requestAccessPromise = promise; - permissionRequested(activity, Manifest.permission.CAMERA); - ActivityCompat.requestPermissions(activity, - new String[]{Manifest.permission.CAMERA}, - CAMERA_PERMISSION_REQUEST_CODE); - } - - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - if (isCameraPermission(requestCode, permissions)) { - if (requestAccessPromise != null) { - requestAccessPromise.resolve(grantResults[0] == PermissionChecker.PERMISSION_GRANTED); - requestAccessPromise = null; - } - } - } - - private boolean isCameraPermission(int requestCode, String[] permissions) { - if (permissions.length > 0) { - return requestCode == CAMERA_PERMISSION_REQUEST_CODE && - Manifest.permission.CAMERA.equals(permissions[0]); - } - return false; - } - - public int checkAuthorizationStatus(Activity activity) { - final int statusCode = PermissionChecker.checkCallingOrSelfPermission(activity, Manifest.permission.CAMERA); - if (statusCode == PermissionChecker.PERMISSION_GRANTED) { - return PERMISSION_GRANTED; - } - if (requestingPermissionForFirstTime(activity, Manifest.permission.CAMERA)) { - return PERMISSION_NOT_DETERMINED; - } - return PERMISSION_DENIED; - } - - private boolean requestingPermissionForFirstTime(Activity activity, String permissionName) { - return !SharedPrefs.getBoolean(activity, permissionName); - } - - private void permissionRequested(Activity activity, String permissionName) { - SharedPrefs.putBoolean(activity, permissionName, true); - } - - private boolean isPermissionGranted(Activity activity) { - return checkAuthorizationStatus(activity) == PermissionChecker.PERMISSION_GRANTED; - } -} diff --git a/android/src/main/java/com/rncamerakit/camera/permission/CameraPermissionRequestCallback.java b/android/src/main/java/com/rncamerakit/camera/permission/CameraPermissionRequestCallback.java deleted file mode 100644 index 4ec88ae..0000000 --- a/android/src/main/java/com/rncamerakit/camera/permission/CameraPermissionRequestCallback.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.rncamerakit.camera.permission; - -import com.rncamerakit.camera.CameraModule; - -public class CameraPermissionRequestCallback { - - private CameraModule cameraModule; - - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - cameraModule.onRequestPermissionsResult(requestCode, permissions, grantResults); - } - - public void setCameraModule(CameraModule cameraModule) { - this.cameraModule = cameraModule; - } -} diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 4d7317e..ffa05d6 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ + - - - this.setState({ example: BarcodeScreen })}> + this.setState({ example: CameraExample })}> - Barcode scanner Screen + Camera - - this.setState({ example: CameraScreen })}> + this.setState({ example: CameraScreenExample })}> Camera Screen - - this.onCheckCameraAuthoPressed()}> + this.setState({ example: BarcodeScreenExample })}> - Camera Permission Status + Barcode Scanner - - ); } - - async onCheckCameraAuthoPressed() { - const success = await CameraKitCamera.checkDeviceCameraAuthorizationStatus(); - if (success) { - Alert.alert('You have permission 🤗') - } - else { - Alert.alert('No permission 😳') - } - } } const styles = StyleSheet.create({ container: { flex: 1, - paddingTop: 60, + paddingTop: 30, alignItems: 'center', backgroundColor: '#F5FCFF', + marginHorizontal: 24, }, headerContainer: { flexDirection: 'column', backgroundColor: '#F5FCFF', justifyContent: 'center', alignItems: 'center', - paddingTop: 100 + paddingTop: 100, }, headerText: { color: 'black', - fontSize: 24 + fontSize: 24, + fontWeight: 'bold', + }, + button: { + height: 60, + borderRadius: 30, + marginVertical: 12, + width: '100%', + backgroundColor: '#dddddd', + justifyContent: 'center', }, buttonText: { - color: 'blue', - marginBottom: 20, - fontSize: 20 - } + textAlign: 'center', + fontSize: 20, + }, }); diff --git a/example/src/BarcodeScreen.js b/example/src/BarcodeScreenExample.tsx similarity index 89% rename from example/src/BarcodeScreen.js rename to example/src/BarcodeScreenExample.tsx index 9a5c382..b584a53 100644 --- a/example/src/BarcodeScreen.js +++ b/example/src/BarcodeScreenExample.tsx @@ -1,9 +1,9 @@ import React, { Component } from 'react'; import { Alert } from 'react-native'; -import CameraKitCameraScreen from '../../src/CameraScreen/CameraKitCameraScreen'; +import CameraScreen from '../../src/CameraScreen'; import CheckingScreen from './CheckingScreen'; -export default class CameraScreen extends Component { +export default class BarcodeScreenExample extends Component { constructor(props) { super(props); this.state = { @@ -27,7 +27,7 @@ export default class CameraScreen extends Component { return ; } return ( - this.onBottomButtonPressed(event)} flashImages={{ diff --git a/example/src/CameraExample.tsx b/example/src/CameraExample.tsx new file mode 100644 index 0000000..4a49fa8 --- /dev/null +++ b/example/src/CameraExample.tsx @@ -0,0 +1,44 @@ +import React, { Component } from 'react'; +import { Alert, View, Text, SafeAreaView, StyleSheet } from 'react-native'; +import Camera from '../../src/Camera'; +import { CameraType } from '../../src/CameraScreen/CameraScreenBase'; + +export default class CameraExample extends Component { + render() { + return ( + + (this.camera = ref)} + type={CameraType.Back} // optional + style={{ flex: 1 }} + flashMode="auto" + cameraOptions={{ + flashMode: 'auto', // on/off/auto(default) + focusMode: 'on', // off/on(default) + zoomMode: 'on', // off/on(default) + ratioOverlay: '1:1', // optional + ratioOverlayColor: '#00000077', // optional + }} + resetFocusTimeout={0} + resetFocusWhenMotionDetected={false} + saveToCameraRole={false} // iOS only + scanBarcode={false} // optional + showFrame={false} // Barcode only, optional + laserColor="red" // Barcode only, optional + frameColor="yellow" // Barcode only, optional + surfaceColor="blue" // Barcode only, optional + onReadCode={(event) => console.log(event.nativeEvent.codeStringValue)} + /> + + ); + } +} + +const styles = StyleSheet.create( + { + cameraContainer: { + flex: 1, + backgroundColor: 'black', + }, + }, +); diff --git a/example/src/CameraScreen.js b/example/src/CameraScreenExample.tsx similarity index 53% rename from example/src/CameraScreen.js rename to example/src/CameraScreenExample.tsx index 013f654..67511a3 100644 --- a/example/src/CameraScreen.js +++ b/example/src/CameraScreenExample.tsx @@ -1,8 +1,9 @@ import React, { Component } from 'react'; import { Alert } from 'react-native'; -import CameraKitCameraScreen from '../../src/CameraScreen/CameraKitCameraScreen'; +import CameraScreen from '../../src/CameraScreen'; +import { CameraOptions } from '../../src/CameraScreen/CameraScreenBase'; -export default class CameraScreen extends Component { +export default class CameraScreenExample extends Component { onBottomButtonPressed(event) { const captureImages = JSON.stringify(event.captureImages); Alert.alert( @@ -14,17 +15,19 @@ export default class CameraScreen extends Component { } render() { + const options: CameraOptions = { flashMode: 'auto', focusMode: 'on', zoomMode: 'on' }; return ( - this.onBottomButtonPressed(event)} + cameraOptions={options} flashImages={{ on: require('../images/flashOn.png'), - off: require('./../images/flashOff.png'), - auto: require('./../images/flashAuto.png'), + off: require('../images/flashOff.png'), + auto: require('../images/flashAuto.png'), }} - cameraFlipImage={require('./../images/cameraFlipIcon.png')} - captureButtonImage={require('./../images/cameraButton.png')} + cameraFlipImage={require('../images/cameraFlipIcon.png')} + captureButtonImage={require('../images/cameraButton.png')} /> ); } diff --git a/example/src/CheckingScreen.js b/example/src/CheckingScreen.js deleted file mode 100644 index bbe3a0f..0000000 --- a/example/src/CheckingScreen.js +++ /dev/null @@ -1,53 +0,0 @@ -import React, { Component } from 'react'; -import { - View, - TouchableOpacity, - Text, - StyleSheet -} from 'react-native'; -import BarcodeScreen from './BarcodeScreen'; - -export default class ExampleScreen extends Component { - - constructor(props) { - super(props); - this.state = { - example: undefined - }; - } - - render() { - if (this.state.example) { - const ExampleScreen = this.state.example; - return ; - } - return ( - - this.setState({example : BarcodeScreen}))}> - - Back button - - - - ); - } -} - - -const styles = StyleSheet.create({ - container: { - flex: 1, - // justifyContent: 'center', - paddingTop: 60, - alignItems: 'center', - backgroundColor: '#F5FCFF', - }, - buttonText: { - color: 'blue', - marginBottom: 20, - fontSize: 20 - } - }); - - - diff --git a/example/src/CheckingScreen.tsx b/example/src/CheckingScreen.tsx new file mode 100644 index 0000000..4f31fb9 --- /dev/null +++ b/example/src/CheckingScreen.tsx @@ -0,0 +1,40 @@ +import React, { Component } from 'react'; +import { View, TouchableOpacity, Text, StyleSheet } from 'react-native'; +import BarcodeScreen from './BarcodeScreenExample'; + +export default class CheckingScreen extends Component { + constructor(props) { + super(props); + this.state = { + example: undefined, + }; + } + + render() { + if (this.state.example) { + const CheckingScreen = this.state.example; + return ; + } + return ( + + this.setState({ example: BarcodeScreen })}> + Back button + + + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + paddingTop: 60, + alignItems: 'center', + backgroundColor: '#F5FCFF', + }, + buttonText: { + color: 'blue', + marginBottom: 20, + fontSize: 20, + }, +}); diff --git a/package.json b/package.json index 563520f..527aaeb 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "devDependencies": { "@react-native-community/eslint-config": "^2.0.0", "@types/jest": "^26.0.0", + "@types/lodash": "^4.14.165", "@types/react": "^16.9.19", "@types/react-native": "0.62.13", "babel-jest": "22.4.1", @@ -38,7 +39,8 @@ "prettier": "^2.1.2", "react": "16.11.0", "react-native": "0.62.2", - "react-test-renderer": "16.6.0" + "react-test-renderer": "16.6.0", + "typescript": "^4.1.2" }, "peerDependencies": { "react": "*", diff --git a/src/Camera.android.tsx b/src/Camera.android.tsx new file mode 100644 index 0000000..fedc009 --- /dev/null +++ b/src/Camera.android.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import _ from 'lodash'; +import { requireNativeComponent, findNodeHandle, NativeModules, processColor } from 'react-native'; + +const { RNCameraKitModule } = NativeModules; +const NativeCamera = requireNativeComponent('CKCameraManager'); + +function Camera(props, ref) { + const nativeRef = React.useRef(); + + React.useImperativeHandle(ref, () => ({ + capture: async (options = {}) => { + // Because RN doesn't support return types for ViewManager methods + // we must use the general module and tell it what View it's supposed to be using + return await RNCameraKitModule.capture(options, findNodeHandle(nativeRef.current)); + }, + requestDeviceCameraAuthorization: async () => { + return await RNCameraKitModule.requestDeviceCameraAuthorization(); + }, + })); + + const transformedProps = _.cloneDeep(props); + _.update(transformedProps, 'cameraOptions.ratioOverlayColor', (c) => processColor(c)); + _.update(transformedProps, 'frameColor', (c) => processColor(c)); + _.update(transformedProps, 'laserColor', (c) => processColor(c)); + _.update(transformedProps, 'surfaceColor', (c) => processColor(c)); + + return ; +} + +const { PORTRAIT, PORTRAIT_UPSIDE_DOWN, LANDSCAPE_LEFT, LANDSCAPE_RIGHT } = RNCameraKitModule.getConstants(); + +export { PORTRAIT, PORTRAIT_UPSIDE_DOWN, LANDSCAPE_LEFT, LANDSCAPE_RIGHT }; + +export default React.forwardRef(Camera); diff --git a/src/Camera.ios.tsx b/src/Camera.ios.tsx new file mode 100644 index 0000000..d8ebed2 --- /dev/null +++ b/src/Camera.ios.tsx @@ -0,0 +1,52 @@ +import * as _ from 'lodash'; +import React, { useEffect } from 'react'; +import { requireNativeComponent, NativeModules, processColor } from 'react-native'; + +const { CKCameraManager } = NativeModules; +const NativeCamera = requireNativeComponent('CKCamera'); + +function Camera(props, ref) { + const nativeRef = React.useRef(); + + useEffect(() => { + CKCameraManager.changeCamera(); + }, [props.type]); + + useEffect(() => { + CKCameraManager.setFlashMode(props.flashMode); + }, [props.flashMode]); + + React.useImperativeHandle(ref, () => ({ + capture: async () => { + return await CKCameraManager.capture({}); + }, + requestDeviceCameraAuthorization: async () => { + return await CKCameraManager.checkDeviceCameraAuthorizationStatus(); + }, + checkDeviceCameraAuthorizationStatus: async () => { + return await CKCameraManager.checkDeviceCameraAuthorizationStatus(); + }, + changeCamera: async () => { + return await CKCameraManager.changeCamera(); + }, + setFlashMode: async (flashMode = 'auto') => { + return await CKCameraManager.setFlashMode(flashMode); + }, + setTorchMode: async (torchMode = '') => { + return await CKCameraManager.setTorchMode(torchMode); + }, + })); + + const transformedProps = _.cloneDeep(props); + _.update(transformedProps, 'cameraOptions.ratioOverlayColor', (c) => processColor(c)); + + return ; +} + +Camera.defaultProps = { + resetFocusTimeout: 0, + resetFocusWhenMotionDetected: true, + saveToCameraRoll: true, +}; + +export default React.forwardRef(Camera); diff --git a/src/CameraKitCamera.android.js b/src/CameraKitCamera.android.js deleted file mode 100644 index d10cc5e..0000000 --- a/src/CameraKitCamera.android.js +++ /dev/null @@ -1,61 +0,0 @@ -import * as _ from 'lodash'; -import React, { Component } from 'react'; -import { - requireNativeComponent, - NativeModules, - processColor, -} from 'react-native'; - -const NativeCamera = requireNativeComponent('CameraView', null); -const NativeCameraModule = NativeModules.RNKitCameraModule; -const TORCH_MODE_ON = 'on'; -const TORCH_MODE_CALL_ARG = 'torch'; - -export default class CameraKitCamera extends React.Component { - async logData() { - console.log('front Camera?', await NativeCameraModule.hasFrontCamera()); - console.log('hasFlash?', await NativeCameraModule.hasFlashForCurrentCamera()); - console.log('flashMode?', await NativeCameraModule.getFlashMode()); - } - - static async requestDeviceCameraAuthorization() { - return await NativeCameraModule.requestDeviceCameraAuthorization(); - } - - async capture(saveToCameraRoll = true) { - return await NativeCameraModule.capture(saveToCameraRoll); - } - - async changeCamera() { - return await NativeCameraModule.changeCamera(); - } - - async setTorchMode(torchMode) { - if (torchMode == TORCH_MODE_ON){ - return await NativeCameraModule.setFlashMode(TORCH_MODE_CALL_ARG); - } - return await NativeCameraModule.setFlashMode(torchMode); - } - - async setFlashMode(flashMode = 'auto') { - return await NativeCameraModule.setFlashMode(flashMode); - } - - static async checkDeviceCameraAuthorizationStatus() { - return await NativeCameraModule.checkDeviceCameraAuthorizationStatus(); - } - - static async hasCameraPermission() { - return await NativeCameraModule.hasCameraPermission(); - } - - render() { - const transformedProps = _.cloneDeep(this.props); - _.update(transformedProps, 'cameraOptions.ratioOverlayColor', (c) => processColor(c)); - _.update(transformedProps, 'frameColor', (c) => processColor(c)); - _.update(transformedProps, 'laserColor', (c) => processColor(c)); - _.update(transformedProps, 'surfaceColor', (c) => processColor(c)); - - return ; - } -} diff --git a/src/CameraKitCamera.ios.js b/src/CameraKitCamera.ios.js deleted file mode 100644 index 69b59f3..0000000 --- a/src/CameraKitCamera.ios.js +++ /dev/null @@ -1,57 +0,0 @@ -import * as _ from 'lodash'; -import React from 'react'; -import { - requireNativeComponent, - NativeModules, - processColor, - Platform, -} from 'react-native'; - -const NativeCamera = requireNativeComponent('CKCamera', null); -const NativeCameraAction = NativeModules.CKCameraManager; - -export default class CameraKitCamera extends React.Component { - static async checkDeviceCameraAuthorizationStatus() { - return await NativeCameraAction.checkDeviceCameraAuthorizationStatus(); - } - - static async requestDeviceCameraAuthorization() { - return await NativeCameraAction.requestDeviceCameraAuthorization(); - } - - capture(options) { - if (Platform.OS === 'ios') { - return NativeCameraAction.capture(options); - } - if (Platform.OS === 'android') { - // Android has not been updated to use props yet - return NativeCameraAction.capture(!!this.props.saveToCameraRoll); - } - } - - async changeCamera() { - return await NativeCameraAction.changeCamera(); - } - - async setFlashMode(flashMode = 'auto') { - return await NativeCameraAction.setFlashMode(flashMode); - } - - async setTorchMode(torchMode = '') { - return await NativeCameraAction.setTorchMode(torchMode); - } - - render() { - const transformedProps = _.cloneDeep(this.props); - _.update(transformedProps, 'cameraOptions.ratioOverlayColor', (c) => processColor(c)); - return ( - - ); - } -} - -CameraKitCamera.defaultProps = { - resetFocusTimeout: 0, - resetFocusWhenMotionDetected: true, - saveToCameraRoll: true, -}; diff --git a/src/CameraKitGallery.android.js b/src/CameraKitGallery.android.js deleted file mode 100644 index 3379cba..0000000 --- a/src/CameraKitGallery.android.js +++ /dev/null @@ -1,63 +0,0 @@ -import {NativeModules} from 'react-native'; -const NativeGalleryModule = NativeModules.NativeGalleryModule; - -async function getAlbumsWithThumbnails() { - return await NativeGalleryModule.getAlbumsWithThumbnails(); -} - -async function getImageUriForId(imageId) { - // Return what getImagesForIds() typically returns in the 'uri' field. - return `file://${imageId}`; -} - -async function getImagesForIds(imagesUris = []) { - return await NativeGalleryModule.getImagesForUris(imagesUris); -} - -async function getImageForTapEvent(nativeEvent) { - const selectedImageId = nativeEvent.selected; - const imageUri = selectedImageId && await getImageUriForId(selectedImageId); - return {selectedImageId, imageUri, width: nativeEvent.width, height: nativeEvent.height}; -} - -async function getImagesForCameraEvent(event) { - if (!event.captureImages) { - return []; - } - - const images = []; - event.captureImages.forEach(async (image) => { - images.push({ - ...image, - uri: await getImageUriForId(image.uri) - }); - }); - return images; -} - -async function checkDevicePhotosAuthorizationStatus() { - return await NativeGalleryModule.checkDeviceStorageAuthorizationStatus(); -} - -async function requestDevicePhotosAuthorization() { - return await NativeGalleryModule.requestDeviceStorageAuthorization(); -} - -async function resizeImage(image = {}, quality = 'original') { - if (quality === 'original') { - return images; - } - return await NativeGalleryModule.resizeImage(image, quality); -} - - -export default { - checkDevicePhotosAuthorizationStatus, - requestDevicePhotosAuthorization, - getAlbumsWithThumbnails, - getImageUriForId, - getImagesForIds, - getImageForTapEvent, - getImagesForCameraEvent, - resizeImage -} diff --git a/src/CameraKitGallery.ios.js b/src/CameraKitGallery.ios.js deleted file mode 100644 index 3554a93..0000000 --- a/src/CameraKitGallery.ios.js +++ /dev/null @@ -1,69 +0,0 @@ -import { - NativeModules, -} from 'react-native'; - -var CKGallery = NativeModules.CKGalleryManager; - -async function getAlbumsWithThumbnails() { - return await CKGallery.getAlbumsWithThumbnails(); -} - -async function getImageUriForId(imageId, imageQuality) { - const {images} = await CKGallery.getImagesForIds([imageId], imageQuality); - if (!images) { - return; - } - if (images.length === 1) { - return images[0].uri; - } - return; -} - -async function getImagesForIds(imagesId = [], imageQuality) { - return await CKGallery.getImagesForIds(imagesId, imageQuality); -} - -async function getImageForTapEvent(nativeEvent) { - let selectedImageId; - let imageUri; - if (nativeEvent.selectedId) { - selectedImageId = nativeEvent.selectedId; - imageUri = nativeEvent.selected; - } else { - selectedImageId = nativeEvent.selected; - imageUri = await getImageUriForId(selectedImageId); - } - return {selectedImageId, imageUri, width: nativeEvent.width, height: nativeEvent.height}; -} - -async function getImagesForCameraEvent(event) { - return event.captureImages || []; -} - -async function resizeImage(image = {}, quality = 'original') { - if (quality === 'original') { - return images; - } - - return await CKGallery.resizeImage(image, quality); -} - -async function checkDevicePhotosAuthorizationStatus() { - return await CKGallery.checkDevicePhotosAuthorizationStatus(); -} - -async function requestDevicePhotosAuthorization() { - return await CKGallery.requestDevicePhotosAuthorization(); -} - - -export default { - getAlbumsWithThumbnails, - getImageUriForId, - getImagesForIds, - getImageForTapEvent, - getImagesForCameraEvent, - checkDevicePhotosAuthorizationStatus, - requestDevicePhotosAuthorization, - resizeImage -} diff --git a/src/CameraKitGalleryView.android.js b/src/CameraKitGalleryView.android.js deleted file mode 100644 index d931ade..0000000 --- a/src/CameraKitGalleryView.android.js +++ /dev/null @@ -1,100 +0,0 @@ -import _ from 'lodash'; -import React, {Component} from 'react'; -import ReactNative, { - requireNativeComponent, - UIManager, - processColor -} from 'react-native'; - -const resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource'); -const GalleryView = requireNativeComponent('GalleryView', null); -const ALL_PHOTOS = 'All Photos'; -const COMMAND_REFRESH_GALLERY = 1; - -export default class CameraKitGalleryView extends Component { - - constructor(props) { - super(props); - this.onTapImage = this.onTapImage.bind(this); - } - - async refreshGalleryView(lastEditedImage = '') { - UIManager.dispatchViewManagerCommand( - ReactNative.findNodeHandle(this), - COMMAND_REFRESH_GALLERY, - [lastEditedImage] - ); - return true; - } - - modifyGalleryViewContentOffset (offset) { - //do nothing. compatability with ios - } - - render() { - const transformedProps = _.cloneDeep(this.props); - transformedProps.albumName = this.props.albumName ? this.props.albumName : ALL_PHOTOS; - if (transformedProps.fileTypeSupport && transformedProps.fileTypeSupport.unsupportedImage) { - _.update(transformedProps, 'fileTypeSupport.unsupportedImage', (image) => resolveAssetSource(image).uri); - } - - if (_.get(transformedProps, 'customButtonStyle.image')) { - _.update(transformedProps, 'customButtonStyle.image', (image) => resolveAssetSource(image).uri); - } - - const customButtonBkgColor = _.get(transformedProps, 'customButtonStyle.backgroundColor'); - if (customButtonBkgColor) { - _.update(transformedProps, 'customButtonStyle.backgroundColor', (color) => processColor(customButtonBkgColor)); - } - - const selectedImageDeprecated = transformedProps.selectedImageIcon; - if (selectedImageDeprecated && _.isNumber(selectedImageDeprecated)) { - transformedProps.selectedImageIcon = resolveAssetSource(selectedImageDeprecated).uri; - } - - const unselectedImageDeprecated = transformedProps.unSelectedImageIcon; - if (unselectedImageDeprecated && _.isNumber(unselectedImageDeprecated)) { - transformedProps.unSelectedImageIcon = resolveAssetSource(unselectedImageDeprecated).uri; - } - - const selectedImage = _.get(transformedProps, 'selection.selectedImage'); - if (selectedImage && _.isNumber(selectedImage)) { - _.update(transformedProps, 'selection.selectedImage', (image) => resolveAssetSource(image).uri); - } - - const unselectedImage = _.get(transformedProps, 'selection.unselectedImage'); - if (unselectedImage && _.isNumber(unselectedImage)) { - _.update(transformedProps, 'selection.unselectedImage', (image) => resolveAssetSource(image).uri); - } - - const selectionPosition = _.get(transformedProps, 'selection.imagePosition'); - if (selectionPosition) { - const positionCode = this.transformSelectedImagePosition(selectionPosition); - _.update(transformedProps, 'selection.imagePosition', (position) => positionCode); - } - - const selectionOverlayColor = _.get(transformedProps, 'selection.overlayColor'); - if (selectionOverlayColor) { - _.update(transformedProps, 'selection.overlayColor', (color) => processColor(selectionOverlayColor)); - } - - return - } - - onTapImage(event) { - if(this.props.onTapImage) { - this.props.onTapImage(event); - } - } - - transformSelectedImagePosition(position) { - switch (position) { - case 'top-right': return 0; - case 'top-left': return 1; - case 'bottom-right': return 2; - case 'bottom-left': return 3; - case 'center': return 4; - default: return null; - } - } -} diff --git a/src/CameraKitGalleryView.ios.js b/src/CameraKitGalleryView.ios.js deleted file mode 100644 index c3a643a..0000000 --- a/src/CameraKitGalleryView.ios.js +++ /dev/null @@ -1,60 +0,0 @@ -import _ from 'lodash'; -import React, {Component} from 'react'; - -import { - requireNativeComponent, - NativeModules, - processColor -} from 'react-native'; - -const GalleryView = requireNativeComponent('CKGalleryView', null); -const GalleryViewManager = NativeModules.CKGalleryViewManager; -const ALL_PHOTOS = 'All Photos'; -const DEFAULT_COLUMN_COUNT = 3; -const resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource'); - -export default class CameraKitGalleryView extends Component { - render() { - const transformedProps = _.cloneDeep(this.props); - transformedProps.albumName = this.props.albumName ? this.props.albumName : ALL_PHOTOS; - transformedProps.columnCount = this.props.columnCount && this.props.columnCount > 0 ? this.props.columnCount : DEFAULT_COLUMN_COUNT; - _.update(transformedProps, 'fileTypeSupport.unsupportedOverlayColor', (c) => processColor(c)); - _.update(transformedProps, 'fileTypeSupport.unsupportedTextColor', (c) => processColor(c)); - if (transformedProps.fileTypeSupport && transformedProps.fileTypeSupport.unsupportedImage) { - _.update(transformedProps, 'fileTypeSupport.unsupportedImage', (image) => resolveAssetSource(image)); - } - if (_.get(transformedProps, 'customButtonStyle.image')) { - _.update(transformedProps, 'customButtonStyle.image', (image) => resolveAssetSource(image)); - } - - if (_.get(transformedProps, 'customButtonStyle.backgroundColor')) { - _.update(transformedProps, 'customButtonStyle.backgroundColor', (c) => processColor(c)); - } - - if (_.get(transformedProps, 'selection.selectedImage')) { - _.update(transformedProps, 'selection.selectedImage', (image) => resolveAssetSource(image)); - } - - if (_.get(transformedProps, 'selection.unselectedImage')) { - _.update(transformedProps, 'selection.unselectedImage', (image) => resolveAssetSource(image)); - } - - if (_.get(transformedProps, 'selection.overlayColor')) { - _.update(transformedProps, 'selection.overlayColor', (c) => processColor(c)); - } - - return - } - - async getSelectedImages() { - return await GalleryViewManager.getSelectedImages(); - } - - async refreshGalleryView(selectedImages = []) { - return await GalleryViewManager.refreshGalleryView(selectedImages); - } - - modifyGalleryViewContentOffset (offset) { - GalleryViewManager.modifyGalleryViewContentOffset(offset); - } -} diff --git a/src/CameraScreen/CameraKitCameraScreen.android.js b/src/CameraScreen/CameraScreen.android.tsx similarity index 68% rename from src/CameraScreen/CameraKitCameraScreen.android.js rename to src/CameraScreen/CameraScreen.android.tsx index 82da147..88cdc9a 100644 --- a/src/CameraScreen/CameraKitCameraScreen.android.js +++ b/src/CameraScreen/CameraScreen.android.tsx @@ -1,12 +1,10 @@ import React from 'react'; -import {View} from 'react-native'; -import CameraScreenBase from './CameraKitCameraScreenBase'; +import { View } from 'react-native'; +import CameraScreenBase from './CameraScreenBase'; export default class CameraScreen extends CameraScreenBase { renderGap() { - return ( - - ); + return ; } render() { @@ -20,5 +18,3 @@ export default class CameraScreen extends CameraScreenBase { ); } } - - diff --git a/src/CameraScreen/CameraKitCameraScreen.ios.js b/src/CameraScreen/CameraScreen.ios.tsx similarity index 78% rename from src/CameraScreen/CameraKitCameraScreen.ios.js rename to src/CameraScreen/CameraScreen.ios.tsx index cdc8d50..9e1fd2b 100644 --- a/src/CameraScreen/CameraKitCameraScreen.ios.js +++ b/src/CameraScreen/CameraScreen.ios.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import {View} from 'react-native'; -import CameraScreenBase from './CameraKitCameraScreenBase'; +import { View } from 'react-native'; +import CameraScreenBase from './CameraScreenBase'; export default class CameraScreen extends CameraScreenBase { render() { diff --git a/src/CameraScreen/CameraKitCameraScreenBase.js b/src/CameraScreen/CameraScreenBase.tsx similarity index 53% rename from src/CameraScreen/CameraKitCameraScreenBase.js rename to src/CameraScreen/CameraScreenBase.tsx index 6e07dbe..38b9084 100644 --- a/src/CameraScreen/CameraKitCameraScreenBase.js +++ b/src/CameraScreen/CameraScreenBase.tsx @@ -12,10 +12,9 @@ import { processColor, } from 'react-native'; import _ from 'lodash'; -import CameraKitCamera from './../CameraKitCamera'; +import Camera from '../Camera'; -const IsIOS = Platform.OS === 'ios'; -const GalleryManager = IsIOS ? NativeModules.CKGalleryManager : NativeModules.NativeGalleryModule; +const GalleryManager = Platform.OS === 'ios' ? NativeModules.CKGalleryManager : NativeModules.NativeGalleryModule; const FLASH_MODE_AUTO = 'auto'; const FLASH_MODE_ON = 'on'; @@ -26,8 +25,53 @@ const OVERLAY_DEFAULT_COLOR = '#ffffff77'; const OFFSET_FRAME = 30; const FRAME_HEIGHT = 200; -export default class CameraScreenBase extends Component { +export enum CameraType { + Front = 'front', + Back = 'back' +} +export type CameraOptions = { + flashMode: 'on' | 'off' | 'auto' + focusMode: 'on' | 'off' + zoomMode: 'on' + ratioOverlay?: string + ratioOverlayColor?: string +} + +export type Props = { + allowCaptureRetake: boolean, + cameraRatioOverlay: any, + cameraOptions: CameraOptions + scannerOptions: any, + offsetForScannerFrame: any, + heightForScannerFrame: any, + colorForScannerFrame: any, + cameraFlipImage: any, + hideControls: any, + showFrame: any, + captureButtonImage: any, + scanBarcode: any, + laserColor: any, + frameColor: any, + surfaceColor: any, + onReadCode: () => void; + onBottomButtonPressed: (any) => void; +} + +type State = { + captureImages: any[], + flashData: any, + torchData: boolean, + ratios: any[], + cameraOptions: any, + ratioArrayPosition: number, + imageCaptured: any, + captured: boolean, + scannerOptions: any, + type: CameraType, +} + +export default class CameraScreenBase extends Component { static propTypes = { allowCaptureRetake: PropTypes.bool, }; @@ -36,33 +80,41 @@ export default class CameraScreenBase extends Component { allowCaptureRetake: false, }; + currentFlashArrayPosition: number; + flashArray: any[]; + camera: any; + constructor(props) { super(props); this.currentFlashArrayPosition = 0; - this.flashArray = [{ - mode: FLASH_MODE_AUTO, - image: _.get(this.props, 'flashImages.auto'), - }, - { - mode: FLASH_MODE_ON, - image: _.get(this.props, 'flashImages.on'), - }, - { - mode: FLASH_MODE_OFF, - image: _.get(this.props, 'flashImages.off'), - }, + this.flashArray = [ + { + mode: FLASH_MODE_AUTO, + image: _.get(this.props, 'flashImages.auto'), + }, + { + mode: FLASH_MODE_ON, + image: _.get(this.props, 'flashImages.on'), + }, + { + mode: FLASH_MODE_OFF, + image: _.get(this.props, 'flashImages.off'), + }, ]; + this.state = { captureImages: [], flashData: this.flashArray[this.currentFlashArrayPosition], torchData: false, ratios: [], - cameraOptions: {}, + cameraOptions: this.props.cameraOptions, ratioArrayPosition: -1, - imageCaptured: undefined, + imageCaptured: false, captured: false, scannerOptions: {}, + type: CameraType.Back, }; + this.onSetFlash = this.onSetFlash.bind(this); this.onSetTorch = this.onSetTorch.bind(this); this.onSwitchCameraPressed = this.onSwitchCameraPressed.bind(this); @@ -78,8 +130,8 @@ export default class CameraScreenBase extends Component { this.setState({ cameraOptions, scannerOptions, - ratios: (ratios || []), - ratioArrayPosition: ((ratios.length > 0) ? 0 : -1), + ratios: ratios || [], + ratioArrayPosition: ratios.length > 0 ? 0 : -1, }); } @@ -118,48 +170,56 @@ export default class CameraScreenBase extends Component { } renderFlashButton() { - return !this.isCaptureRetakeMode() && - this.onSetFlash(FLASH_MODE_AUTO)}> - - ; + return ( + !this.isCaptureRetakeMode() && ( + this.onSetFlash()}> + + + ) + ); } renderSwitchCameraButton() { - return (this.props.cameraFlipImage && !this.isCaptureRetakeMode()) && - - - ; + return ( + this.props.cameraFlipImage && + !this.isCaptureRetakeMode() && ( + + + + ) + ); } renderTopButtons() { - return !this.props.hideControls && ( - - {this.renderFlashButton()} - {this.renderSwitchCameraButton()} - + return ( + !this.props.hideControls && ( + + {this.renderFlashButton()} + {this.renderSwitchCameraButton()} + + ) ); } renderCamera() { return ( - { - this.isCaptureRetakeMode() ? - : - this.camera = cam} + {this.isCaptureRetakeMode() ? ( + + ) : ( + (this.camera = cam)} + type={this.state.type} style={{ flex: 1, justifyContent: 'flex-end' }} + flashMode={this.state.flashData.mode} cameraOptions={this.state.cameraOptions} saveToCameraRoll={!this.props.allowCaptureRetake} showFrame={this.props.showFrame} @@ -167,10 +227,10 @@ export default class CameraScreenBase extends Component { laserColor={this.props.laserColor} frameColor={this.props.frameColor} surfaceColor={this.props.surfaceColor} - onReadCode = {this.props.onReadCode} - scannerOptions = {this.state.scannerOptions} + onReadCode={this.props.onReadCode} + scannerOptions={this.state.scannerOptions} /> - } + )} ); } @@ -187,23 +247,19 @@ export default class CameraScreenBase extends Component { } renderCaptureButton() { - return (this.props.captureButtonImage && !this.isCaptureRetakeMode()) && - - this.onCaptureImagePressed()} - > - - - - {this.numberOfImagesTaken()} - - - - - ; + return ( + this.props.captureButtonImage && + !this.isCaptureRetakeMode() && ( + + this.onCaptureImagePressed()}> + + + {this.numberOfImagesTaken()} + + + + ) + ); } renderRatioStrip() { @@ -239,7 +295,7 @@ export default class CameraScreenBase extends Component { this.setState({ imageCaptured: undefined }); } } else { - this.sendBottomButtonPressedAction(type, captureRetakeMode); + this.sendBottomButtonPressedAction(type, captureRetakeMode, null); } } @@ -257,30 +313,31 @@ export default class CameraScreenBase extends Component { ); } else { - return ( - - ); + return ; } } renderBottomButtons() { - return !this.props.hideControls && ( - - {this.renderBottomButton('left')} - {this.renderCaptureButton()} - + return ( + !this.props.hideControls && ( + + {/*this.renderBottomButton('left')*/} + {this.renderCaptureButton()} + + ) ); } onSwitchCameraPressed() { - this.camera.changeCamera(); + const direction = this.state.type === CameraType.Back ? CameraType.Front : CameraType.Back; + console.log('SWITCH: ' + direction); + this.setState({ type: direction }); } async onSetFlash() { this.currentFlashArrayPosition = (this.currentFlashArrayPosition + 1) % 3; const newFlashData = this.flashArray[this.currentFlashArrayPosition]; this.setState({ flashData: newFlashData }); - this.camera.setFlashMode(newFlashData.mode); } onSetTorch() { @@ -296,73 +353,83 @@ export default class CameraScreenBase extends Component { this.setState({ imageCaptured: image }); } else { if (image) { - this.setState({ captured: true, imageCaptured: image, captureImages: _.concat(this.state.captureImages, image) }); + this.setState({ + captured: true, + imageCaptured: image, + captureImages: _.concat(this.state.captureImages, image), + }); } this.sendBottomButtonPressedAction('capture', false, image); } } onRatioButtonPressed() { - const newRatiosArrayPosition = ((this.state.ratioArrayPosition + 1) % this.state.ratios.length); - const newCameraOptions = _.update(this.state.cameraOptions, 'ratioOverlay', (val) => this.state.ratios[newRatiosArrayPosition]); + const newRatiosArrayPosition = (this.state.ratioArrayPosition + 1) % this.state.ratios.length; + const newCameraOptions = _.update( + this.state.cameraOptions, + 'ratioOverlay', + (val) => this.state.ratios[newRatiosArrayPosition], + ); this.setState({ ratioArrayPosition: newRatiosArrayPosition, cameraOptions: newCameraOptions }); } render() { - throw ('Implemented in CameraKitCameraScreen!'); + throw 'Implemented in CameraScreen!'; } } -import styleObject from './CameraKitCameraScreenStyleObject'; -const styles = StyleSheet.create(_.merge(styleObject, { - textStyle: { - color: 'white', - fontSize: 20, - }, - ratioBestText: { - color: 'white', - fontSize: 18, - }, - ratioText: { - color: '#ffc233', - fontSize: 18, - }, - topButtons: { - flex: 1, - flexDirection: 'row', - justifyContent: 'space-between', - paddingTop: 8, - paddingBottom: 0, - }, - cameraContainer: { - flex: 10, - flexDirection: 'column', - }, - captureButtonContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - textNumberContainer: { - position: 'absolute', - top: 0, - left: 0, - bottom: 0, - right: 0, - justifyContent: 'center', - alignItems: 'center', - }, - bottomButton: { - flex: 1, - flexDirection: 'row', - alignItems: 'center', - padding: 10, - }, - bottomContainerGap: { - flex: 1, - flexDirection: 'row', - justifyContent: 'flex-end', - alignItems: 'center', - padding: 10, - }, -})); +import styleObject from './CameraScreenStyleObject'; +const styles = StyleSheet.create( + _.merge(styleObject, { + textStyle: { + color: 'white', + fontSize: 20, + }, + ratioBestText: { + color: 'white', + fontSize: 18, + }, + ratioText: { + color: '#ffc233', + fontSize: 18, + }, + topButtons: { + flex: 1, + flexDirection: 'row', + justifyContent: 'space-between', + paddingTop: 8, + paddingBottom: 0, + }, + cameraContainer: { + flex: 10, + flexDirection: 'column', + }, + captureButtonContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + textNumberContainer: { + position: 'absolute', + top: 0, + left: 0, + bottom: 0, + right: 0, + justifyContent: 'center', + alignItems: 'center', + }, + bottomButton: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + padding: 10, + }, + bottomContainerGap: { + flex: 1, + flexDirection: 'row', + justifyContent: 'flex-end', + alignItems: 'center', + padding: 10, + }, + }), +); diff --git a/src/CameraScreen/CameraKitCameraScreenStyleObject.android.js b/src/CameraScreen/CameraScreenStyleObject.android.ts similarity index 94% rename from src/CameraScreen/CameraKitCameraScreenStyleObject.android.js rename to src/CameraScreen/CameraScreenStyleObject.android.ts index 9a74d33..8acaf33 100644 --- a/src/CameraScreen/CameraKitCameraScreenStyleObject.android.js +++ b/src/CameraScreen/CameraScreenStyleObject.android.ts @@ -6,7 +6,8 @@ const styleObject = { position: 'absolute', top: 0, left: 0, - width, height, + width, + height, }, bottomButtons: { flex: 2, diff --git a/src/CameraScreen/CameraKitCameraScreenStyleObject.ios.js b/src/CameraScreen/CameraScreenStyleObject.ios.ts similarity index 100% rename from src/CameraScreen/CameraKitCameraScreenStyleObject.ios.js rename to src/CameraScreen/CameraScreenStyleObject.ios.ts diff --git a/src/CameraScreen/index.ts b/src/CameraScreen/index.ts new file mode 100644 index 0000000..615b1d6 --- /dev/null +++ b/src/CameraScreen/index.ts @@ -0,0 +1,6 @@ +import CameraScreen from './CameraScreen'; +import CameraScreenBase from './CameraScreenBase'; + +export default CameraScreen; + +export { CameraScreenBase }; diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 01af3b8..0000000 --- a/src/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import { NativeModules } from "react-native"; - -import CameraKitGallery from "./CameraKitGallery"; -import CameraKitCamera from "./CameraKitCamera"; -import CameraKitGalleryView from "./CameraKitGalleryView"; -import CameraKitCameraScreen from "./CameraScreen/CameraKitCameraScreen"; - -const { CameraKit } = NativeModules; - -export default CameraKit; - -export { - CameraKitGallery, - CameraKitCamera, - CameraKitGalleryView, - CameraKitCameraScreen, -}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..e66e3d3 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,10 @@ +import { NativeModules } from 'react-native'; + +import Camera from './Camera'; +import CameraScreen from './CameraScreen/CameraScreen'; + +const { CameraKit } = NativeModules; + +export default CameraKit; + +export { Camera, CameraScreen }; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f5751d3 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "allowJs": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "jsx": "react", + "lib": ["es6"], + "moduleResolution": "node", + "noEmit": true, + "strict": true, + "target": "esnext", + "resolveJsonModule": true, // Required for JSON files + "skipLibCheck": true, // Skips *.d.ts files + "strictFunctionTypes": false, + "noImplicitAny": false, + "outDir": "./lib", + "baseUrl": "app", + "paths": { + "*": ["*", "*.ios", "*.android"] + } + }, + "exclude": ["node_modules", "babel.config.js", "metro.config.js", "jest.config.js", "**/*.json", "**/*.spec.ts"] +} diff --git a/yarn.lock b/yarn.lock index 6f00986..e988e7d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1250,6 +1250,11 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/lodash@^4.14.165": + version "4.14.165" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.165.tgz#74d55d947452e2de0742bad65270433b63a8c30f" + integrity sha512-tjSSOTHhI5mCHTy/OOXYIhi2Wt1qcbHmuXD1Ha7q70CgI/I71afO4XtLb/cVexki1oVYchpul/TOuu3Arcdxrg== + "@types/node@*": version "14.14.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.2.tgz#d25295f9e4ca5989a2c610754dc02a9721235eeb" @@ -7370,6 +7375,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typescript@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.2.tgz#6369ef22516fe5e10304aae5a5c4862db55380e9" + integrity sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ== + ua-parser-js@^0.7.18: version "0.7.22" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.22.tgz#960df60a5f911ea8f1c818f3747b99c6e177eae3"