diff --git a/README.md b/README.md
index d0ac70a..4a83cb3 100644
--- a/README.md
+++ b/README.md
@@ -113,14 +113,26 @@ export default function App() {
const handleBarcodeScanned = event => {
const {data, bounds, type} = event?.nativeEvent;
setScannedData({data, bounds, type});
- console.log('Barcode scanned:', data, bounds, type);
+ console.log('Barcode / QR Code scanned:', data, bounds, type);
+ };
+
+ const enableFlashlight = () => {
+ if (scannerRef?.current) {
+ Commands.enableFlashlight(scannerRef.current);
+ }
+ };
+
+ const disableFlashlight = () => {
+ if (scannerRef?.current) {
+ Commands.disableFlashlight(scannerRef.current);
+ }
};
// Pause the camera after barcode / QR code is scanned
- const pauseScanning = () => {
+ const stopScanning = () => {
if (scannerRef?.current) {
- Commands.pauseScanning(scannerRef?.current);
- console.log('Camera preview paused');
+ Commands.stopScanning(scannerRef?.current);
+ console.log('Scanning paused');
}
};
@@ -128,10 +140,22 @@ export default function App() {
const resumeScanning = () => {
if (scannerRef?.current) {
Commands.resumeScanning(scannerRef?.current);
- console.log('Camera preview resumed');
+ console.log('Scanning resumed');
}
};
+ const releaseCamera = () => {
+ if (scannerRef?.current) {
+ Commands.releaseCamera(scannerRef?.current);
+ }
+ }
+
+ const startScanning = () => {
+ if (scannerRef?.current) {
+ Commands.startCamera(scannerRef?.current);
+ }
+ }
+
const checkCameraPermission = async () => {
request(
Platform.OS === 'ios'
@@ -165,22 +189,57 @@ export default function App() {
if (isCameraPermissionGranted) {
return (
-
+
{isActive && (
)}
-
-
-
{scannedData && (
@@ -189,16 +248,13 @@ export default function App() {
Scanned Data: {scannedData?.data}
Type: {scannedData?.type}
-
- Bounds: {JSON.stringify(scannedData?.bounds)}
-
)}
);
} else {
return (
-
+
You need to grant camera permission first
);
@@ -208,8 +264,12 @@ export default function App() {
const styles = StyleSheet.create({
container: {
flex: 1,
- padding: 16,
- backgroundColor: '#fff',
+ },
+ box: {
+ position: 'absolute',
+ borderWidth: 2,
+ borderColor: 'green',
+ zIndex: 10,
},
scanner: {
flex: 1,
@@ -232,6 +292,10 @@ const styles = StyleSheet.create({
fontSize: 16,
marginVertical: 4,
},
+ TextStyle: {
+ fontSize: 30,
+ color: 'red',
+ },
});
```
@@ -385,6 +449,12 @@ default: `false`
If set to `true`, the scanner will pause after capturing a QR code or barcode.
+#### `showBox` (optional)
+propType: `boolean`
+default: `false`
+
+If set to `true`, a green box will be displayed around the QR code or barcode that is detected.
+
#### `isActive` (required)
propType: `boolean`
default: `true`
@@ -420,18 +490,28 @@ if(cameraRef.current) {
This command is used to release the camera.
```js
-if(cameraRef.current) {
- Commands.releaseCamera(cameraRef.current);
+if(cameraRef?.current) {
+ Commands?.releaseCamera(cameraRef?.current);
+}
+```
+
+#### `startCamera`
+
+This command is used to start the camera.
+
+```js
+if(cameraRef?.current) {
+ Commands.startCamera(cameraRef?.current);
}
```
-### `pauseScanning`
+#### `stopScanning`
-This command is used to pause the scanning.
+This command is used to stop the scanning.
```js
if(cameraRef.current) {
- Commands.pauseScanning(cameraRef.current);
+ Commands.stopScanning(cameraRef.current);
}
```
diff --git a/android/build.gradle b/android/build.gradle
index ffb6cfb..5a19c03 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -156,15 +156,23 @@ dependencies {
implementation "com.facebook.react:react-native:+"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
- //Camera-X
- def camerax_version = "1.2.1"
+ def camerax_version = "1.5.0-alpha04"
+ // The following line is optional, as the core library is included indirectly by camera-camera2
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
+ // If you want to additionally use the CameraX Lifecycle library
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
+ // If you want to additionally use the CameraX VideoCapture library
+ implementation "androidx.camera:camera-video:${camerax_version}"
+ // If you want to additionally use the CameraX View class
implementation "androidx.camera:camera-view:${camerax_version}"
+ // If you want to additionally add CameraX ML Kit Vision Integration
+ implementation "androidx.camera:camera-mlkit-vision:${camerax_version}"
+ // If you want to additionally use the CameraX Extensions library
+ implementation "androidx.camera:camera-extensions:${camerax_version}"
//MLkit Barcode scanning
- implementation 'com.google.mlkit:barcode-scanning:17.0.3'
+ implementation 'com.google.mlkit:barcode-scanning:17.3.0'
}
if (isNewArchitectureEnabled()) {
diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt
index 71a3a4f..431e1f3 100644
--- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt
+++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt
@@ -1,70 +1,100 @@
package com.pushpendersingh.reactnativescanner
import android.Manifest
-import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Rect
+import android.os.Handler
+import android.os.Looper
import android.view.Choreographer
+import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
-import androidx.camera.core.ImageProxy
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.camera.view.LifecycleCameraController
import androidx.camera.view.PreviewView
import androidx.core.content.ContextCompat
+import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContext
-import com.facebook.react.uimanager.UIManagerHelper
-import com.facebook.react.uimanager.events.EventDispatcher
+import com.facebook.react.uimanager.events.RCTEventEmitter
import com.google.mlkit.vision.barcode.BarcodeScanner
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode
-import com.google.mlkit.vision.common.InputImage
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import androidx.camera.core.CameraControl
+import androidx.camera.mlkit.vision.MlKitAnalyzer
+import androidx.camera.view.CameraController
+import android.util.Log
-class ReactNativeScannerView(context: Context) : LinearLayout(context) {
+class ReactNativeScannerView(context: Context) : LinearLayout(context) {
+
+ private val TAG = "ReactNativeScannerView"
private var preview: PreviewView
private var mCameraProvider: ProcessCameraProvider? = null
- private lateinit var cameraExecutor: ExecutorService
+ private val cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor()
private lateinit var options: BarcodeScannerOptions
private lateinit var scanner: BarcodeScanner
- private var analysisUseCase: ImageAnalysis = ImageAnalysis.Builder()
- .build()
private lateinit var cameraControl: CameraControl
+ private lateinit var lifecycleCameraController: LifecycleCameraController
+ private lateinit var overlay: BarcodeOverlayView
+ private var showBoxFromUser = false
+ @Volatile
private var isCameraRunning: Boolean = false
+ @Volatile
private var pauseAfterCapture: Boolean = false
+ @Volatile
private var isActive: Boolean = true
+ private lateinit var surfacePreview: Preview
+ private lateinit var imageAnalysis: ImageAnalysis
+ private lateinit var mlKitAnalyzer: MlKitAnalyzer
+
+ // Handler and Runnable for hiding the box after delay
+ private val handler = Handler(Looper.getMainLooper())
+ private val hideBoxRunnable = Runnable {
+ overlay.setShowBox(false)
+ overlay.setRect(null, false)
+ }
+
companion object {
- private val REQUIRED_PERMISSIONS =
- mutableListOf(
- Manifest.permission.CAMERA
- ).toTypedArray()
+ private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
}
init {
- val linearLayoutParams = ViewGroup.LayoutParams(
+ layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
- layoutParams = linearLayoutParams
orientation = VERTICAL
- preview = PreviewView(context)
- preview.layoutParams = ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT
- )
+ preview = PreviewView(context).apply {
+ layoutParams = ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
+ }
addView(preview)
+ overlay = BarcodeOverlayView(context).apply {
+ layoutParams = ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
+ }
+ addView(overlay)
+
setupLayoutHack()
manuallyLayoutChildren()
}
@@ -95,8 +125,6 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) {
startCamera(reactApplicationContext)
}
- cameraExecutor = Executors.newSingleThreadExecutor()
-
options = BarcodeScannerOptions.Builder()
.setBarcodeFormats(
Barcode.FORMAT_QR_CODE,
@@ -116,127 +144,170 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) {
.build()
scanner = BarcodeScanning.getClient(options)
- analysisUseCase.setAnalyzer(
- // newSingleThreadExecutor() will let us perform analysis on a single worker thread
- Executors.newSingleThreadExecutor()
- ) { imageProxy ->
- processImageProxy(scanner, imageProxy, reactApplicationContext)
+ lifecycleCameraController = LifecycleCameraController(context).apply {
+ bindToLifecycle(reactApplicationContext.currentActivity as AppCompatActivity)
+ cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
}
- }
- @SuppressLint("UnsafeOptInUsageError")
- private fun processImageProxy(
- barcodeScanner: BarcodeScanner,
- imageProxy: ImageProxy,
- reactApplicationContext: ReactApplicationContext
- ) {
- imageProxy.image?.let { image ->
- val inputImage =
- InputImage.fromMediaImage(
- image,
- imageProxy.imageInfo.rotationDegrees
- )
+ preview.controller = lifecycleCameraController
+
+ mlKitAnalyzer = MlKitAnalyzer(
+ listOf(scanner),
+ CameraController.COORDINATE_SYSTEM_VIEW_REFERENCED,
+ ContextCompat.getMainExecutor(context)
+ ) { result ->
+ if (!isCameraRunning || !isActive) return@MlKitAnalyzer
+
+ val barcodes = result?.getValue(scanner).orEmpty()
+ val barcode = barcodes.firstOrNull { !it.rawValue.isNullOrEmpty() }
+ barcode?.boundingBox?.let { bounds ->
+ if (showBoxFromUser) {
+ updateOverlay(bounds, true)
+ handler.removeCallbacks(hideBoxRunnable)
+ handler.postDelayed(hideBoxRunnable, 1000)
+ }
+ sendBarcodeEvent(barcode.rawValue ?: "", bounds, showBoxFromUser, barcode.format)
- if (!isCameraRunning) {
- return;
+ if (pauseAfterCapture) {
+ stopScanning()
+ }
}
+ }
- barcodeScanner.process(inputImage)
- .addOnSuccessListener { barcodeList ->
- if (barcodeList.isNotEmpty()) {
- if (pauseAfterCapture) {
- pauseScanning()
- }
-
- val surfaceId = UIManagerHelper.getSurfaceId(reactApplicationContext)
- val reactContext = context as ReactContext
- val eventDispatcher: EventDispatcher? = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id)
-
- barcodeList.forEach { barcode ->
- barcode?.let { code ->
- code.cornerPoints?.let { cornerPoints ->
- code.boundingBox?.let { bounds ->
- eventDispatcher?.dispatchEvent(ReactNativeScannerViewEvent(surfaceId, id, code.rawValue?: "", bounds, cornerPoints, code.format))
- }
- }
- }
- }
- }
- }
- .addOnFailureListener {
- // This failure will happen if the barcode scanning model
- // fails to download from Google Play Services
- }.addOnCompleteListener {
- // When the image is from CameraX analysis use case, must
- // call image.close() on received images when finished
- // using them. Otherwise, new images may not be received
- // or the camera may stall.
- imageProxy.image?.close()
- imageProxy.close()
- }
+ imageAnalysis = ImageAnalysis.Builder()
+ .build()
+ .also {
+ it.setAnalyzer(cameraExecutor, mlKitAnalyzer)
+ }
+
+ lifecycleCameraController.setImageAnalysisAnalyzer(
+ cameraExecutor,
+ mlKitAnalyzer
+ )
+ lifecycleCameraController.setEnabledUseCases(CameraController.IMAGE_ANALYSIS)
+ }
+
+ private fun updateOverlay(rect: Rect, showBox: Boolean) {
+ overlay.setRect(rect, showBox)
+ }
+
+ private fun sendBarcodeEvent(data: String, bounds: Rect, showBox: Boolean, type: Int) {
+ val reactContext = context as ReactContext
+ val event = Arguments.createMap().apply {
+ putString("data", data)
+ putString("type", when (type) {
+ Barcode.FORMAT_QR_CODE -> "QR_CODE"
+ Barcode.FORMAT_AZTEC -> "AZTEC"
+ Barcode.FORMAT_CODE_128 -> "CODE_128"
+ Barcode.FORMAT_CODE_39 -> "CODE_39"
+ Barcode.FORMAT_CODE_93 -> "CODE_93"
+ Barcode.FORMAT_CODABAR -> "CODABAR"
+ Barcode.FORMAT_DATA_MATRIX -> "DATA_MATRIX"
+ Barcode.FORMAT_EAN_13 -> "EAN_13"
+ Barcode.FORMAT_EAN_8 -> "EAN_8"
+ Barcode.FORMAT_ITF -> "ITF"
+ Barcode.FORMAT_PDF417 -> "PDF417"
+ Barcode.FORMAT_UPC_A -> "UPC_A"
+ Barcode.FORMAT_UPC_E -> "UPC_E"
+ else -> "UNKNOWN"
+ })
+ putBoolean("showBox", showBox)
+ putMap("bounds", Arguments.createMap().apply {
+ putDouble("width", bounds.width().toDouble())
+ putDouble("height", bounds.height().toDouble())
+ putMap("origin", Arguments.createMap().apply {
+ putMap("topLeft", Arguments.createMap().apply {
+ putDouble("x", bounds.left.toDouble())
+ putDouble("y", bounds.top.toDouble())
+ })
+ putMap("bottomLeft", Arguments.createMap().apply {
+ putDouble("x", bounds.left.toDouble())
+ putDouble("y", bounds.bottom.toDouble())
+ })
+ putMap("bottomRight", Arguments.createMap().apply {
+ putDouble("x", bounds.right.toDouble())
+ putDouble("y", bounds.bottom.toDouble())
+ })
+ putMap("topRight", Arguments.createMap().apply {
+ putDouble("x", bounds.right.toDouble())
+ putDouble("y", bounds.top.toDouble())
+ })
+ })
+ })
}
+ reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, "onQrScanned", event)
}
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
- ContextCompat.checkSelfPermission(
- context, it
- ) == PackageManager.PERMISSION_GRANTED
+ ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
}
private fun startCamera(reactApplicationContext: ReactApplicationContext) {
-
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener({
- // Used to bind the lifecycle of cameras to the lifecycle owner
- val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
- mCameraProvider = cameraProvider
- // Preview
- val surfacePreview = Preview.Builder()
- .build()
- .also {
- it.setSurfaceProvider(preview.surfaceProvider)
- }
+ try {
+ val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
+ mCameraProvider = cameraProvider
- // Select back camera as a default
- val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
+ surfacePreview = Preview.Builder()
+ .build()
+ .also {
+ it.setSurfaceProvider(preview.surfaceProvider)
+ }
- isCameraRunning = true
+ imageAnalysis = ImageAnalysis.Builder()
+ .build()
+ .also {
+ it.setAnalyzer(cameraExecutor, mlKitAnalyzer)
+ }
+
+ val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
+
+ isCameraRunning = true
- try {
- // Unbind use cases before rebinding
cameraProvider.unbindAll()
- // Bind use cases to camera
val camera = cameraProvider.bindToLifecycle(
- (reactApplicationContext.currentActivity as AppCompatActivity),
+ reactApplicationContext.currentActivity as AppCompatActivity,
cameraSelector,
surfacePreview,
- analysisUseCase
+ imageAnalysis
)
cameraControl = camera.cameraControl
-
} catch (exc: Exception) {
+ Log.e(TAG, "Error starting camera: ${exc.message}")
isCameraRunning = false
}
}, ContextCompat.getMainExecutor(context))
}
fun enableFlashlight() {
- cameraControl.enableTorch(true)
+ try {
+ cameraControl.enableTorch(true)
+ } catch (e: Exception) {
+ Log.e(TAG, "Error enabling flashlight: ${e.message}")
+ }
}
fun disableFlashlight() {
- cameraControl.enableTorch(false)
+ try {
+ cameraControl.enableTorch(false)
+ } catch (e: Exception) {
+ Log.e(TAG, "Error disabling flashlight: ${e.message}")
+ }
}
fun releaseCamera() {
- cameraExecutor.shutdown()
- mCameraProvider?.unbindAll()
- }
-
- private fun stopCamera() {
-
+ try {
+ cameraExecutor.shutdown()
+ isCameraRunning = false
+ imageAnalysis.clearAnalyzer()
+ lifecycleCameraController.unbind()
+ mCameraProvider?.unbindAll()
+ } catch (e: Exception) {
+ Log.e(TAG, "Error releasing camera: ${e.message}")
+ }
}
fun setPauseAfterCapture(value: Boolean) {
@@ -247,30 +318,83 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) {
isActive = value
}
- fun pauseScanning() {
+ @Synchronized
+ fun stopScanning() {
if (isCameraRunning) {
- isCameraRunning = false
- mCameraProvider?.unbind(analysisUseCase)
+ try {
+ isCameraRunning = false
+ imageAnalysis.clearAnalyzer()
+ lifecycleCameraController.unbind()
+ mCameraProvider?.unbindAll()
+ } catch (e: Exception) {
+ Log.e(TAG, "Error pausing camera: ${e.message}")
+ }
}
}
+ @Synchronized
fun resumeScanning() {
if (!isCameraRunning) {
- isCameraRunning = true
-
try {
val reactContext = context as ReactContext
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
- // Bind use cases to camera
mCameraProvider?.bindToLifecycle(
- (reactContext.currentActivity as AppCompatActivity),
+ reactContext.currentActivity as AppCompatActivity,
cameraSelector,
- analysisUseCase
+ surfacePreview,
+ imageAnalysis
)
+ isCameraRunning = true
} catch (exc: Exception) {
+ Log.e(TAG, "Error resuming camera: ${exc.message}")
isCameraRunning = false
}
}
}
-}
+
+ fun setShowBox(showBox: Boolean) {
+ showBoxFromUser = showBox
+ if (!showBox) {
+ overlay.setRect(null, false)
+ }
+ overlay.setShowBox(showBox)
+ }
+
+ inner class BarcodeOverlayView(context: Context) : View(context) {
+ private var rect: Rect? = null
+ private var showBox: Boolean = false
+ private val paint = Paint().apply {
+ color = Color.GREEN
+ style = Paint.Style.STROKE
+ strokeWidth = 5f
+ }
+
+ fun setRect(rect: Rect?, showBox: Boolean) {
+ this.rect = rect
+ this.showBox = showBox
+ invalidate()
+ }
+
+ fun setShowBox(showBox: Boolean) {
+ this.showBox = showBox
+ if (!showBox) {
+ rect = null
+ }
+ invalidate()
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+ if (showBox && rect != null) {
+ canvas.drawRect(rect!!, paint)
+ }
+ }
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ handler.removeCallbacksAndMessages(null)
+ releaseCamera()
+ }
+}
\ No newline at end of file
diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewEvent.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewEvent.kt
index f602a0c..85f8d96 100644
--- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewEvent.kt
+++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewEvent.kt
@@ -1,51 +1,13 @@
package com.pushpendersingh.reactnativescanner
-import android.graphics.Point
-import android.graphics.Rect
-import com.facebook.react.bridge.Arguments
-import com.facebook.react.bridge.WritableArray
-import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event
-import com.google.mlkit.vision.barcode.common.Barcode
class ReactNativeScannerViewEvent(
surfaceId: Int,
viewId: Int,
- private val qrValue: String,
- private val rect: Rect,
- private val origin: Array,
- private val type: Int
) : Event(surfaceId, viewId) {
override fun getEventName(): String {
return "onQrScanned"
}
-
- override fun getEventData(): WritableMap {
- val event: WritableMap = Arguments.createMap()
- val bounds = Arguments.createMap()
- bounds.putArray("origin", getPoints(origin))
- bounds.putInt("width", rect.width())
- bounds.putInt("height", rect.height())
-
- event.putMap("bounds", bounds)
- event.putString("data", qrValue)
- if (type == Barcode.FORMAT_QR_CODE)
- event.putString("type", "QR_CODE")
- else
- event.putString("type", "UNKNOWN")
-
- return event
- }
-
- private fun getPoints(points: Array): WritableArray {
- val origin: WritableArray = Arguments.createArray()
- for (point in points) {
- val pointData: WritableMap = Arguments.createMap()
- pointData.putInt("x", point.x)
- pointData.putInt("y", point.y)
- origin.pushMap(pointData);
- }
- return origin
- }
}
diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt
index f975c19..e909d97 100644
--- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt
+++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt
@@ -11,7 +11,8 @@ import com.facebook.react.viewmanagers.ReactNativeScannerViewManagerDelegate
@ReactModule(name = ReactNativeScannerViewManager.NAME)
class ReactNativeScannerViewManager(private val mCallerContext: ReactApplicationContext) :
- SimpleViewManager(), ReactNativeScannerViewManagerInterface {
+ SimpleViewManager(),
+ ReactNativeScannerViewManagerInterface {
private val mDelegate: ViewManagerDelegate
@@ -32,6 +33,11 @@ class ReactNativeScannerViewManager(private val mCallerContext: ReactApplication
view?.setPauseAfterCapture(value)
}
+ @ReactProp(name = "showBox")
+ override fun setShowBox(view: ReactNativeScannerView?, value: Boolean) {
+ view?.setShowBox(value)
+ }
+
@ReactProp(name = "isActive")
override fun setIsActive(view: ReactNativeScannerView?, value: Boolean) {
view?.setIsActive(value)
@@ -49,14 +55,21 @@ class ReactNativeScannerViewManager(private val mCallerContext: ReactApplication
view?.releaseCamera()
}
- override fun pauseScanning(view: ReactNativeScannerView?) {
- view?.pauseScanning()
+ override fun stopScanning(view: ReactNativeScannerView?) {
+ view?.stopScanning()
}
override fun resumeScanning(view: ReactNativeScannerView?) {
view?.resumeScanning()
}
+ override fun startCamera(view: ReactNativeScannerView?) {
+ val reactAppContext = view?.context as? ReactApplicationContext
+ reactAppContext?.let {
+ view.setUpCamera(it)
+ }
+ }
+
override fun createViewInstance(reactContext: ThemedReactContext): ReactNativeScannerView {
val reactnativeScannerView = ReactNativeScannerView(mCallerContext)
reactnativeScannerView.setUpCamera(mCallerContext)
@@ -66,4 +79,4 @@ class ReactNativeScannerViewManager(private val mCallerContext: ReactApplication
companion object {
const val NAME = "ReactNativeScannerView"
}
-}
+}
\ No newline at end of file
diff --git a/example/App.tsx b/example/App.tsx
index 529749f..cbdd936 100644
--- a/example/App.tsx
+++ b/example/App.tsx
@@ -37,10 +37,22 @@ export default function App() {
console.log('Barcode / QR Code scanned:', data, bounds, type);
};
+ const enableFlashlight = () => {
+ if (scannerRef?.current) {
+ Commands.enableFlashlight(scannerRef.current);
+ }
+ };
+
+ const disableFlashlight = () => {
+ if (scannerRef?.current) {
+ Commands.disableFlashlight(scannerRef.current);
+ }
+ };
+
// Pause the camera after barcode / QR code is scanned
- const pauseScanning = () => {
+ const stopScanning = () => {
if (scannerRef?.current) {
- Commands.pauseScanning(scannerRef?.current);
+ Commands.stopScanning(scannerRef?.current);
console.log('Scanning paused');
}
};
@@ -53,6 +65,18 @@ export default function App() {
}
};
+ const releaseCamera = () => {
+ if (scannerRef?.current) {
+ Commands.releaseCamera(scannerRef?.current);
+ }
+ }
+
+ const startScanning = () => {
+ if (scannerRef?.current) {
+ Commands.startCamera(scannerRef?.current);
+ }
+ }
+
const checkCameraPermission = async () => {
request(
Platform.OS === 'ios'
@@ -94,14 +118,49 @@ export default function App() {
onQrScanned={handleBarcodeScanned}
pauseAfterCapture={false} // Pause the scanner after barcode / QR code is scanned
isActive={isActive} // Start / stop the scanner using this prop
+ showBox={true} // Show the box around the barcode / QR code
/>
)}
-
-
- setIsActive(false)} />
- setIsActive(true)} />
+ {
+ stopScanning();
+ setIsActive(false);
+ }}
+ />
+ {
+ resumeScanning();
+ setIsActive(true);
+ }}
+ />
+ {
+ disableFlashlight();
+ }}
+ />
+ {
+ enableFlashlight();
+ }}
+ />
+ {
+ releaseCamera();
+ }}
+ />
+ {
+ startScanning();
+ }}
+ />
{scannedData && (
@@ -110,9 +169,6 @@ export default function App() {
Scanned Data: {scannedData?.data}
Type: {scannedData?.type}
-
- Bounds: {JSON.stringify(scannedData?.bounds)}
-
)}
@@ -130,6 +186,12 @@ const styles = StyleSheet.create({
container: {
flex: 1,
},
+ box: {
+ position: 'absolute',
+ borderWidth: 2,
+ borderColor: 'green',
+ zIndex: 10,
+ },
scanner: {
flex: 1,
},
diff --git a/example/ios/AwesomeProject/Info.plist b/example/ios/AwesomeProject/Info.plist
index 831f906..355909c 100644
--- a/example/ios/AwesomeProject/Info.plist
+++ b/example/ios/AwesomeProject/Info.plist
@@ -26,16 +26,15 @@
NSAppTransportSecurity
-
NSAllowsArbitraryLoads
NSAllowsLocalNetworking
+ NSCameraUsageDescription
+ Your message to user when the camera is accessed for the first time
NSLocationWhenInUseUsageDescription
- NSCameraUsageDescription
- Your message to user when the camera is accessed for the first time
UILaunchStoryboardName
LaunchScreen
UIRequiredDeviceCapabilities
@@ -44,9 +43,16 @@
UISupportedInterfaceOrientations
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+
+ UISupportedInterfaceOrientations~ipad
+
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+ UIInterfaceOrientationPortrait
UIViewControllerBasedStatusBarAppearance
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
index a59cbba..1a5082c 100644
--- a/example/ios/Podfile.lock
+++ b/example/ios/Podfile.lock
@@ -7,7 +7,7 @@ PODS:
- hermes-engine (0.76.0):
- hermes-engine/Pre-built (= 0.76.0)
- hermes-engine/Pre-built (0.76.0)
- - pushpendersingh-react-native-scanner (1.3.1):
+ - pushpendersingh-react-native-scanner (1.4.0):
- DoubleConversion
- glog
- hermes-engine
@@ -1767,7 +1767,7 @@ SPEC CHECKSUMS:
fmt: 10c6e61f4be25dc963c36bd73fc7b1705fe975be
glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a
hermes-engine: 9de51d2f67336348a6cd5b686330e436d1dbd522
- pushpendersingh-react-native-scanner: 54b29f07c01cabf2ca5c9e71f797e50e08ced4f9
+ pushpendersingh-react-native-scanner: 6484690304a239489120a50da2b87d3abec15077
RCT-Folly: bf5c0376ffe4dd2cf438dcf86db385df9fdce648
RCTDeprecation: 4c2c4a088b6f0ccfcbd53c9d5614b0238ad57909
RCTRequired: 2d8a683a7848bc0baf5883f0792c1ac43f6267b5
diff --git a/example/package.json b/example/package.json
index 2830c0b..a7e138d 100644
--- a/example/package.json
+++ b/example/package.json
@@ -8,10 +8,11 @@
"lint": "eslint .",
"start": "react-native start",
"test": "jest",
- "xcode": "xed -b ios"
+ "xcode": "xed -b ios",
+ "clear": "watchman watch-del-all"
},
"dependencies": {
- "@pushpendersingh/react-native-scanner": "1.3.1",
+ "@pushpendersingh/react-native-scanner": "^1.4.0",
"react": "18.3.1",
"react-native": "0.76.0",
"react-native-permissions": "^5.0.1"
diff --git a/ios/ReactNativeScannerView.mm b/ios/ReactNativeScannerView.mm
index 22b86ec..4f6d008 100644
--- a/ios/ReactNativeScannerView.mm
+++ b/ios/ReactNativeScannerView.mm
@@ -10,19 +10,21 @@
using namespace facebook::react;
@interface ReactNativeScannerView ()
+@property (nonatomic, strong) dispatch_queue_t sessionQueue;
@end
@implementation ReactNativeScannerView {
UIView * _view;
-
AVCaptureSession *_session;
AVCaptureDevice *_device;
AVCaptureDeviceInput *_input;
AVCaptureMetadataOutput *_output;
AVCaptureVideoPreviewLayer *_prevLayer;
+ CAShapeLayer *_boundingBoxLayer;
BOOL pauseAfterCapture;
BOOL isActive;
+ BOOL showBox;
}
+ (NSArray *)metadataObjectTypes
@@ -45,49 +47,66 @@ + (ComponentDescriptorProvider)componentDescriptorProvider
return concreteComponentDescriptorProvider();
}
+/**
+ * Initializes a ReactNativeScannerView with the specified frame.
+ *
+ * This method sets up the session queue for handling scanner operations,
+ * initializes the view and bounding box layer to display detected object bounds,
+ * and ensures the bounding box is rendered above other view elements.
+ */
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
- static const auto defaultProps = std::make_shared();
- _props = defaultProps;
-
- _view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
- _session = [[AVCaptureSession alloc] init];
- _device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
-
- NSError *error = nil;
- _input = [AVCaptureDeviceInput deviceInputWithDevice:_device error:&error];
- if (_input) {
- [_session addInput:_input];
- } else {
- NSLog(@"%@", [error localizedDescription]);
- }
-
- _output = [[AVCaptureMetadataOutput alloc] init];
- [_output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
- [_session addOutput:_output];
-
- _output.metadataObjectTypes = [ReactNativeScannerView metadataObjectTypes];
-
- _prevLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session];
- _prevLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
- [_view.layer addSublayer:_prevLayer];
-
-
- // Create a dispatch queue.
- dispatch_queue_t sessionQueue = dispatch_queue_create("session queue", DISPATCH_QUEUE_SERIAL);
+ _sessionQueue = dispatch_queue_create("com.pushpendersingh.reactNativeScanner.sessionQueue", DISPATCH_QUEUE_SERIAL);
- // Use dispatch_async to call the startRunning method on the sessionQueue.
- dispatch_async(sessionQueue, ^{
- [self->_session startRunning];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ self->_view = [[UIView alloc] initWithFrame:self.bounds];
+ self->_boundingBoxLayer = [CAShapeLayer layer];
+ self->_boundingBoxLayer.strokeColor = [UIColor greenColor].CGColor;
+ self->_boundingBoxLayer.lineWidth = 2.0;
+ self->_boundingBoxLayer.fillColor = [UIColor clearColor].CGColor;
+
+ // Ensure bounding box is on top
+ self->_boundingBoxLayer.zPosition = 999;
+
+ [self->_view.layer addSublayer:self->_boundingBoxLayer];
+ self.contentView = self->_view;
});
- self.contentView = _view;
+ dispatch_async(_sessionQueue, ^{
+ [self setupSessionIfNeeded];
+ });
}
-
return self;
}
+/**
+ * Overrides the layoutSubviews method to update the frames of _view and _prevLayer.
+ * Ensures that the updates are performed on the main dispatch queue for proper UI rendering.
+ */
+- (void)layoutSubviews
+{
+ [super layoutSubviews];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ self->_view.frame = self.bounds;
+ self->_prevLayer.frame = self->_view.bounds;
+ });
+}
+
+/**
+ * Handles the output from the capture session and processes detected metadata objects.
+ *
+ * @param captureOutput The capture output that is providing the metadata objects.
+ * @param metadataObjects An array of metadata objects detected by the capture session.
+ * @param connection The connection from which the metadata objects are received.
+ *
+ * This method filters the detected metadata objects to include only valid barcodes based on
+ * predefined barcode types. If `pauseAfterCapture` is enabled and valid barcodes are found,
+ * the scanning session is paused. For each valid barcode, it transforms the metadata object,
+ * extracts the bounding box and corner points, and emits a scanned event with the barcode's
+ * details. Additionally, if `showBox` is enabled, it draws a bounding box around the detected
+ * barcode and removes it after a short delay.
+ */
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
if (_eventEmitter == nullptr) {
@@ -115,7 +134,7 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:
// * Pause AVCaptureSession for further processing, after valid barcodes found,
// * Can be resumed back by calling resumeScanning from the owner of the component
if (pauseAfterCapture == YES && validBarCodes.count > 0) {
- [self pauseScanning];
+ [self stopScanning];
}
for (AVMetadataObject *metadata in validBarCodes) {
@@ -159,48 +178,173 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:
.data = std::string([codeString UTF8String]),
.target = std::int32_t([codeString lengthOfBytesUsingEncoding:NSUTF8StringEncoding])
});
+
+ // Draw the bounding box if showBox is true
+ if (showBox == YES) {
+ UIBezierPath *path = [UIBezierPath bezierPathWithRect:highlightViewRect];
+ _boundingBoxLayer.path = path.CGPath;
+
+ // Hide bounding box after 1 second
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+ self->_boundingBoxLayer.path = nil;
+ });
+ } else {
+ _boundingBoxLayer.path = nil;
+ }
}
}
+/**
+ * Maps a dictionary to a CGPoint.
+ *
+ * This method extracts the "X" and "Y" values from the provided dictionary and creates a CGPoint.
+ * If the dictionary is nil or missing either the "X" or "Y" value, it logs an error and returns CGPointZero.
+ *
+ * @param object The dictionary containing "X" and "Y" keys with NSNumber values.
+ * @return A CGPoint constructed from the "X" and "Y" values, or CGPointZero if inputs are invalid.
+ */
- (CGPoint)mapObject:(NSDictionary *)object {
if (object == nil) {
+ NSLog(@"Corner dictionary is nil.");
return CGPointMake(0, 0);
}
-
- return CGPointMake([[object objectForKey:@"X"] doubleValue], [[object objectForKey:@"Y"] doubleValue]);
+ NSNumber *xValue = object[@"X"];
+ NSNumber *yValue = object[@"Y"];
+ if (xValue == nil || yValue == nil) {
+ NSLog(@"Missing corner X/Y values.");
+ return CGPointMake(0, 0);
+ }
+ return CGPointMake([xValue doubleValue], [yValue doubleValue]);
}
+/**
+ * Sets the active state of the scanner.
+ *
+ * @param active A boolean value indicating whether the scanner should be active.
+ * - If `YES`, the preview and scanning will resume.
+ * - If `NO`, the preview and scanning will pause, and the camera will be released.
+ */
- (void)setIsActive:(BOOL)active {
isActive = active;
// Enable/Disable Preview Layer
if (isActive) {
+ [self resumePreview];
[self resumeScanning];
} else {
- [self pauseScanning];
- }
-
- if (isActive == _session.isRunning) {
- return;
- }
- // Start/Stop session
- if (isActive) {
- [_session startRunning];
- } else {
- [_session stopRunning];
+ [self pausePreview];
+ [self stopScanning];
+ [self releaseCamera];
}
}
+/**
+ * Releases the camera session and associated resources.
+ *
+ * This method stops the camera session from running, removes all inputs and outputs,
+ * removes the preview layer from its superlayer, and sets all related properties to nil.
+ * It ensures that the camera and session resources are properly released.
+ */
- (void)releaseCamera {
-
NSLog(@"%@", @"Release Camera");
+ if (_session != nil) {
+ [_session stopRunning];
+ for (AVCaptureInput *input in _session.inputs) {
+ [_session removeInput:input];
+ }
+ for (AVCaptureOutput *output in _session.outputs) {
+ [_session removeOutput:output];
+ }
+ [_prevLayer removeFromSuperlayer];
+ _prevLayer = nil;
+ _session = nil;
+ _device = nil;
+ _input = nil;
+ _output = nil;
+ NSLog(@"Camera and session resources released.");
+ }
+}
+/**
+ * Sets up the AVCaptureSession if it hasn't been initialized.
+ *
+ * This method initializes the capture session, configures the video device input,
+ * sets up metadata output for barcode scanning, and creates a preview layer
+ * to display the camera feed. It ensures that the session is only set up once
+ * and starts running the session.
+ */
+- (void)setupSessionIfNeeded {
if (_session != nil) {
- // Stop the session
- [_session stopRunning];
+ return; // Already set up
+ }
+
+ _session = [[AVCaptureSession alloc] init];
+ if (!_session) {
+ NSLog(@"Failed to create AVCaptureSession.");
+ return;
+ }
+
+ // Disable automatic configuration of audio session since we don't need it for scanning barcodes
+ _session.automaticallyConfiguresApplicationAudioSession = NO;
+ _session.automaticallyConfiguresCaptureDeviceForWideColor = NO;
+
+ _device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
+ if (!_device) {
+ NSLog(@"Failed to get default AVCaptureDevice for video.");
+ return;
}
+
+ NSError *error = nil;
+ _input = [AVCaptureDeviceInput deviceInputWithDevice:_device error:&error];
+ if (_input) {
+ [_session addInput:_input];
+ } else {
+ NSLog(@"Error creating AVCaptureDeviceInput: %@", [error localizedDescription]);
+ }
+
+ _output = [[AVCaptureMetadataOutput alloc] init];
+ if (_output) {
+ [_output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
+ [_session addOutput:_output];
+ _output.metadataObjectTypes = [ReactNativeScannerView metadataObjectTypes];
+ } else {
+ NSLog(@"Failed to create AVCaptureMetadataOutput.");
+ }
+
+ // Create preview layer on main thread
+ dispatch_async(dispatch_get_main_queue(), ^{
+ self->_prevLayer = [AVCaptureVideoPreviewLayer layerWithSession:self->_session];
+ self->_prevLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
+ [self->_view.layer addSublayer:self->_prevLayer];
+ self->_prevLayer.frame = self->_view.bounds;
+ });
+
+ // Start the session
+ [_session startRunning];
+}
+
+/**
+ * Initiates the camera by setting up the session and starting it if it's not already running.
+ * Executes the setup on the session queue asynchronously to ensure thread safety.
+ */
+- (void)startCamera {
+ dispatch_async(_sessionQueue, ^{
+ [self setupSessionIfNeeded];
+ if (self->_session && !self->_session.isRunning) {
+ [self->_session startRunning];
+ NSLog(@"Capture session started.");
+ }
+ });
}
+/**
+ * Enables the device's flashlight (torch).
+ *
+ * This method checks if the device has a torch and if turning it on is supported.
+ * If supported, it attempts to lock the device for configuration,
+ * sets the torch mode to on, and then unlocks the device.
+ * If configuration fails, it logs the encountered error.
+ */
- (void)enableFlashlight {
if ([_device hasTorch] && [_device isTorchModeSupported:AVCaptureTorchModeOn]) {
NSError *error = nil;
@@ -214,6 +358,11 @@ - (void)enableFlashlight {
}
}
+/**
+ * Disables the device's flashlight if it is available and supported.
+ * Locks the device configuration, sets the torch mode to off, and then unlocks the configuration.
+ * Logs an error message if the device configuration cannot be locked.
+ */
- (void)disableFlashlight {
if ([_device hasTorch] && [_device isTorchModeSupported:AVCaptureTorchModeOff]) {
NSError *error = nil;
@@ -227,41 +376,115 @@ - (void)disableFlashlight {
}
}
-- (void)pauseScanning {
+- (void)pausePreview {
+ // Check if the preview layer's connection is currently enabled
if ([[_prevLayer connection] isEnabled]) {
+ // Disable the preview layer's connection to pause the preview
[[_prevLayer connection] setEnabled:NO];
}
}
-- (void)resumeScanning {
+/**
+ * Resumes the camera preview by enabling the connection of the previous layer if it is not already enabled.
+ */
+- (void)resumePreview {
if (![[_prevLayer connection] isEnabled]) {
[[_prevLayer connection] setEnabled:YES];
}
}
+/**
+ * Stop the scanning session if it is currently running.
+ *
+ * This method first checks if the scanning session exists. If the session is active,
+ * it stops the session asynchronously and logs a message indicating that the capture
+ * session has been stop.
+ */
+- (void)stopScanning {
+ if (_session == nil) {
+ NSLog(@"Session is nil, cannot stop scanning.");
+ return;
+ }
+ dispatch_async(_sessionQueue, ^{
+ if (self->_session.isRunning) {
+ [self->_session stopRunning];
+ NSLog(@"Capture session stop.");
+ }
+ });
+}
+
+/**
+ * Resumes the scanning session.
+ *
+ * This method checks whether the scanning session is initialized. If the session
+ * exists and is not currently running, it starts the session and logs the action.
+ * If the session is nil, it logs an appropriate message and does not attempt to resume.
+ */
+- (void)resumeScanning {
+ if (_session == nil) {
+ NSLog(@"Session is nil, cannot resume scanning.");
+ return;
+ }
+ dispatch_async(_sessionQueue, ^{
+ if (!self->_session.isRunning) {
+ [self->_session startRunning];
+ NSLog(@"Capture session resumed.");
+ }
+ });
+}
+
+/**
+ * Updates the properties of the ReactNativeScannerView.
+ *
+ * This method receives new and old property sets, updates internal state variables
+ * such as `pauseAfterCapture` and `showBox` based on the new properties, sets the active
+ * state, and calls the superclass's `updateProps` method to handle any additional updates.
+ *
+ * @param props The new set of properties to apply.
+ * @param oldProps The previous set of properties before the update.
+ */
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &oldViewProps = *std::static_pointer_cast(_props);
const auto &newViewProps = *std::static_pointer_cast(props);
pauseAfterCapture = newViewProps.pauseAfterCapture;
+ showBox = newViewProps.showBox;
[self setIsActive:newViewProps.isActive];
[super updateProps:props oldProps:oldProps];
}
+/**
+ * Updates the layout metrics for the React Native Scanner view.
+ *
+ * This method adjusts the layout based on the new layout metrics provided.
+ * It ensures that the previous layer's frame matches the bounds of the current view's layer.
+ *
+ * @param layoutMetrics The new layout metrics to apply.
+ * @param oldLayoutMetrics The previous layout metrics before the update.
+ */
- (void)updateLayoutMetrics:(const facebook::react::LayoutMetrics &)layoutMetrics oldLayoutMetrics:(const facebook::react::LayoutMetrics &)oldLayoutMetrics{
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
_prevLayer.frame = [_view.layer bounds];
}
+/**
+ * Handles the specified command with the provided arguments.
+ *
+ * @param commandName The name of the command to execute.
+ * @param args An array of arguments associated with the command.
+ */
- (void)handleCommand:(nonnull const NSString *)commandName args:(nonnull const NSArray *)args {
RCTReactNativeScannerViewHandleCommand(self, commandName, args);
}
@end
+/**
+ * Retrieves the ReactNativeScannerView class that conforms to the RCTComponentViewProtocol.
+ */
Class ReactNativeScannerViewCls(void)
{
return ReactNativeScannerView.class;
-}
+}
\ No newline at end of file
diff --git a/ios/ReactNativeScannerViewManager.mm b/ios/ReactNativeScannerViewManager.mm
index 8d5f603..9fcafe8 100644
--- a/ios/ReactNativeScannerViewManager.mm
+++ b/ios/ReactNativeScannerViewManager.mm
@@ -12,6 +12,7 @@ @implementation ReactNativeScannerViewManager
RCT_EXPORT_VIEW_PROPERTY(onQrScanned, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(pauseAfterCapture, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(showBox, BOOL)
RCT_EXPORT_VIEW_PROPERTY(isActive, BOOL)
@end
diff --git a/package.json b/package.json
index 71a46d2..4a58b22 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@pushpendersingh/react-native-scanner",
- "version": "1.3.1",
+ "version": "1.4.0-beta.1",
"description": "A QR code & Barcode Scanner for React Native Projects.",
"main": "lib/commonjs/index",
"module": "lib/module/index",
diff --git a/src/ReactNativeScannerViewNativeComponent.ts b/src/ReactNativeScannerViewNativeComponent.ts
index 2441391..c99f2e5 100644
--- a/src/ReactNativeScannerViewNativeComponent.ts
+++ b/src/ReactNativeScannerViewNativeComponent.ts
@@ -33,16 +33,20 @@ interface NativeCommands {
releaseCamera: (
viewRef: React.ElementRef>
) => Promise;
- pauseScanning: (
+ stopScanning: (
viewRef: React.ElementRef>
) => void;
resumeScanning: (
viewRef: React.ElementRef>
) => void;
+ startCamera: (
+ viewRef: React.ElementRef>
+ ) => void;
}
interface NativeProps extends ViewProps {
pauseAfterCapture?: boolean;
+ showBox: boolean;
isActive?: boolean;
onQrScanned?: DirectEventHandler; // Event handler for QR code scanned
}
@@ -52,8 +56,9 @@ export const Commands = codegenNativeCommands({
'enableFlashlight',
'disableFlashlight',
'releaseCamera',
- 'pauseScanning',
+ 'stopScanning',
'resumeScanning',
+ 'startCamera',
],
});