diff --git a/android/src/main/java/com/rncamerakit/CKCamera.kt b/android/src/main/java/com/rncamerakit/CKCamera.kt index 809cfbf..394a62b 100644 --- a/android/src/main/java/com/rncamerakit/CKCamera.kt +++ b/android/src/main/java/com/rncamerakit/CKCamera.kt @@ -9,6 +9,7 @@ import android.content.ContentValues import android.content.Context import android.content.pm.PackageManager import android.graphics.Color +import android.graphics.Rect import android.hardware.SensorManager import android.media.AudioManager import android.media.MediaActionSound @@ -17,6 +18,7 @@ import android.util.Log import android.view.* import android.widget.FrameLayout import android.widget.LinearLayout +import androidx.annotation.ColorInt import androidx.appcompat.app.AppCompatActivity import androidx.camera.core.* import androidx.camera.lifecycle.ProcessCameraProvider @@ -32,6 +34,7 @@ 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 com.rncamerakit.barcode.BarcodeFrame import java.io.File import java.util.concurrent.ExecutorService import java.util.concurrent.Executors @@ -39,24 +42,34 @@ 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 camera: Camera? = null 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 barcodeFrame: BarcodeFrame? = null private var cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor() - private var scanBarcode: Boolean = false - private var lensType = CameraSelector.LENS_FACING_BACK - private var autoFocus = "on" - private var zoomMode = "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 + // Camera Props + private var lensType = CameraSelector.LENS_FACING_BACK + private var autoFocus = "on" + private var zoomMode = "on" + + // Barcode Props + private var scanBarcode: Boolean = false + private var showFrame = false + private var frameRect: Rect? = null + private var frameColor = Color.GREEN + private var laserColor = Color.RED + private fun getActivity() : Activity { return currentContext.currentActivity!! } @@ -194,15 +207,17 @@ class CKCamera(context: ThemedReactContext) : FrameLayout(context), LifecycleObs .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!!) + + if (scanBarcode) { + val analyzer = QRCodeAnalyzer { barcodes -> + if (barcodes.isNotEmpty()) { + onBarcodeRead(barcodes) + } + } + qrCodeAnalyzer!!.setAnalyzer(cameraExecutor, analyzer) + useCases.add(qrCodeAnalyzer!!) + } try { // Unbind use cases before rebinding @@ -317,10 +332,10 @@ class CKCamera(context: ThemedReactContext) : FrameLayout(context), LifecycleObs private fun onBarcodeRead(barcodes: List) { val event: WritableMap = Arguments.createMap() - event.putArray("barcodes", Arguments.makeNativeArray(barcodes)) + event.putString("codeStringValue", barcodes.first()) currentContext.getJSModule(RCTEventEmitter::class.java).receiveEvent( id, - "onBarcodeRead", + "onReadCode", event ) } @@ -409,6 +424,41 @@ class CKCamera(context: ThemedReactContext) : FrameLayout(context), LifecycleObs outputPath = path } + fun setShowFrame(enabled: Boolean) { + if (enabled) { + barcodeFrame = BarcodeFrame(context) + val actualPreviewWidth = resources.displayMetrics.widthPixels + val actualPreviewHeight = resources.displayMetrics.heightPixels + val height: Int = convertDeviceHeightToSupportedAspectRatio(actualPreviewWidth, actualPreviewHeight) + barcodeFrame!!.setFrameColor(frameColor) + barcodeFrame!!.setLaserColor(laserColor) + (barcodeFrame as View).layout(0, 0, actualPreviewWidth, height) + addView(barcodeFrame) + } else if (barcodeFrame != null) { + removeView(barcodeFrame) + barcodeFrame = null + } + } + + fun setLaserColor(@ColorInt color: Int) { + laserColor = color + if (barcodeFrame != null) { + barcodeFrame!!.setLaserColor(laserColor) + } + } + + fun setFrameColor(@ColorInt color: Int) { + frameColor = color + if (barcodeFrame != null) { + barcodeFrame!!.setFrameColor(color) + } + } + + private fun convertDeviceHeightToSupportedAspectRatio(actualWidth: Int, actualHeight: Int): Int { + val maxScreenRatio = 16 / 9f + return (if (actualHeight / actualWidth > maxScreenRatio) actualWidth * maxScreenRatio else actualHeight).toInt() + } + private fun hasPermissions(): Boolean { val requiredPermissions = arrayOf(Manifest.permission.CAMERA) if (requiredPermissions.all { diff --git a/android/src/main/java/com/rncamerakit/CKCameraManager.kt b/android/src/main/java/com/rncamerakit/CKCameraManager.kt index 5f2ba33..8024188 100644 --- a/android/src/main/java/com/rncamerakit/CKCameraManager.kt +++ b/android/src/main/java/com/rncamerakit/CKCameraManager.kt @@ -1,6 +1,9 @@ package com.rncamerakit +import android.graphics.Color import android.util.Log +import androidx.annotation.ColorInt +import androidx.camera.view.CameraView import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableType import com.facebook.react.common.MapBuilder @@ -9,6 +12,7 @@ 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 { @@ -42,7 +46,7 @@ class CKCameraManager : SimpleViewManager() { override fun getExportedCustomDirectEventTypeConstants(): Map { return MapBuilder.of( "onOrientationChange", MapBuilder.of("registrationName", "onOrientationChange"), - "onBarcodeRead", MapBuilder.of("registrationName", "onBarcodeRead"), + "onReadCode", MapBuilder.of("registrationName", "onReadCode"), "onPictureTaken", MapBuilder.of("registrationName", "onPictureTaken") ) } @@ -72,6 +76,21 @@ class CKCameraManager : SimpleViewManager() { view.setScanBarcode(enabled) } + @ReactProp(name = "showFrame") + fun setShowFrame(view: CKCamera, enabled: Boolean) { + view.setShowFrame(enabled) + } + + @ReactProp(name = "laserColor", defaultInt = Color.RED) + fun setLaserColor(view: CKCamera, @ColorInt color: Int) { + view.setLaserColor(color) + } + + @ReactProp(name = "frameColor", defaultInt = Color.GREEN) + fun setFrameColor(view: CKCamera, @ColorInt color: Int) { + view.setFrameColor(color) + } + @ReactProp(name = "outputPath") fun setOutputPath(view: CKCamera, path: String) { view.setOutputPath(path) diff --git a/android/src/main/java/com/rncamerakit/barcode/BarcodeFrame.kt b/android/src/main/java/com/rncamerakit/barcode/BarcodeFrame.kt new file mode 100644 index 0000000..2822a05 --- /dev/null +++ b/android/src/main/java/com/rncamerakit/barcode/BarcodeFrame.kt @@ -0,0 +1,94 @@ +package com.rncamerakit.barcode + +import android.content.Context +import android.graphics.* +import android.view.View +import androidx.annotation.ColorInt + +import com.rncamerakit.R + +class BarcodeFrame(context: Context) : View(context) { + + private var dimPaint: Paint = Paint() + private var framePaint: Paint = Paint() + private var borderPaint: Paint = Paint() + private var laserPaint: Paint = Paint() + var frameRect: Rect = Rect() + + private var frameWidth = 0 + private var frameHeight = 0 + private var borderMargin = 0 + private var previousFrameTime = System.currentTimeMillis() + private var laserY = 0 + + private fun init(context: Context) { + framePaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) + dimPaint.style = Paint.Style.FILL + dimPaint.color = context.resources.getColor(R.color.bg_dark) + borderPaint = Paint() + borderPaint.style = Paint.Style.STROKE + borderPaint.strokeWidth = STROKE_WIDTH.toFloat() + laserPaint.style = Paint.Style.STROKE + laserPaint.strokeWidth = STROKE_WIDTH.toFloat() + borderMargin = context.resources.getDimensionPixelSize(R.dimen.border_length) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + frameWidth = measuredWidth + frameHeight = measuredHeight + val marginWidth = width / WIDTH_SCALE + val marginHeight = (height / HEIGHT_SCALE).toInt() + frameRect.left = marginWidth + frameRect.right = width - marginWidth + frameRect.top = marginHeight + frameRect.bottom = height - marginHeight + } + + override fun onDraw(canvas: Canvas) { + val timeElapsed = System.currentTimeMillis() - previousFrameTime + super.onDraw(canvas) + canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), dimPaint) + canvas.drawRect(frameRect, framePaint) + drawBorder(canvas) + drawLaser(canvas, timeElapsed) + previousFrameTime = System.currentTimeMillis() + this.invalidate(frameRect) + } + + private fun drawBorder(canvas: Canvas) { + canvas.drawLine(frameRect.left.toFloat(), frameRect.top.toFloat(), frameRect.left.toFloat(), (frameRect.top + borderMargin).toFloat(), borderPaint) + canvas.drawLine(frameRect.left.toFloat(), frameRect.top.toFloat(), (frameRect.left + borderMargin).toFloat(), frameRect.top.toFloat(), borderPaint) + canvas.drawLine(frameRect.left.toFloat(), frameRect.bottom.toFloat(), frameRect.left.toFloat(), (frameRect.bottom - borderMargin).toFloat(), borderPaint) + canvas.drawLine(frameRect.left.toFloat(), frameRect.bottom.toFloat(), (frameRect.left + borderMargin).toFloat(), frameRect.bottom.toFloat(), borderPaint) + canvas.drawLine(frameRect.right.toFloat(), frameRect.top.toFloat(), (frameRect.right - borderMargin).toFloat(), frameRect.top.toFloat(), borderPaint) + canvas.drawLine(frameRect.right.toFloat(), frameRect.top.toFloat(), frameRect.right.toFloat(), (frameRect.top + borderMargin).toFloat(), borderPaint) + canvas.drawLine(frameRect.right.toFloat(), frameRect.bottom.toFloat(), frameRect.right.toFloat(), (frameRect.bottom - borderMargin).toFloat(), borderPaint) + canvas.drawLine(frameRect.right.toFloat(), frameRect.bottom.toFloat(), (frameRect.right - borderMargin).toFloat(), frameRect.bottom.toFloat(), borderPaint) + } + + private fun drawLaser(canvas: Canvas, timeElapsed: Long) { + if (laserY > frameRect.bottom || laserY < frameRect.top) laserY = frameRect.top + canvas.drawLine((frameRect.left + STROKE_WIDTH).toFloat(), laserY.toFloat(), (frameRect.right - STROKE_WIDTH).toFloat(), laserY.toFloat(), laserPaint) + laserY += (timeElapsed / ANIMATION_SPEED).toInt() + } + + fun setFrameColor(@ColorInt borderColor: Int) { + borderPaint.color = borderColor + } + + fun setLaserColor(@ColorInt laserColor: Int) { + laserPaint.color = laserColor + } + + companion object { + private const val STROKE_WIDTH = 5 + private const val ANIMATION_SPEED = 8 + private const val WIDTH_SCALE = 7 + private const val HEIGHT_SCALE = 2.75 + } + + init { + init(context) + } +} diff --git a/example/src/BarcodeScreenExample.tsx b/example/src/BarcodeScreenExample.tsx index 6bffcd2..4a601a3 100644 --- a/example/src/BarcodeScreenExample.tsx +++ b/example/src/BarcodeScreenExample.tsx @@ -8,6 +8,7 @@ export default class BarcodeScreenExample extends Component { super(props); this.state = { example: undefined, + value: undefined, }; } @@ -23,8 +24,8 @@ export default class BarcodeScreenExample extends Component { render() { if (this.state.example) { - const CameraScreen = this.state.example; - return ; + const Screen = this.state.example; + return ; } return ( { - console.log(event); - this.setState({ example: CheckingScreen }); + this.setState({ example: CheckingScreen, value: event.nativeEvent.codeStringValue }); }} hideControls // offsetForScannerFrame = {10} diff --git a/example/src/CheckingScreen.tsx b/example/src/CheckingScreen.tsx index 4f31fb9..a6e78bc 100644 --- a/example/src/CheckingScreen.tsx +++ b/example/src/CheckingScreen.tsx @@ -13,10 +13,12 @@ export default class CheckingScreen extends Component { render() { if (this.state.example) { const CheckingScreen = this.state.example; - return ; + const value = this.state.value; + return ; } return ( + {this.props.value} this.setState({ example: BarcodeScreen })}> Back button @@ -32,6 +34,10 @@ const styles = StyleSheet.create({ alignItems: 'center', backgroundColor: '#F5FCFF', }, + valueText: { + marginBottom: 20, + fontSize: 40, + }, buttonText: { color: 'blue', marginBottom: 20, diff --git a/src/Camera.android.tsx b/src/Camera.android.tsx index fedc009..9542b16 100644 --- a/src/Camera.android.tsx +++ b/src/Camera.android.tsx @@ -25,7 +25,7 @@ function Camera(props, ref) { _.update(transformedProps, 'laserColor', (c) => processColor(c)); _.update(transformedProps, 'surfaceColor', (c) => processColor(c)); - return ; + return ; } const { PORTRAIT, PORTRAIT_UPSIDE_DOWN, LANDSCAPE_LEFT, LANDSCAPE_RIGHT } = RNCameraKitModule.getConstants();