From ed80a83a5c2e5a0240c6ccf75c2bbbf33604ac08 Mon Sep 17 00:00:00 2001 From: pushpender singh Date: Thu, 24 Aug 2023 07:29:14 -0400 Subject: [PATCH 01/28] Fetching coordinates for barcode --- .../ReactNativeScannerView.kt | 324 +++++++++--------- .../ReactNativeScannerViewEvent.kt | 59 +++- 2 files changed, 209 insertions(+), 174 deletions(-) diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt index 58b0f36..aa85db9 100644 --- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt +++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt @@ -4,6 +4,7 @@ import android.Manifest import android.annotation.SuppressLint import android.content.Context import android.content.pm.PackageManager +import android.util.Log import android.view.Choreographer import android.view.ViewGroup import android.widget.LinearLayout @@ -24,182 +25,191 @@ 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.Arrays import java.util.concurrent.ExecutorService import java.util.concurrent.Executors class ReactNativeScannerView(context: Context) : LinearLayout(context) { - private var preview: PreviewView - private var mCameraProvider: ProcessCameraProvider? = null - private lateinit var cameraExecutor: ExecutorService - private lateinit var options: BarcodeScannerOptions - private lateinit var scanner: BarcodeScanner - private var analysisUseCase: ImageAnalysis = ImageAnalysis.Builder() - .build() - - companion object { - private const val REQUEST_CODE_PERMISSIONS = 10 - private val REQUIRED_PERMISSIONS = - mutableListOf( - Manifest.permission.CAMERA - ).toTypedArray() - } - - init { - val linearLayoutParams = 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 - ) - addView(preview) - - setupLayoutHack() + private var preview: PreviewView + private var mCameraProvider: ProcessCameraProvider? = null + private lateinit var cameraExecutor: ExecutorService + private lateinit var options: BarcodeScannerOptions + private lateinit var scanner: BarcodeScanner + private var analysisUseCase: ImageAnalysis = ImageAnalysis.Builder() + .build() + + companion object { + private const val REQUEST_CODE_PERMISSIONS = 10 + private val REQUIRED_PERMISSIONS = + mutableListOf( + Manifest.permission.CAMERA + ).toTypedArray() + } + + init { + val linearLayoutParams = 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 + ) + addView(preview) + + setupLayoutHack() + manuallyLayoutChildren() + } + + private fun setupLayoutHack() { + Choreographer.getInstance().postFrameCallback(object : Choreographer.FrameCallback { + override fun doFrame(frameTimeNanos: Long) { manuallyLayoutChildren() + viewTreeObserver.dispatchOnGlobalLayout() + Choreographer.getInstance().postFrameCallback(this) + } + }) + } + + private fun manuallyLayoutChildren() { + for (i in 0 until childCount) { + val child = getChildAt(i) + child.measure( + MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY) + ) + child.layout(0, 0, child.measuredWidth, child.measuredHeight) } + } - private fun setupLayoutHack() { - Choreographer.getInstance().postFrameCallback(object : Choreographer.FrameCallback { - override fun doFrame(frameTimeNanos: Long) { - manuallyLayoutChildren() - viewTreeObserver.dispatchOnGlobalLayout() - Choreographer.getInstance().postFrameCallback(this) - } - }) + fun setUpCamera(reactApplicationContext: ReactApplicationContext) { + if (allPermissionsGranted()) { + startCamera(reactApplicationContext) } - private fun manuallyLayoutChildren() { - for (i in 0 until childCount) { - val child = getChildAt(i) - child.measure( - MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY) - ) - child.layout(0, 0, child.measuredWidth, child.measuredHeight) - } + cameraExecutor = Executors.newSingleThreadExecutor() + + options = BarcodeScannerOptions.Builder() + .setBarcodeFormats( + Barcode.FORMAT_QR_CODE, + Barcode.FORMAT_AZTEC, + Barcode.FORMAT_CODE_128, + Barcode.FORMAT_CODE_39, + Barcode.FORMAT_CODE_93, + Barcode.FORMAT_CODABAR, + Barcode.FORMAT_DATA_MATRIX, + Barcode.FORMAT_EAN_13, + Barcode.FORMAT_EAN_8, + Barcode.FORMAT_ITF, + Barcode.FORMAT_PDF417, + Barcode.FORMAT_UPC_A, + Barcode.FORMAT_UPC_E + ) + .build() + scanner = BarcodeScanning.getClient(options) + + analysisUseCase.setAnalyzer( + // newSingleThreadExecutor() will let us perform analysis on a single worker thread + Executors.newSingleThreadExecutor() + ) { imageProxy -> + processImageProxy(scanner, imageProxy) } + } + + @SuppressLint("UnsafeOptInUsageError") + private fun processImageProxy( + barcodeScanner: BarcodeScanner, + imageProxy: ImageProxy + ) { + imageProxy.image?.let { image -> + val inputImage = + InputImage.fromMediaImage( + image, + imageProxy.imageInfo.rotationDegrees + ) - fun setUpCamera(reactApplicationContext: ReactApplicationContext) { - if (allPermissionsGranted()) { - startCamera(reactApplicationContext) + barcodeScanner.process(inputImage) + .addOnSuccessListener { barcodeList -> + val barcode = + barcodeList.getOrNull(0) // `rawValue` is the decoded value of the barcode + + // Log.e("adasdasd", barcodeList.getOrNull(0)?.cornerPoints.v); + barcode?.let { value -> + // mCameraProvider?.unbindAll() // this line will stop the camera from scanning after the first scan + val reactContext = context as ReactContext + val surfaceId = UIManagerHelper.getSurfaceId(reactContext) + val eventDispatcher: EventDispatcher? = + UIManagerHelper.getEventDispatcherForReactTag( + reactContext, id + ) + eventDispatcher?.dispatchEvent(value.cornerPoints?.let { + value.boundingBox?.let { it1 -> + ReactNativeScannerViewEvent(surfaceId, id, it1,value.rawValue?:"", + it + ) + } + }) + } } - - cameraExecutor = Executors.newSingleThreadExecutor() - - options = BarcodeScannerOptions.Builder() - .setBarcodeFormats( - Barcode.FORMAT_QR_CODE, - Barcode.FORMAT_AZTEC, - Barcode.FORMAT_CODE_128, - Barcode.FORMAT_CODE_39, - Barcode.FORMAT_CODE_93, - Barcode.FORMAT_CODABAR, - Barcode.FORMAT_DATA_MATRIX, - Barcode.FORMAT_EAN_13, - Barcode.FORMAT_EAN_8, - Barcode.FORMAT_ITF, - Barcode.FORMAT_PDF417, - Barcode.FORMAT_UPC_A, - Barcode.FORMAT_UPC_E - ) - .build() - scanner = BarcodeScanning.getClient(options) - - analysisUseCase.setAnalyzer( - // newSingleThreadExecutor() will let us perform analysis on a single worker thread - Executors.newSingleThreadExecutor() - ) { imageProxy -> - processImageProxy(scanner, imageProxy) + .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() } } + } - @SuppressLint("UnsafeOptInUsageError") - private fun processImageProxy( - barcodeScanner: BarcodeScanner, - imageProxy: ImageProxy - ) { - imageProxy.image?.let { image -> - val inputImage = - InputImage.fromMediaImage( - image, - imageProxy.imageInfo.rotationDegrees - ) + private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all { + ContextCompat.checkSelfPermission( + context, it + ) == PackageManager.PERMISSION_GRANTED + } + + private fun startCamera(reactApplicationContext: ReactApplicationContext) { + + val cameraProviderFuture = ProcessCameraProvider.getInstance(context) - barcodeScanner.process(inputImage) - .addOnSuccessListener { barcodeList -> - val barcode = - barcodeList.getOrNull(0) // `rawValue` is the decoded value of the barcode - - barcode?.rawValue?.let { value -> - // mCameraProvider?.unbindAll() // this line will stop the camera from scanning after the first scan - val reactContext = context as ReactContext - val eventDispatcher: EventDispatcher? = - UIManagerHelper.getEventDispatcherForReactTag( - reactContext, id - ) - eventDispatcher?.dispatchEvent(ReactNativeScannerViewEvent(id, value)) - } - } - .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() - } + 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) } - } - private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all { - ContextCompat.checkSelfPermission( - context, it - ) == PackageManager.PERMISSION_GRANTED - } + // Select back camera as a default + val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA - 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) - } - - // Select back camera as a default - val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA - - try { - // Unbind use cases before rebinding - cameraProvider.unbindAll() - - // Bind use cases to camera - cameraProvider.bindToLifecycle( - (reactApplicationContext.currentActivity as AppCompatActivity), - cameraSelector, - surfacePreview, - analysisUseCase - ) + try { + // Unbind use cases before rebinding + cameraProvider.unbindAll() - } catch (exc: Exception) { - - } + // Bind use cases to camera + cameraProvider.bindToLifecycle( + (reactApplicationContext.currentActivity as AppCompatActivity), + cameraSelector, + surfacePreview, + analysisUseCase + ) - }, ContextCompat.getMainExecutor(context)) - } -} \ No newline at end of file + } catch (exc: Exception) { + + } + + }, ContextCompat.getMainExecutor(context)) + } +} diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewEvent.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewEvent.kt index 7e7e6c4..f344e8c 100644 --- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewEvent.kt +++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewEvent.kt @@ -1,30 +1,55 @@ 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.facebook.react.uimanager.events.RCTModernEventEmitter +class ReactNativeScannerViewEvent( + surfaceId: Int, + viewId: Int, + private val rect: Rect, + private val qrValue: String, + private val origin: Array +) : Event(surfaceId, viewId) { -class ReactNativeScannerViewEvent(viewId: Int, private val qrValue: String): Event(viewId) { + override fun getEventName(): String { + return "topOnQrScanned" + } - override fun getEventName(): String { - return "topOnQrScanned" - } + override fun dispatchModern(rctEventEmitter: RCTModernEventEmitter?) { + super.dispatchModern(rctEventEmitter) // if we don't call this, the react native part won't receive the event but because of this line event call two times + rctEventEmitter?.receiveEvent( + -1, + viewTag, eventName, + Arguments.createMap() + ) + } - override fun dispatchModern(rctEventEmitter: RCTModernEventEmitter?) { - super.dispatchModern(rctEventEmitter) // if we don't call this, the react native part won't receive the event but because of this line event call two times - rctEventEmitter?.receiveEvent( - -1, - viewTag, eventName, - Arguments.createMap() - ) - } + 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) + return event + } - override fun getEventData(): WritableMap { - val event: WritableMap = Arguments.createMap() - event.putString("value", qrValue) - return event + 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 + } -} \ No newline at end of file +} From 03cd5d1e72ebc1bd1da3abf526d59bd8db780a5c Mon Sep 17 00:00:00 2001 From: pushpender singh Date: Thu, 24 Aug 2023 07:36:45 -0400 Subject: [PATCH 02/28] Removing logs. --- .../reactnativescanner/ReactNativeScannerView.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt index aa85db9..dfd1365 100644 --- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt +++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt @@ -4,7 +4,6 @@ import android.Manifest import android.annotation.SuppressLint import android.content.Context import android.content.pm.PackageManager -import android.util.Log import android.view.Choreographer import android.view.ViewGroup import android.widget.LinearLayout @@ -137,8 +136,6 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { .addOnSuccessListener { barcodeList -> val barcode = barcodeList.getOrNull(0) // `rawValue` is the decoded value of the barcode - - // Log.e("adasdasd", barcodeList.getOrNull(0)?.cornerPoints.v); barcode?.let { value -> // mCameraProvider?.unbindAll() // this line will stop the camera from scanning after the first scan val reactContext = context as ReactContext From dd613fe663c7fd8af321f170fba5509e268beb18 Mon Sep 17 00:00:00 2001 From: Ravindra Gupta Date: Fri, 25 Aug 2023 10:24:06 +0530 Subject: [PATCH 03/28] Update Scanner Native Module to expose barcode bounds and corners --- ios/ReactNativeScannerView.mm | 142 +++++++++++-------- src/ReactNativeScannerViewNativeComponent.ts | 16 ++- 2 files changed, 99 insertions(+), 59 deletions(-) diff --git a/ios/ReactNativeScannerView.mm b/ios/ReactNativeScannerView.mm index 034c37e..0db8a2f 100644 --- a/ios/ReactNativeScannerView.mm +++ b/ios/ReactNativeScannerView.mm @@ -30,68 +30,95 @@ + (ComponentDescriptorProvider)componentDescriptorProvider - (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]); + 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 = [_output availableMetadataObjectTypes]; + + _prevLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session]; + + _prevLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; + [_view.layer addSublayer:_prevLayer]; + [_session startRunning]; + + self.contentView = _view; } - - _output = [[AVCaptureMetadataOutput alloc] init]; - [_output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()]; - [_session addOutput:_output]; - - _output.metadataObjectTypes = [_output availableMetadataObjectTypes]; - - _prevLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session]; - - _prevLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; - [_view.layer addSublayer:_prevLayer]; - [_session startRunning]; - - self.contentView = _view; - } - - return self; + + return self; } - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection { - CGRect highlightViewRect = CGRectZero; - AVMetadataMachineReadableCodeObject *barCodeObject; - NSString *detectionString = nil; - NSArray *barCodeTypes = @[AVMetadataObjectTypeUPCECode, AVMetadataObjectTypeCode39Code, AVMetadataObjectTypeCode39Mod43Code, - AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode93Code, AVMetadataObjectTypeCode128Code, - AVMetadataObjectTypePDF417Code, AVMetadataObjectTypeQRCode, AVMetadataObjectTypeAztecCode]; - - for (AVMetadataObject *metadata in metadataObjects) { - for (NSString *type in barCodeTypes) { - if ([metadata.type isEqualToString:type]) { - barCodeObject = (AVMetadataMachineReadableCodeObject *)[_prevLayer transformedMetadataObjectForMetadataObject:(AVMetadataMachineReadableCodeObject *)metadata]; - highlightViewRect = barCodeObject.bounds; - detectionString = [(AVMetadataMachineReadableCodeObject *)metadata stringValue]; - break; - } + if (_eventEmitter != nullptr) { + return; } - if (detectionString != nil) { - if (_eventEmitter != nullptr) { - std::dynamic_pointer_cast(_eventEmitter)->onQrScanned(facebook::react::ReactNativeScannerViewEventEmitter::OnQrScanned{ - .value = std::string([detectionString UTF8String]) - }); - } + + NSArray *barCodeTypes = @[AVMetadataObjectTypeUPCECode, AVMetadataObjectTypeCode39Code, AVMetadataObjectTypeCode39Mod43Code, + AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode93Code, AVMetadataObjectTypeCode128Code, + AVMetadataObjectTypePDF417Code, AVMetadataObjectTypeQRCode, AVMetadataObjectTypeAztecCode]; + + for (AVMetadataObject *metadata in metadataObjects) { + BOOL isValidCode = false; + for (NSString *type in barCodeTypes) { + if ([metadata.type isEqualToString:type]) { + isValidCode = true; + break; + } + } + + if (isValidCode == true) { + AVMetadataMachineReadableCodeObject *barCodeObject = (AVMetadataMachineReadableCodeObject *)[_prevLayer transformedMetadataObjectForMetadataObject:(AVMetadataMachineReadableCodeObject *)metadata]; + CGRect highlightViewRect = barCodeObject.bounds; + NSArray *corners = barCodeObject.corners; + NSString *codeString = [(AVMetadataMachineReadableCodeObject *)metadata stringValue]; + + CGPoint *topLeft = nullptr, *bottomLeft = nullptr, *bottomRight = nullptr, *topRight = nullptr; + + if(corners.count == 4) { + CGPointMakeWithDictionaryRepresentation((CFDictionaryRef)corners[0], topLeft); + CGPointMakeWithDictionaryRepresentation((CFDictionaryRef)corners[1], bottomLeft); + CGPointMakeWithDictionaryRepresentation((CFDictionaryRef)corners[2], bottomRight); + CGPointMakeWithDictionaryRepresentation((CFDictionaryRef)corners[3], topRight); + } + + facebook::react::ReactNativeScannerViewEventEmitter::OnQrScannedBounds bounds = { + .width = highlightViewRect.size.width, + .height = highlightViewRect.size.height, + .origin = { + .topLeft = {.x = topLeft->x, .y = topLeft->y}, + .bottomLeft = {.x = bottomLeft->x, .y = bottomLeft->y}, + .bottomRight = {.x = bottomRight->x, .y = bottomRight->y}, + .topRight = {.x = topRight->x, .y = topRight->y} + } + }; + + std::dynamic_pointer_cast(_eventEmitter)->onQrScanned(facebook::react::ReactNativeScannerViewEventEmitter::OnQrScanned{ + .bounds = bounds, + .type = std::string([metadata.type UTF8String]), + .data = std::string([codeString UTF8String]), + .target = std::int32_t([codeString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) + }); + } } - } } - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps @@ -100,8 +127,8 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & } - (void)updateLayoutMetrics:(const facebook::react::LayoutMetrics &)layoutMetrics oldLayoutMetrics:(const facebook::react::LayoutMetrics &)oldLayoutMetrics{ - [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics]; - _prevLayer.frame = [_view.layer bounds]; + [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics]; + _prevLayer.frame = [_view.layer bounds]; } @end @@ -110,3 +137,4 @@ - (void)updateLayoutMetrics:(const facebook::react::LayoutMetrics &)layoutMetric { return ReactNativeScannerView.class; } + diff --git a/src/ReactNativeScannerViewNativeComponent.ts b/src/ReactNativeScannerViewNativeComponent.ts index e94d37f..983f7fd 100644 --- a/src/ReactNativeScannerViewNativeComponent.ts +++ b/src/ReactNativeScannerViewNativeComponent.ts @@ -1,9 +1,21 @@ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; import type { ViewProps } from 'react-native'; -import type { DirectEventHandler } from 'react-native/Libraries/Types/CodegenTypes'; +import type { DirectEventHandler, Int32, Double } from 'react-native/Libraries/Types/CodegenTypes'; type Event = Readonly<{ - value: string; + bounds: Readonly<{ + width: Double, + height: Double, + origin: Readonly<{ + topLeft: Readonly<{ x: Double, y: Double }>; + bottomLeft: Readonly<{ x: Double, y: Double }>; + bottomRight: Readonly<{ x: Double, y: Double }>; + topRight: Readonly<{ x: Double, y: Double }>; + }> + }>; + type: string; + data: string; + target: Int32; }>; interface NativeProps extends ViewProps { From 1b58ffe5d139b92d1dbd94b89ed577b4e8c236e6 Mon Sep 17 00:00:00 2001 From: Ravindra Gupta Date: Fri, 25 Aug 2023 20:36:51 +0530 Subject: [PATCH 04/28] Minor changes --- .../reactnativescanner/ReactNativeScannerViewEvent.kt | 3 +-- .../reactnativescanner/ReactNativeScannerViewManager.kt | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewEvent.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewEvent.kt index f344e8c..03fb25e 100644 --- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewEvent.kt +++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewEvent.kt @@ -41,7 +41,7 @@ class ReactNativeScannerViewEvent( return event } - fun getPoints(points: Array): WritableArray { + private fun getPoints(points: Array): WritableArray { val origin: WritableArray = Arguments.createArray() for (point in points) { val pointData: WritableMap = Arguments.createMap() @@ -51,5 +51,4 @@ class ReactNativeScannerViewEvent( } 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 068b217..920c670 100644 --- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt +++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt @@ -28,9 +28,9 @@ class ReactNativeScannerViewManager(private val mCallerContext: ReactApplication } override fun createViewInstance(reactContext: ThemedReactContext): ReactNativeScannerView { - val reactnativeScannerView = ReactNativeScannerView(mCallerContext) - reactnativeScannerView.setUpCamera(mCallerContext) - return reactnativeScannerView + val scannerView = ReactNativeScannerView(mCallerContext) + scannerView.setUpCamera(mCallerContext) + return scannerView } companion object { From c41e5a5f192d961b062c820a8d5ac371ae982d96 Mon Sep 17 00:00:00 2001 From: Ravindra Gupta Date: Sun, 27 Aug 2023 17:47:30 +0530 Subject: [PATCH 05/28] Updated code to pass format of code --- .../ReactNativeScannerView.kt | 34 +++++++++---------- .../ReactNativeScannerViewEvent.kt | 22 ++++++------ 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt index dfd1365..4923912 100644 --- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt +++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt @@ -134,23 +134,23 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { barcodeScanner.process(inputImage) .addOnSuccessListener { barcodeList -> - val barcode = - barcodeList.getOrNull(0) // `rawValue` is the decoded value of the barcode - barcode?.let { value -> - // mCameraProvider?.unbindAll() // this line will stop the camera from scanning after the first scan - val reactContext = context as ReactContext - val surfaceId = UIManagerHelper.getSurfaceId(reactContext) - val eventDispatcher: EventDispatcher? = - UIManagerHelper.getEventDispatcherForReactTag( - reactContext, id - ) - eventDispatcher?.dispatchEvent(value.cornerPoints?.let { - value.boundingBox?.let { it1 -> - ReactNativeScannerViewEvent(surfaceId, id, it1,value.rawValue?:"", - it - ) - } - }) + // mCameraProvider?.unbindAll() // this line will stop the camera from scanning after the first scan + val reactContext = context as ReactContext + val surfaceId = UIManagerHelper.getSurfaceId(reactContext) + val eventDispatcher: EventDispatcher? = + UIManagerHelper.getEventDispatcherForReactTag( + reactContext, id + ) + + barcodeList.forEach { barcode -> + barcode?.let { code -> + eventDispatcher?.dispatchEvent(code.cornerPoints?.let { cornerPoints -> + code.boundingBox?.let { bounds -> + ReactNativeScannerViewEvent(surfaceId, id, code.rawValue + ?: "", bounds, cornerPoints, code.format) + } + }) + } } } .addOnFailureListener { diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewEvent.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewEvent.kt index 03fb25e..addec40 100644 --- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewEvent.kt +++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewEvent.kt @@ -7,28 +7,21 @@ import com.facebook.react.bridge.WritableArray import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.events.Event import com.facebook.react.uimanager.events.RCTModernEventEmitter +import com.google.mlkit.vision.barcode.common.Barcode class ReactNativeScannerViewEvent( surfaceId: Int, viewId: Int, - private val rect: Rect, private val qrValue: String, - private val origin: Array -) : Event(surfaceId, viewId) { + private val rect: Rect, + private val origin: Array, + private val type: Int + ) : Event(surfaceId, viewId) { override fun getEventName(): String { return "topOnQrScanned" } - override fun dispatchModern(rctEventEmitter: RCTModernEventEmitter?) { - super.dispatchModern(rctEventEmitter) // if we don't call this, the react native part won't receive the event but because of this line event call two times - rctEventEmitter?.receiveEvent( - -1, - viewTag, eventName, - Arguments.createMap() - ) - } - override fun getEventData(): WritableMap { val event: WritableMap = Arguments.createMap() val bounds = Arguments.createMap() @@ -38,6 +31,11 @@ class ReactNativeScannerViewEvent( 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 } From 0434afa0142059d9c4c31cbe813cd21039b05864 Mon Sep 17 00:00:00 2001 From: Ravindra Gupta Date: Mon, 28 Aug 2023 15:09:38 +0530 Subject: [PATCH 06/28] Fixed code issues in iOS --- ios/ReactNativeScannerView.mm | 39 +++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/ios/ReactNativeScannerView.mm b/ios/ReactNativeScannerView.mm index 0db8a2f..ddae44e 100644 --- a/ios/ReactNativeScannerView.mm +++ b/ios/ReactNativeScannerView.mm @@ -68,7 +68,7 @@ - (instancetype)initWithFrame:(CGRect)frame - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection { - if (_eventEmitter != nullptr) { + if (_eventEmitter == nullptr) { return; } @@ -91,23 +91,32 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects: NSArray *corners = barCodeObject.corners; NSString *codeString = [(AVMetadataMachineReadableCodeObject *)metadata stringValue]; - CGPoint *topLeft = nullptr, *bottomLeft = nullptr, *bottomRight = nullptr, *topRight = nullptr; + CGPoint topLeft, bottomLeft, bottomRight, topRight = CGPointMake(0, 0); - if(corners.count == 4) { - CGPointMakeWithDictionaryRepresentation((CFDictionaryRef)corners[0], topLeft); - CGPointMakeWithDictionaryRepresentation((CFDictionaryRef)corners[1], bottomLeft); - CGPointMakeWithDictionaryRepresentation((CFDictionaryRef)corners[2], bottomRight); - CGPointMakeWithDictionaryRepresentation((CFDictionaryRef)corners[3], topRight); + if (corners.count >= 0) { + topLeft = [self mapObject: corners[0]]; + } + + if (corners.count >= 1) { + bottomLeft = [self mapObject: corners[1]]; + } + + if (corners.count >= 2) { + bottomRight = [self mapObject: corners[2]]; + } + + if (corners.count >= 3) { + topRight = [self mapObject: corners[3]]; } facebook::react::ReactNativeScannerViewEventEmitter::OnQrScannedBounds bounds = { .width = highlightViewRect.size.width, .height = highlightViewRect.size.height, .origin = { - .topLeft = {.x = topLeft->x, .y = topLeft->y}, - .bottomLeft = {.x = bottomLeft->x, .y = bottomLeft->y}, - .bottomRight = {.x = bottomRight->x, .y = bottomRight->y}, - .topRight = {.x = topRight->x, .y = topRight->y} + .topLeft = {.x = topLeft.x, .y = topLeft.y}, + .bottomLeft = {.x = bottomLeft.x, .y = bottomLeft.y}, + .bottomRight = {.x = bottomRight.x, .y = bottomRight.y}, + .topRight = {.x = topRight.x, .y = topRight.y} } }; @@ -121,6 +130,14 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects: } } +- (CGPoint)mapObject:(NSDictionary *)object { + if (object == nil) { + return CGPointMake(0, 0); + } + + return CGPointMake([[object objectForKey:@"X"] doubleValue], [[object objectForKey:@"Y"] doubleValue]); +} + - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps { [super updateProps:props oldProps:oldProps]; From 269bb81ad8b3c36ac37324391eb97f3cd40f091f Mon Sep 17 00:00:00 2001 From: Ravindra Gupta Date: Mon, 28 Aug 2023 17:18:56 +0530 Subject: [PATCH 07/28] Added feature for - exposed property pause after capture - commands pausePreview/resumePreview --- ios/ReactNativeScannerView.mm | 33 +++++++++++++++++++- src/ReactNativeScannerViewNativeComponent.ts | 13 +++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/ios/ReactNativeScannerView.mm b/ios/ReactNativeScannerView.mm index ddae44e..f5facb7 100644 --- a/ios/ReactNativeScannerView.mm +++ b/ios/ReactNativeScannerView.mm @@ -10,7 +10,7 @@ using namespace facebook::react; @interface ReactNativeScannerView () - +@property Boolean pauseAfterCapture; @end @implementation ReactNativeScannerView { @@ -72,6 +72,10 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects: return; } + if (self.pauseAfterCapture == true) { + [[_prevLayer connection] setEnabled:NO]; + } + NSArray *barCodeTypes = @[AVMetadataObjectTypeUPCECode, AVMetadataObjectTypeCode39Code, AVMetadataObjectTypeCode39Mod43Code, AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode93Code, AVMetadataObjectTypeCode128Code, AVMetadataObjectTypePDF417Code, AVMetadataObjectTypeQRCode, AVMetadataObjectTypeAztecCode]; @@ -140,6 +144,21 @@ - (CGPoint)mapObject:(NSDictionary *)object { - (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); + +#define REMAP_WEBVIEW_PROP(name) \ + if (oldViewProps.name != newViewProps.name) { \ + self.name = newViewProps.name; \ + } + +#define REMAP_WEBVIEW_STRING_PROP(name) \ + if (oldViewProps.name != newViewProps.name) { \ + self.name = RCTNSStringFromString(newViewProps.name); \ + } + + REMAP_WEBVIEW_PROP(pauseAfterCapture) + [super updateProps:props oldProps:oldProps]; } @@ -148,6 +167,18 @@ - (void)updateLayoutMetrics:(const facebook::react::LayoutMetrics &)layoutMetric _prevLayer.frame = [_view.layer bounds]; } +- (void)handleCommand:(nonnull const NSString *)commandName args:(nonnull const NSArray *)args { + +} + +- (void)pausePreview { + [[_prevLayer connection] setEnabled:NO]; +} + +- (void)resumePreview { + [[_prevLayer connection] setEnabled:YES]; +} + @end Class ReactNativeScannerViewCls(void) diff --git a/src/ReactNativeScannerViewNativeComponent.ts b/src/ReactNativeScannerViewNativeComponent.ts index 983f7fd..5dfa42e 100644 --- a/src/ReactNativeScannerViewNativeComponent.ts +++ b/src/ReactNativeScannerViewNativeComponent.ts @@ -1,5 +1,6 @@ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; -import type { ViewProps } from 'react-native'; +import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands'; +import type { HostComponent, ViewProps } from 'react-native'; import type { DirectEventHandler, Int32, Double } from 'react-native/Libraries/Types/CodegenTypes'; type Event = Readonly<{ @@ -18,7 +19,17 @@ type Event = Readonly<{ target: Int32; }>; +export interface NativeCommands { + pausePreview: (viewRef: React.ElementRef>) => void; + resumePreview: (viewRef: React.ElementRef>) => void; +} + +export const Commands = codegenNativeCommands({ + supportedCommands: ['pausePreview', 'resumePreview'], +}); + interface NativeProps extends ViewProps { + pauseAfterCapture?: boolean, onQrScanned?: DirectEventHandler; // Event name should start with "on" } From 9dd2dcdd47a40a29cadb981841344b514bc8f9e5 Mon Sep 17 00:00:00 2001 From: Ravindra Gupta Date: Tue, 29 Aug 2023 11:35:22 +0530 Subject: [PATCH 08/28] Implemented Pause/Resume, pause after capture for Scanner on Android --- .../ReactNativeScannerView.kt | 26 +++++++++++++++++++ .../ReactNativeScannerViewManager.kt | 22 +++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt index 4923912..89c40f2 100644 --- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt +++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt @@ -31,12 +31,15 @@ import java.util.concurrent.Executors class ReactNativeScannerView(context: Context) : LinearLayout(context) { private var preview: PreviewView + private var mSurfacePreview: Preview? = null private var mCameraProvider: ProcessCameraProvider? = null private lateinit var cameraExecutor: ExecutorService private lateinit var options: BarcodeScannerOptions private lateinit var scanner: BarcodeScanner private var analysisUseCase: ImageAnalysis = ImageAnalysis.Builder() .build() + private var hasSetSurfaceProvider: Boolean = false + private var pauseAfterCapture: Boolean = false companion object { private const val REQUEST_CODE_PERMISSIONS = 10 @@ -116,6 +119,9 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { // newSingleThreadExecutor() will let us perform analysis on a single worker thread Executors.newSingleThreadExecutor() ) { imageProxy -> + if (pauseAfterCapture) { + pauseCamera() + } processImageProxy(scanner, imageProxy) } } @@ -187,6 +193,8 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { .also { it.setSurfaceProvider(preview.surfaceProvider) } + mSurfacePreview = surfacePreview + hasSetSurfaceProvider = true // Select back camera as a default val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA @@ -209,4 +217,22 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { }, ContextCompat.getMainExecutor(context)) } + + fun pauseAfterCapture(value: Boolean) { + pauseAfterCapture = value + } + + fun pauseCamera() { + if (hasSetSurfaceProvider) { + hasSetSurfaceProvider = false + mSurfacePreview?.setSurfaceProvider(null) + } + } + + fun resumeCamera() { + if (!hasSetSurfaceProvider) { + hasSetSurfaceProvider = true + mSurfacePreview?.setSurfaceProvider(preview.surfaceProvider) + } + } } diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt index 920c670..b2078aa 100644 --- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt +++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt @@ -1,13 +1,16 @@ package com.pushpendersingh.reactnativescanner import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableArray import com.facebook.react.common.MapBuilder import com.facebook.react.module.annotations.ReactModule import com.facebook.react.uimanager.SimpleViewManager import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.ViewManagerDelegate -import com.facebook.react.viewmanagers.ReactNativeScannerViewManagerInterface +import com.facebook.react.uimanager.annotations.ReactProp import com.facebook.react.viewmanagers.ReactNativeScannerViewManagerDelegate +import com.facebook.react.viewmanagers.ReactNativeScannerViewManagerInterface + @ReactModule(name = ReactNativeScannerViewManager.NAME) class ReactNativeScannerViewManager(private val mCallerContext: ReactApplicationContext) : @@ -37,6 +40,23 @@ class ReactNativeScannerViewManager(private val mCallerContext: ReactApplication const val NAME = "ReactNativeScannerView" } + @ReactProp(name = "pauseAfterCapture") + fun setPauseAfterCapture(view: ReactNativeScannerView?, value: Boolean) { + view?.pauseAfterCapture(value) + } + + override fun receiveCommand(root: ReactNativeScannerView, commandId: String?, args: ReadableArray?) { + when (commandId) { + "pauseCamera" -> root.pauseCamera() + "resumeCamera" -> root.resumeCamera() + else -> { + println("Unsupported Command") + } + } + + super.receiveCommand(root, commandId, args) + } + override fun getExportedCustomDirectEventTypeConstants(): Map { return MapBuilder.of( "topOnQrScanned", From cdbcbe4b0074114b2cd33ae14ac1fee6b63ab976 Mon Sep 17 00:00:00 2001 From: Ravindra Gupta Date: Tue, 29 Aug 2023 13:52:47 +0530 Subject: [PATCH 09/28] Minor code changes --- .../ReactNativeScannerView.kt | 6 ++-- .../ReactNativeScannerViewManager.kt | 32 ++++++++----------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt index 89c40f2..b16e721 100644 --- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt +++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt @@ -218,21 +218,21 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { }, ContextCompat.getMainExecutor(context)) } - fun pauseAfterCapture(value: Boolean) { + fun setPauseAfterCapture(value: Boolean) { pauseAfterCapture = value } fun pauseCamera() { if (hasSetSurfaceProvider) { hasSetSurfaceProvider = false - mSurfacePreview?.setSurfaceProvider(null) + //mSurfacePreview?.setSurfaceProvider(null) } } fun resumeCamera() { if (!hasSetSurfaceProvider) { hasSetSurfaceProvider = true - mSurfacePreview?.setSurfaceProvider(preview.surfaceProvider) + //mSurfacePreview?.setSurfaceProvider(preview.surfaceProvider) } } } diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt index b2078aa..f89668a 100644 --- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt +++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt @@ -1,7 +1,6 @@ package com.pushpendersingh.reactnativescanner import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReadableArray import com.facebook.react.common.MapBuilder import com.facebook.react.module.annotations.ReactModule import com.facebook.react.uimanager.SimpleViewManager @@ -11,7 +10,6 @@ import com.facebook.react.uimanager.annotations.ReactProp import com.facebook.react.viewmanagers.ReactNativeScannerViewManagerDelegate import com.facebook.react.viewmanagers.ReactNativeScannerViewManagerInterface - @ReactModule(name = ReactNativeScannerViewManager.NAME) class ReactNativeScannerViewManager(private val mCallerContext: ReactApplicationContext) : SimpleViewManager(), ReactNativeScannerViewManagerInterface { @@ -40,27 +38,23 @@ class ReactNativeScannerViewManager(private val mCallerContext: ReactApplication const val NAME = "ReactNativeScannerView" } - @ReactProp(name = "pauseAfterCapture") - fun setPauseAfterCapture(view: ReactNativeScannerView?, value: Boolean) { - view?.pauseAfterCapture(value) - } - - override fun receiveCommand(root: ReactNativeScannerView, commandId: String?, args: ReadableArray?) { - when (commandId) { - "pauseCamera" -> root.pauseCamera() - "resumeCamera" -> root.resumeCamera() - else -> { - println("Unsupported Command") - } - } - - super.receiveCommand(root, commandId, args) - } - override fun getExportedCustomDirectEventTypeConstants(): Map { return MapBuilder.of( "topOnQrScanned", MapBuilder.of("registrationName", "onQrScanned") ) } + + @ReactProp(name = "pauseAfterCapture") + override fun setPauseAfterCapture(view: ReactNativeScannerView?, value: Boolean) { + view?.setPauseAfterCapture(value) + } + + override fun pausePreview(view: ReactNativeScannerView?) { + view?.pauseCamera() + } + + override fun resumePreview(view: ReactNativeScannerView?) { + view?.resumeCamera() + } } From 4ae135e7d0e81b86919ac410869ca65ec0f8b04c Mon Sep 17 00:00:00 2001 From: Ravindra Gupta Date: Wed, 30 Aug 2023 15:01:36 +0530 Subject: [PATCH 10/28] Minor fixes for commands --- .../ReactNativeScannerView.kt | 51 ++++++++++--------- .../ReactNativeScannerViewManager.kt | 29 +++++++++-- src/ReactNativeScannerViewNativeComponent.ts | 14 ++--- 3 files changed, 60 insertions(+), 34 deletions(-) diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt index b16e721..0c6d117 100644 --- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt +++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt @@ -119,9 +119,6 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { // newSingleThreadExecutor() will let us perform analysis on a single worker thread Executors.newSingleThreadExecutor() ) { imageProxy -> - if (pauseAfterCapture) { - pauseCamera() - } processImageProxy(scanner, imageProxy) } } @@ -141,21 +138,28 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { barcodeScanner.process(inputImage) .addOnSuccessListener { barcodeList -> // mCameraProvider?.unbindAll() // this line will stop the camera from scanning after the first scan - val reactContext = context as ReactContext - val surfaceId = UIManagerHelper.getSurfaceId(reactContext) - val eventDispatcher: EventDispatcher? = - UIManagerHelper.getEventDispatcherForReactTag( - reactContext, id - ) - - barcodeList.forEach { barcode -> - barcode?.let { code -> - eventDispatcher?.dispatchEvent(code.cornerPoints?.let { cornerPoints -> - code.boundingBox?.let { bounds -> - ReactNativeScannerViewEvent(surfaceId, id, code.rawValue - ?: "", bounds, cornerPoints, code.format) - } - }) + + if (barcodeList.isNotEmpty()) { + if (pauseAfterCapture) { + pausePreview() + } + + val reactContext = context as ReactContext + val surfaceId = UIManagerHelper.getSurfaceId(reactContext) + val eventDispatcher: EventDispatcher? = + UIManagerHelper.getEventDispatcherForReactTag( + reactContext, id + ) + + barcodeList.forEach { barcode -> + barcode?.let { code -> + eventDispatcher?.dispatchEvent(code.cornerPoints?.let { cornerPoints -> + code.boundingBox?.let { bounds -> + ReactNativeScannerViewEvent(surfaceId, id, code.rawValue + ?: "", bounds, cornerPoints, code.format) + } + }) + } } } } @@ -180,7 +184,6 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { } private fun startCamera(reactApplicationContext: ReactApplicationContext) { - val cameraProviderFuture = ProcessCameraProvider.getInstance(context) cameraProviderFuture.addListener({ @@ -210,11 +213,9 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { surfacePreview, analysisUseCase ) - } catch (exc: Exception) { } - }, ContextCompat.getMainExecutor(context)) } @@ -222,17 +223,17 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { pauseAfterCapture = value } - fun pauseCamera() { + fun pausePreview() { if (hasSetSurfaceProvider) { hasSetSurfaceProvider = false - //mSurfacePreview?.setSurfaceProvider(null) + mSurfacePreview?.setSurfaceProvider(null) } } - fun resumeCamera() { + fun resumePreview() { if (!hasSetSurfaceProvider) { hasSetSurfaceProvider = true - //mSurfacePreview?.setSurfaceProvider(preview.surfaceProvider) + mSurfacePreview?.setSurfaceProvider(preview.surfaceProvider) } } } diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt index f89668a..6aef89b 100644 --- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt +++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt @@ -1,6 +1,7 @@ package com.pushpendersingh.reactnativescanner import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableArray import com.facebook.react.common.MapBuilder import com.facebook.react.module.annotations.ReactModule import com.facebook.react.uimanager.SimpleViewManager @@ -20,7 +21,7 @@ class ReactNativeScannerViewManager(private val mCallerContext: ReactApplication mDelegate = ReactNativeScannerViewManagerDelegate(this) } - override fun getDelegate(): ViewManagerDelegate? { + override fun getDelegate(): ViewManagerDelegate { return mDelegate } @@ -36,6 +37,9 @@ class ReactNativeScannerViewManager(private val mCallerContext: ReactApplication companion object { const val NAME = "ReactNativeScannerView" + + const val COMMAND_PAUSE_PREVIEW = 1 + const val COMMAND_RESUME_PREVIEW = 2 } override fun getExportedCustomDirectEventTypeConstants(): Map { @@ -45,16 +49,35 @@ class ReactNativeScannerViewManager(private val mCallerContext: ReactApplication ) } + override fun getCommandsMap(): MutableMap { + val map = mutableMapOf() + map["pausePreview"] = COMMAND_PAUSE_PREVIEW + map["resumePreview"] = COMMAND_RESUME_PREVIEW + return map + } + + override fun receiveCommand(root: ReactNativeScannerView, commandId: String?, args: ReadableArray?) { + when (commandId) { + "pausePreview" -> root.pausePreview() + "resumePreview" -> root.resumePreview() + else -> { + println("Unsupported Command") + } + } + + super.receiveCommand(root, commandId, args) + } + @ReactProp(name = "pauseAfterCapture") override fun setPauseAfterCapture(view: ReactNativeScannerView?, value: Boolean) { view?.setPauseAfterCapture(value) } override fun pausePreview(view: ReactNativeScannerView?) { - view?.pauseCamera() + view?.pausePreview() } override fun resumePreview(view: ReactNativeScannerView?) { - view?.resumeCamera() + view?.resumePreview() } } diff --git a/src/ReactNativeScannerViewNativeComponent.ts b/src/ReactNativeScannerViewNativeComponent.ts index 5dfa42e..eedd12a 100644 --- a/src/ReactNativeScannerViewNativeComponent.ts +++ b/src/ReactNativeScannerViewNativeComponent.ts @@ -19,6 +19,11 @@ type Event = Readonly<{ target: Int32; }>; +interface NativeProps extends ViewProps { + pauseAfterCapture?: boolean, + onQrScanned?: DirectEventHandler; // Event name should start with "on" +} + export interface NativeCommands { pausePreview: (viewRef: React.ElementRef>) => void; resumePreview: (viewRef: React.ElementRef>) => void; @@ -28,9 +33,6 @@ export const Commands = codegenNativeCommands({ supportedCommands: ['pausePreview', 'resumePreview'], }); -interface NativeProps extends ViewProps { - pauseAfterCapture?: boolean, - onQrScanned?: DirectEventHandler; // Event name should start with "on" -} - -export default codegenNativeComponent('ReactNativeScannerView'); +export default codegenNativeComponent( + 'ReactNativeScannerView' +) as HostComponent; From f9238d4e65ec98c9a601a2f24a24978591f444da Mon Sep 17 00:00:00 2001 From: Ravindra Gupta Date: Wed, 30 Aug 2023 20:00:52 +0530 Subject: [PATCH 11/28] Minor fixes in typescript file --- src/ReactNativeScannerViewNativeComponent.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/ReactNativeScannerViewNativeComponent.ts b/src/ReactNativeScannerViewNativeComponent.ts index eedd12a..7425219 100644 --- a/src/ReactNativeScannerViewNativeComponent.ts +++ b/src/ReactNativeScannerViewNativeComponent.ts @@ -24,15 +24,18 @@ interface NativeProps extends ViewProps { onQrScanned?: DirectEventHandler; // Event name should start with "on" } -export interface NativeCommands { - pausePreview: (viewRef: React.ElementRef>) => void; - resumePreview: (viewRef: React.ElementRef>) => void; +type ComponentType = HostComponent; + +interface NativeCommands { + pausePreview: (viewRef: React.ElementRef) => void; + resumePreview: (viewRef: React.ElementRef) => void; } -export const Commands = codegenNativeCommands({ - supportedCommands: ['pausePreview', 'resumePreview'], +export const Commands: NativeCommands = codegenNativeCommands({ + supportedCommands: [ + 'pausePreview', + 'resumePreview' + ], }); -export default codegenNativeComponent( - 'ReactNativeScannerView' -) as HostComponent; +export default codegenNativeComponent('ReactNativeScannerView', {}) From 8a1eef75c16478305b6a1c08a10e8b4bc95cf817 Mon Sep 17 00:00:00 2001 From: Ravindra Gupta Date: Fri, 1 Sep 2023 21:19:20 +0530 Subject: [PATCH 12/28] Exported component with Ref to support commands --- src/ReactNativeScannerViewNativeComponent.ts | 12 ++--- src/ScannerView.android.tsx | 3 ++ src/ScannerView.ios.tsx | 3 ++ src/ScannerView.tsx | 13 +++++ src/ScannerViewBase.tsx | 36 +++++++++++++ src/ScannerViewTypes.ts | 55 ++++++++++++++++++++ src/index.tsx | 6 ++- 7 files changed, 120 insertions(+), 8 deletions(-) create mode 100644 src/ScannerView.android.tsx create mode 100644 src/ScannerView.ios.tsx create mode 100644 src/ScannerView.tsx create mode 100644 src/ScannerViewBase.tsx create mode 100644 src/ScannerViewTypes.ts diff --git a/src/ReactNativeScannerViewNativeComponent.ts b/src/ReactNativeScannerViewNativeComponent.ts index 7425219..ba3ced2 100644 --- a/src/ReactNativeScannerViewNativeComponent.ts +++ b/src/ReactNativeScannerViewNativeComponent.ts @@ -19,16 +19,14 @@ type Event = Readonly<{ target: Int32; }>; -interface NativeProps extends ViewProps { +export interface NativeProps extends ViewProps { pauseAfterCapture?: boolean, onQrScanned?: DirectEventHandler; // Event name should start with "on" } -type ComponentType = HostComponent; - interface NativeCommands { - pausePreview: (viewRef: React.ElementRef) => void; - resumePreview: (viewRef: React.ElementRef) => void; + pausePreview: (viewRef: React.ElementRef>) => void; + resumePreview: (viewRef: React.ElementRef>) => void; } export const Commands: NativeCommands = codegenNativeCommands({ @@ -38,4 +36,6 @@ export const Commands: NativeCommands = codegenNativeCommands({ ], }); -export default codegenNativeComponent('ReactNativeScannerView', {}) +export default codegenNativeComponent( + 'ReactNativeScannerView', {} +) as HostComponent; diff --git a/src/ScannerView.android.tsx b/src/ScannerView.android.tsx new file mode 100644 index 0000000..f95b025 --- /dev/null +++ b/src/ScannerView.android.tsx @@ -0,0 +1,3 @@ +import ReactNativeScannerView from './ScannerViewBase' + +export default ReactNativeScannerView; diff --git a/src/ScannerView.ios.tsx b/src/ScannerView.ios.tsx new file mode 100644 index 0000000..f95b025 --- /dev/null +++ b/src/ScannerView.ios.tsx @@ -0,0 +1,3 @@ +import ReactNativeScannerView from './ScannerViewBase' + +export default ReactNativeScannerView; diff --git a/src/ScannerView.tsx b/src/ScannerView.tsx new file mode 100644 index 0000000..0b838f0 --- /dev/null +++ b/src/ScannerView.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { Text, View } from 'react-native'; +import type { ScannerViewProps } from "./ScannerViewTypes"; + +const ScannerViewComponent = React.FunctionComponent = () => ( + + + React Native Scanner View does not support this platform. + + +); + +export default ScannerViewComponent; \ No newline at end of file diff --git a/src/ScannerViewBase.tsx b/src/ScannerViewBase.tsx new file mode 100644 index 0000000..20d3927 --- /dev/null +++ b/src/ScannerViewBase.tsx @@ -0,0 +1,36 @@ +import React, { forwardRef, useCallback, useImperativeHandle, useRef } from 'react'; +import type { HostComponent } from 'react-native'; + +import ReactNativeScannerView, { Commands } from './ReactNativeScannerViewNativeComponent'; +import type { NativeProps } from './ReactNativeScannerViewNativeComponent'; +import type { ScannerViewProps, ScannerViewQRScanEvent } from "./ScannerViewTypes"; + +const ScannerViewComponent = forwardRef<{}, ScannerViewProps>(({ + pauseAfterCapture = false, + onQrScanned: onQrScannedProp, + ...otherProps +}, ref) => { + + const scannerViewRef = useRef> | null>(null); + + useImperativeHandle(ref, () => ({ + pausePreview: () => scannerViewRef.current && Commands.pausePreview(scannerViewRef.current), + resumePreview: () => scannerViewRef.current && Commands.resumePreview(scannerViewRef.current), + }), [scannerViewRef]) + + const onQrScanned = useCallback((event: ScannerViewQRScanEvent) => { + onQrScannedProp?.(event); + }, [onQrScannedProp]); + + const scannerView = + + return scannerView; +}); + +export default ScannerViewComponent; \ No newline at end of file diff --git a/src/ScannerViewTypes.ts b/src/ScannerViewTypes.ts new file mode 100644 index 0000000..4872c77 --- /dev/null +++ b/src/ScannerViewTypes.ts @@ -0,0 +1,55 @@ +import { + NativeSyntheticEvent, + ViewProps, + UIManagerStatic, +} from 'react-native'; + +type ScannerViewCommands = + | 'pausePreview' + | 'resumePreview'; + +interface RNScannerViewUIManager extends UIManagerStatic { + getViewManagerConfig: (name: string) => { + Commands: { [key in Commands]: number }; + }; +} + +export type ReactNativeScannerViewUIManager = RNScannerViewUIManager; + +export interface Point { + x: number; + y: number; +} + +export interface Origin { + topLeft: Point; + bottomLeft: Point; + bottomRight: Point; + topRight: Point; +} + +export interface Bounds { + width: number; + height: number; + origin: Origin | Point[]; +} + +export interface ScannerViewNativeEvent { + type: string; + data: string; + target: number; +} + +export interface ScannerViewQRScan extends ScannerViewNativeEvent { + bounds: Bounds; +} + +export type ScannerViewQRScanEvent = NativeSyntheticEvent; + +export interface ScannerViewSharedProps extends ViewProps { + pauseAfterCapture?: boolean; +} + +export interface ScannerViewProps extends ScannerViewSharedProps { + onQrScanned?: (event: ScannerViewQRScanEvent) => void; +} \ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx index 770fdf8..3cc67aa 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,2 +1,4 @@ -export { default as ReactNativeScannerView } from './ReactNativeScannerViewNativeComponent'; -export * from './ReactNativeScannerViewNativeComponent'; +import ReactNativeScannerView from './ScannerView'; + +export { ReactNativeScannerView }; +export default ReactNativeScannerView; From 37a3c3ac4bfc561c9a1209db90fab068eb923544 Mon Sep 17 00:00:00 2001 From: Ravindra Gupta Date: Sat, 2 Sep 2023 13:49:36 +0530 Subject: [PATCH 13/28] Update camera android code --- .../ReactNativeScannerView.kt | 66 ++++++++++++------- .../ReactNativeScannerViewManager.kt | 2 +- 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt index 0c6d117..9129023 100644 --- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt +++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt @@ -15,7 +15,6 @@ import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.view.PreviewView import androidx.core.content.ContextCompat -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 @@ -24,7 +23,6 @@ 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.Arrays import java.util.concurrent.ExecutorService import java.util.concurrent.Executors @@ -33,16 +31,16 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { private var preview: PreviewView private var mSurfacePreview: Preview? = null private var mCameraProvider: ProcessCameraProvider? = null - private lateinit var cameraExecutor: ExecutorService - private lateinit var options: BarcodeScannerOptions - private lateinit var scanner: BarcodeScanner private var analysisUseCase: ImageAnalysis = ImageAnalysis.Builder() .build() - private var hasSetSurfaceProvider: Boolean = false + + private lateinit var options: BarcodeScannerOptions + private lateinit var scanner: BarcodeScanner + + private var isCameraRunning: Boolean = false private var pauseAfterCapture: Boolean = false companion object { - private const val REQUEST_CODE_PERMISSIONS = 10 private val REQUIRED_PERMISSIONS = mutableListOf( Manifest.permission.CAMERA @@ -89,12 +87,13 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { } } - fun setUpCamera(reactApplicationContext: ReactApplicationContext) { + fun setUpCamera() { if (allPermissionsGranted()) { - startCamera(reactApplicationContext) + startCamera() } - cameraExecutor = Executors.newSingleThreadExecutor() + // newSingleThreadExecutor() will let us perform analysis on a single worker thread + val cameraExecutor = Executors.newSingleThreadExecutor() options = BarcodeScannerOptions.Builder() .setBarcodeFormats( @@ -116,8 +115,7 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { scanner = BarcodeScanning.getClient(options) analysisUseCase.setAnalyzer( - // newSingleThreadExecutor() will let us perform analysis on a single worker thread - Executors.newSingleThreadExecutor() + cameraExecutor ) { imageProxy -> processImageProxy(scanner, imageProxy) } @@ -135,6 +133,10 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { imageProxy.imageInfo.rotationDegrees ) + if (!isCameraRunning) { + return; + } + barcodeScanner.process(inputImage) .addOnSuccessListener { barcodeList -> // mCameraProvider?.unbindAll() // this line will stop the camera from scanning after the first scan @@ -183,13 +185,15 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { ) == PackageManager.PERMISSION_GRANTED } - private fun startCamera(reactApplicationContext: ReactApplicationContext) { - val cameraProviderFuture = ProcessCameraProvider.getInstance(context) + private fun startCamera() { + val reactContext = context as ReactContext + val cameraProviderFuture = ProcessCameraProvider.getInstance(reactContext) cameraProviderFuture.addListener({ // Used to bind the lifecycle of cameras to the lifecycle owner - val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() + val cameraProvider = cameraProviderFuture.get() mCameraProvider = cameraProvider + // Preview val surfacePreview = Preview.Builder() .build() @@ -197,24 +201,25 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { it.setSurfaceProvider(preview.surfaceProvider) } mSurfacePreview = surfacePreview - hasSetSurfaceProvider = true // Select back camera as a default val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA + isCameraRunning = true + try { // Unbind use cases before rebinding cameraProvider.unbindAll() // Bind use cases to camera cameraProvider.bindToLifecycle( - (reactApplicationContext.currentActivity as AppCompatActivity), + (reactContext.currentActivity as AppCompatActivity), cameraSelector, surfacePreview, analysisUseCase ) } catch (exc: Exception) { - + isCameraRunning = false } }, ContextCompat.getMainExecutor(context)) } @@ -224,16 +229,29 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { } fun pausePreview() { - if (hasSetSurfaceProvider) { - hasSetSurfaceProvider = false - mSurfacePreview?.setSurfaceProvider(null) + if (isCameraRunning) { + isCameraRunning = false + mCameraProvider?.unbind(analysisUseCase) } } fun resumePreview() { - if (!hasSetSurfaceProvider) { - hasSetSurfaceProvider = true - mSurfacePreview?.setSurfaceProvider(preview.surfaceProvider) + 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), + cameraSelector, + analysisUseCase + ) + } catch (exc: Exception) { + isCameraRunning = false + } } } } diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt index 6aef89b..e596d23 100644 --- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt +++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt @@ -31,7 +31,7 @@ class ReactNativeScannerViewManager(private val mCallerContext: ReactApplication override fun createViewInstance(reactContext: ThemedReactContext): ReactNativeScannerView { val scannerView = ReactNativeScannerView(mCallerContext) - scannerView.setUpCamera(mCallerContext) + scannerView.setUpCamera() return scannerView } From 1709c20778e89bdcac7bfcdf104cf021e0705191 Mon Sep 17 00:00:00 2001 From: Ravindra Gupta Date: Mon, 4 Sep 2023 17:19:13 +0530 Subject: [PATCH 14/28] Update ios code for pauseAfterCapture property --- ios/ReactNativeScannerView.mm | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/ios/ReactNativeScannerView.mm b/ios/ReactNativeScannerView.mm index f5facb7..b4b3bab 100644 --- a/ios/ReactNativeScannerView.mm +++ b/ios/ReactNativeScannerView.mm @@ -10,7 +10,6 @@ using namespace facebook::react; @interface ReactNativeScannerView () -@property Boolean pauseAfterCapture; @end @implementation ReactNativeScannerView { @@ -21,6 +20,8 @@ @implementation ReactNativeScannerView { AVCaptureDeviceInput *_input; AVCaptureMetadataOutput *_output; AVCaptureVideoPreviewLayer *_prevLayer; + + bool pauseAfterCapture; } + (ComponentDescriptorProvider)componentDescriptorProvider @@ -72,7 +73,7 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects: return; } - if (self.pauseAfterCapture == true) { + if (pauseAfterCapture == true) { [[_prevLayer connection] setEnabled:NO]; } @@ -146,18 +147,8 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & { const auto &oldViewProps = *std::static_pointer_cast(_props); const auto &newViewProps = *std::static_pointer_cast(props); - -#define REMAP_WEBVIEW_PROP(name) \ - if (oldViewProps.name != newViewProps.name) { \ - self.name = newViewProps.name; \ - } - -#define REMAP_WEBVIEW_STRING_PROP(name) \ - if (oldViewProps.name != newViewProps.name) { \ - self.name = RCTNSStringFromString(newViewProps.name); \ - } - - REMAP_WEBVIEW_PROP(pauseAfterCapture) + + pauseAfterCapture = newViewProps.pauseAfterCapture; [super updateProps:props oldProps:oldProps]; } From ea1a682ee023e49362becb22df44501c86ed44fc Mon Sep 17 00:00:00 2001 From: Ravindra Gupta Date: Mon, 4 Sep 2023 17:40:00 +0530 Subject: [PATCH 15/28] Minor fixes --- src/ScannerViewTypes.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ScannerViewTypes.ts b/src/ScannerViewTypes.ts index 4872c77..8cf281a 100644 --- a/src/ScannerViewTypes.ts +++ b/src/ScannerViewTypes.ts @@ -46,10 +46,7 @@ export interface ScannerViewQRScan extends ScannerViewNativeEvent { export type ScannerViewQRScanEvent = NativeSyntheticEvent; -export interface ScannerViewSharedProps extends ViewProps { +export interface ScannerViewProps extends ViewProps { pauseAfterCapture?: boolean; -} - -export interface ScannerViewProps extends ScannerViewSharedProps { onQrScanned?: (event: ScannerViewQRScanEvent) => void; } \ No newline at end of file From d6738971e6e1e454c07cfdc430ab0dd39d8bac82 Mon Sep 17 00:00:00 2001 From: Ravindra Gupta Date: Mon, 4 Sep 2023 19:30:40 +0530 Subject: [PATCH 16/28] Fixed issues on iOS for property and commands --- ios/ReactNativeScannerView.mm | 6 +++--- ios/ReactNativeScannerViewManager.mm | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ios/ReactNativeScannerView.mm b/ios/ReactNativeScannerView.mm index b4b3bab..e7f648e 100644 --- a/ios/ReactNativeScannerView.mm +++ b/ios/ReactNativeScannerView.mm @@ -21,7 +21,7 @@ @implementation ReactNativeScannerView { AVCaptureMetadataOutput *_output; AVCaptureVideoPreviewLayer *_prevLayer; - bool pauseAfterCapture; + BOOL pauseAfterCapture; } + (ComponentDescriptorProvider)componentDescriptorProvider @@ -73,7 +73,7 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects: return; } - if (pauseAfterCapture == true) { + if (pauseAfterCapture == YES) { [[_prevLayer connection] setEnabled:NO]; } @@ -159,7 +159,7 @@ - (void)updateLayoutMetrics:(const facebook::react::LayoutMetrics &)layoutMetric } - (void)handleCommand:(nonnull const NSString *)commandName args:(nonnull const NSArray *)args { - + RCTReactNativeScannerViewHandleCommand(self, commandName, args); } - (void)pausePreview { diff --git a/ios/ReactNativeScannerViewManager.mm b/ios/ReactNativeScannerViewManager.mm index c76fe90..74f37e4 100644 --- a/ios/ReactNativeScannerViewManager.mm +++ b/ios/ReactNativeScannerViewManager.mm @@ -11,5 +11,6 @@ @implementation ReactNativeScannerViewManager RCT_EXPORT_MODULE(ReactNativeScannerView) RCT_EXPORT_VIEW_PROPERTY(onQrScanned, RCTDirectEventBlock) +RCT_EXPORT_VIEW_PROPERTY(pauseAfterCapture, BOOL) @end From cba6d0d615def91d9ac0b49a2b49f02d1fb57b66 Mon Sep 17 00:00:00 2001 From: Ravindra Gupta Date: Tue, 21 Nov 2023 02:06:57 -0500 Subject: [PATCH 17/28] Improved camera handling --- ios/ReactNativeScannerView.mm | 128 ++++++++++++++++++++-------------- 1 file changed, 76 insertions(+), 52 deletions(-) diff --git a/ios/ReactNativeScannerView.mm b/ios/ReactNativeScannerView.mm index e7f648e..41efdc6 100644 --- a/ios/ReactNativeScannerView.mm +++ b/ios/ReactNativeScannerView.mm @@ -14,7 +14,7 @@ @interface ReactNativeScannerView () @implementation ReactNativeScannerView { UIView * _view; - + AVCaptureSession *_session; AVCaptureDevice *_device; AVCaptureDeviceInput *_input; @@ -24,6 +24,20 @@ @implementation ReactNativeScannerView { BOOL pauseAfterCapture; } ++ (NSArray *)metadataObjectTypes +{ + return @[AVMetadataObjectTypeUPCECode, + AVMetadataObjectTypeCode39Code, + AVMetadataObjectTypeCode39Mod43Code, + AVMetadataObjectTypeEAN13Code, + AVMetadataObjectTypeEAN8Code, + AVMetadataObjectTypeCode93Code, + AVMetadataObjectTypeCode128Code, + AVMetadataObjectTypePDF417Code, + AVMetadataObjectTypeQRCode, + AVMetadataObjectTypeAztecCode]; +} + + (ComponentDescriptorProvider)componentDescriptorProvider { return concreteComponentDescriptorProvider(); @@ -53,7 +67,7 @@ - (instancetype)initWithFrame:(CGRect)frame [_output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()]; [_session addOutput:_output]; - _output.metadataObjectTypes = [_output availableMetadataObjectTypes]; + _output.metadataObjectTypes = [ReactNativeScannerView metadataObjectTypes]; _prevLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session]; @@ -73,13 +87,8 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects: return; } - if (pauseAfterCapture == YES) { - [[_prevLayer connection] setEnabled:NO]; - } - - NSArray *barCodeTypes = @[AVMetadataObjectTypeUPCECode, AVMetadataObjectTypeCode39Code, AVMetadataObjectTypeCode39Mod43Code, - AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode93Code, AVMetadataObjectTypeCode128Code, - AVMetadataObjectTypePDF417Code, AVMetadataObjectTypeQRCode, AVMetadataObjectTypeAztecCode]; + NSMutableArray *validBarCodes = [[NSMutableArray alloc] init]; + NSArray *barCodeTypes = [ReactNativeScannerView metadataObjectTypes]; for (AVMetadataObject *metadata in metadataObjects) { BOOL isValidCode = false; @@ -90,49 +99,60 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects: } } - if (isValidCode == true) { - AVMetadataMachineReadableCodeObject *barCodeObject = (AVMetadataMachineReadableCodeObject *)[_prevLayer transformedMetadataObjectForMetadataObject:(AVMetadataMachineReadableCodeObject *)metadata]; - CGRect highlightViewRect = barCodeObject.bounds; - NSArray *corners = barCodeObject.corners; - NSString *codeString = [(AVMetadataMachineReadableCodeObject *)metadata stringValue]; - - CGPoint topLeft, bottomLeft, bottomRight, topRight = CGPointMake(0, 0); - - if (corners.count >= 0) { - topLeft = [self mapObject: corners[0]]; - } - - if (corners.count >= 1) { - bottomLeft = [self mapObject: corners[1]]; - } - - if (corners.count >= 2) { - bottomRight = [self mapObject: corners[2]]; - } - - if (corners.count >= 3) { - topRight = [self mapObject: corners[3]]; - } - - facebook::react::ReactNativeScannerViewEventEmitter::OnQrScannedBounds bounds = { - .width = highlightViewRect.size.width, - .height = highlightViewRect.size.height, - .origin = { - .topLeft = {.x = topLeft.x, .y = topLeft.y}, - .bottomLeft = {.x = bottomLeft.x, .y = bottomLeft.y}, - .bottomRight = {.x = bottomRight.x, .y = bottomRight.y}, - .topRight = {.x = topRight.x, .y = topRight.y} - } - }; - - std::dynamic_pointer_cast(_eventEmitter)->onQrScanned(facebook::react::ReactNativeScannerViewEventEmitter::OnQrScanned{ - .bounds = bounds, - .type = std::string([metadata.type UTF8String]), - .data = std::string([codeString UTF8String]), - .target = std::int32_t([codeString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) - }); + if (isValidCode == true) { + [validBarCodes addObject:metadata]; } } + + // pauseAfterCapture: + // * Pause AVCaptureSession for further processing, after valid barcodes found, + // * Can be resumed back by calling resumePreview from the owner of the component + if (pauseAfterCapture == YES && validBarCodes.count > 0) { + [self pausePreview]; + } + + for (AVMetadataObject *metadata in validBarCodes) { + AVMetadataMachineReadableCodeObject *barCodeObject = (AVMetadataMachineReadableCodeObject *)[_prevLayer transformedMetadataObjectForMetadataObject:(AVMetadataMachineReadableCodeObject *)metadata]; + CGRect highlightViewRect = barCodeObject.bounds; + NSArray *corners = barCodeObject.corners; + NSString *codeString = [(AVMetadataMachineReadableCodeObject *)metadata stringValue]; + + CGPoint topLeft, bottomLeft, bottomRight, topRight = CGPointMake(0, 0); + + if (corners.count >= 0) { + topLeft = [self mapObject: corners[0]]; + } + + if (corners.count >= 1) { + bottomLeft = [self mapObject: corners[1]]; + } + + if (corners.count >= 2) { + bottomRight = [self mapObject: corners[2]]; + } + + if (corners.count >= 3) { + topRight = [self mapObject: corners[3]]; + } + + facebook::react::ReactNativeScannerViewEventEmitter::OnQrScannedBounds bounds = { + .width = highlightViewRect.size.width, + .height = highlightViewRect.size.height, + .origin = { + .topLeft = {.x = topLeft.x, .y = topLeft.y}, + .bottomLeft = {.x = bottomLeft.x, .y = bottomLeft.y}, + .bottomRight = {.x = bottomRight.x, .y = bottomRight.y}, + .topRight = {.x = topRight.x, .y = topRight.y} + } + }; + + std::dynamic_pointer_cast(_eventEmitter)->onQrScanned(facebook::react::ReactNativeScannerViewEventEmitter::OnQrScanned{ + .bounds = bounds, + .type = std::string([metadata.type UTF8String]), + .data = std::string([codeString UTF8String]), + .target = std::int32_t([codeString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) + }); + } } - (CGPoint)mapObject:(NSDictionary *)object { @@ -163,11 +183,15 @@ - (void)handleCommand:(nonnull const NSString *)commandName args:(nonnull const } - (void)pausePreview { - [[_prevLayer connection] setEnabled:NO]; + if ([[_prevLayer connection] isEnabled]) { + [[_prevLayer connection] setEnabled:NO]; + } } - (void)resumePreview { - [[_prevLayer connection] setEnabled:YES]; + if (![[_prevLayer connection] isEnabled]) { + [[_prevLayer connection] setEnabled:YES]; + } } @end From 1fc7da752a0c97b4b03ead86e6e188e4ea71d385 Mon Sep 17 00:00:00 2001 From: Ravindra Gupta Date: Tue, 21 Nov 2023 06:57:25 -0500 Subject: [PATCH 18/28] Added isActive property to control session state --- .../ReactNativeScannerView.kt | 5 ++++ .../ReactNativeScannerViewManager.kt | 5 ++++ ios/ReactNativeScannerView.mm | 25 ++++++++++++++++--- ios/ReactNativeScannerViewManager.mm | 1 + src/ReactNativeScannerViewNativeComponent.ts | 1 + src/ScannerViewBase.tsx | 2 ++ src/ScannerViewTypes.ts | 1 + 7 files changed, 36 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt index 9129023..038a65b 100644 --- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt +++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt @@ -39,6 +39,7 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { private var isCameraRunning: Boolean = false private var pauseAfterCapture: Boolean = false + private var isActive: Boolean = false companion object { private val REQUIRED_PERMISSIONS = @@ -228,6 +229,10 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { pauseAfterCapture = value } + fun setIsActive(value: Boolean) { + isActive = value + } + fun pausePreview() { if (isCameraRunning) { isCameraRunning = false diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt index e596d23..e42ecb6 100644 --- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt +++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt @@ -73,6 +73,11 @@ class ReactNativeScannerViewManager(private val mCallerContext: ReactApplication view?.setPauseAfterCapture(value) } + @ReactProp(name = "isActive") + override fun setIsActive(view: ReactNativeScannerView?, value: Boolean) { + view?.setIsActive(value) + } + override fun pausePreview(view: ReactNativeScannerView?) { view?.pausePreview() } diff --git a/ios/ReactNativeScannerView.mm b/ios/ReactNativeScannerView.mm index 41efdc6..c488495 100644 --- a/ios/ReactNativeScannerView.mm +++ b/ios/ReactNativeScannerView.mm @@ -22,6 +22,7 @@ @implementation ReactNativeScannerView { AVCaptureVideoPreviewLayer *_prevLayer; BOOL pauseAfterCapture; + BOOL isActive; } + (NSArray *)metadataObjectTypes @@ -50,12 +51,10 @@ - (instancetype)initWithFrame:(CGRect)frame _props = defaultProps; _view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; - - _session = [[AVCaptureSession alloc] init]; _device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; - NSError *error = nil; + NSError *error = nil; _input = [AVCaptureDeviceInput deviceInputWithDevice:_device error:&error]; if (_input) { [_session addInput:_input]; @@ -70,7 +69,6 @@ - (instancetype)initWithFrame:(CGRect)frame _output.metadataObjectTypes = [ReactNativeScannerView metadataObjectTypes]; _prevLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session]; - _prevLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; [_view.layer addSublayer:_prevLayer]; [_session startRunning]; @@ -163,12 +161,31 @@ - (CGPoint)mapObject:(NSDictionary *)object { return CGPointMake([[object objectForKey:@"X"] doubleValue], [[object objectForKey:@"Y"] doubleValue]); } +- (void)checkIsActive { + if (isActive == _session.isRunning) { + return; + } + + // Start/Stop session + if (isActive) { + [_session startRunning]; + } else { + [captureSession stopRunning]; + } + } + - (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; + + if (isActive != newViewProps.isActive) { + isActive = newViewProps.pauseAfterCapture; + + [self checkIsActive]; + } [super updateProps:props oldProps:oldProps]; } diff --git a/ios/ReactNativeScannerViewManager.mm b/ios/ReactNativeScannerViewManager.mm index 74f37e4..8d5f603 100644 --- a/ios/ReactNativeScannerViewManager.mm +++ b/ios/ReactNativeScannerViewManager.mm @@ -12,5 +12,6 @@ @implementation ReactNativeScannerViewManager RCT_EXPORT_VIEW_PROPERTY(onQrScanned, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(pauseAfterCapture, BOOL) +RCT_EXPORT_VIEW_PROPERTY(isActive, BOOL) @end diff --git a/src/ReactNativeScannerViewNativeComponent.ts b/src/ReactNativeScannerViewNativeComponent.ts index ba3ced2..7c1c37d 100644 --- a/src/ReactNativeScannerViewNativeComponent.ts +++ b/src/ReactNativeScannerViewNativeComponent.ts @@ -21,6 +21,7 @@ type Event = Readonly<{ export interface NativeProps extends ViewProps { pauseAfterCapture?: boolean, + isActive?: boolean, onQrScanned?: DirectEventHandler; // Event name should start with "on" } diff --git a/src/ScannerViewBase.tsx b/src/ScannerViewBase.tsx index 20d3927..0a33589 100644 --- a/src/ScannerViewBase.tsx +++ b/src/ScannerViewBase.tsx @@ -7,6 +7,7 @@ import type { ScannerViewProps, ScannerViewQRScanEvent } from "./ScannerViewType const ScannerViewComponent = forwardRef<{}, ScannerViewProps>(({ pauseAfterCapture = false, + isActive = false, onQrScanned: onQrScannedProp, ...otherProps }, ref) => { @@ -27,6 +28,7 @@ const ScannerViewComponent = forwardRef<{}, ScannerViewProps>(({ key="scannerViewKey" ref={scannerViewRef} pauseAfterCapture={pauseAfterCapture} + isActive={isActive} onQrScanned={onQrScanned} /> diff --git a/src/ScannerViewTypes.ts b/src/ScannerViewTypes.ts index 8cf281a..7e6e42c 100644 --- a/src/ScannerViewTypes.ts +++ b/src/ScannerViewTypes.ts @@ -48,5 +48,6 @@ export type ScannerViewQRScanEvent = NativeSyntheticEvent; export interface ScannerViewProps extends ViewProps { pauseAfterCapture?: boolean; + isActive?: boolean; onQrScanned?: (event: ScannerViewQRScanEvent) => void; } \ No newline at end of file From 865b487c2f0f2cc2db5dab4a45b361333a12ba0b Mon Sep 17 00:00:00 2001 From: Ravindra Gupta Date: Tue, 21 Nov 2023 07:15:13 -0500 Subject: [PATCH 19/28] Minor fixes in iOS --- ios/ReactNativeScannerView.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/ReactNativeScannerView.mm b/ios/ReactNativeScannerView.mm index c488495..5e4c879 100644 --- a/ios/ReactNativeScannerView.mm +++ b/ios/ReactNativeScannerView.mm @@ -170,9 +170,9 @@ - (void)checkIsActive { if (isActive) { [_session startRunning]; } else { - [captureSession stopRunning]; + [_session stopRunning]; } - } +} - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps { From 4791a366187919daa7f375c76876346544de1298 Mon Sep 17 00:00:00 2001 From: Ravindra Gupta Date: Wed, 22 Nov 2023 03:26:03 -0500 Subject: [PATCH 20/28] Added startScanning, stopScanning commands to the scanner module --- .../ReactNativeScannerView.kt | 12 ++++++++ .../ReactNativeScannerViewManager.kt | 14 ++++++++++ ios/ReactNativeScannerView.mm | 28 +++++++++++-------- src/ReactNativeScannerViewNativeComponent.ts | 6 +++- src/ScannerViewBase.tsx | 2 ++ src/ScannerViewTypes.ts | 4 ++- 6 files changed, 53 insertions(+), 13 deletions(-) diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt index 038a65b..c4ef681 100644 --- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt +++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt @@ -225,6 +225,10 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { }, ContextCompat.getMainExecutor(context)) } + private fun stopCamera() { + + } + fun setPauseAfterCapture(value: Boolean) { pauseAfterCapture = value } @@ -259,4 +263,12 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { } } } + + fun startScanning() { + setIsActive(true) + } + + fun stopScanning() { + setIsActive(false) + } } diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt index e42ecb6..8614548 100644 --- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt +++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt @@ -40,6 +40,8 @@ class ReactNativeScannerViewManager(private val mCallerContext: ReactApplication const val COMMAND_PAUSE_PREVIEW = 1 const val COMMAND_RESUME_PREVIEW = 2 + const val COMMAND_START_SCANNING = 3 + const val COMMAND_STOP_SCANNING = 4 } override fun getExportedCustomDirectEventTypeConstants(): Map { @@ -53,6 +55,8 @@ class ReactNativeScannerViewManager(private val mCallerContext: ReactApplication val map = mutableMapOf() map["pausePreview"] = COMMAND_PAUSE_PREVIEW map["resumePreview"] = COMMAND_RESUME_PREVIEW + map["startScanning"] = COMMAND_START_SCANNING + map["stopScanning"] = COMMAND_STOP_SCANNING return map } @@ -60,6 +64,8 @@ class ReactNativeScannerViewManager(private val mCallerContext: ReactApplication when (commandId) { "pausePreview" -> root.pausePreview() "resumePreview" -> root.resumePreview() + "startScanning" -> root.startScanning() + "stopScanning" -> root.stopScanning() else -> { println("Unsupported Command") } @@ -85,4 +91,12 @@ class ReactNativeScannerViewManager(private val mCallerContext: ReactApplication override fun resumePreview(view: ReactNativeScannerView?) { view?.resumePreview() } + + override fun startScanning(view: ReactNativeScannerView?) { + view?.startScanning() + } + + override fun stopScanning(view: ReactNativeScannerView?) { + view?.stopScanning() + } } diff --git a/ios/ReactNativeScannerView.mm b/ios/ReactNativeScannerView.mm index 5e4c879..6f979b0 100644 --- a/ios/ReactNativeScannerView.mm +++ b/ios/ReactNativeScannerView.mm @@ -89,15 +89,15 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects: NSArray *barCodeTypes = [ReactNativeScannerView metadataObjectTypes]; for (AVMetadataObject *metadata in metadataObjects) { - BOOL isValidCode = false; + BOOL isValidCode = NO; for (NSString *type in barCodeTypes) { if ([metadata.type isEqualToString:type]) { - isValidCode = true; + isValidCode = YES; break; } } - if (isValidCode == true) { + if (isValidCode == YES) { [validBarCodes addObject:metadata]; } } @@ -161,16 +161,19 @@ - (CGPoint)mapObject:(NSDictionary *)object { return CGPointMake([[object objectForKey:@"X"] doubleValue], [[object objectForKey:@"Y"] doubleValue]); } -- (void)checkIsActive { - if (isActive == _session.isRunning) { +- (void)setIsActive:(BOOL)active { + if (active == _session.isRunning) { return; } // Start/Stop session + isActive = active; if (isActive) { [_session startRunning]; + [self resumePreview]; } else { [_session stopRunning]; + [self pausePreview]; } } @@ -180,12 +183,7 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & const auto &newViewProps = *std::static_pointer_cast(props); pauseAfterCapture = newViewProps.pauseAfterCapture; - - if (isActive != newViewProps.isActive) { - isActive = newViewProps.pauseAfterCapture; - - [self checkIsActive]; - } + [self setIsActive:newViewProps.isActive]; [super updateProps:props oldProps:oldProps]; } @@ -211,6 +209,14 @@ - (void)resumePreview { } } +- (void)startScanning { + [self setIsActive:YES]; +} + +- (void)stopScanning { + [self setIsActive:NO]; +} + @end Class ReactNativeScannerViewCls(void) diff --git a/src/ReactNativeScannerViewNativeComponent.ts b/src/ReactNativeScannerViewNativeComponent.ts index 7c1c37d..44d14ab 100644 --- a/src/ReactNativeScannerViewNativeComponent.ts +++ b/src/ReactNativeScannerViewNativeComponent.ts @@ -28,12 +28,16 @@ export interface NativeProps extends ViewProps { interface NativeCommands { pausePreview: (viewRef: React.ElementRef>) => void; resumePreview: (viewRef: React.ElementRef>) => void; + startScanning: (viewRef: React.ElementRef>) => void; + stopScanning: (viewRef: React.ElementRef>) => void; } export const Commands: NativeCommands = codegenNativeCommands({ supportedCommands: [ 'pausePreview', - 'resumePreview' + 'resumePreview', + 'startScanning', + 'stopScanning' ], }); diff --git a/src/ScannerViewBase.tsx b/src/ScannerViewBase.tsx index 0a33589..a6841c3 100644 --- a/src/ScannerViewBase.tsx +++ b/src/ScannerViewBase.tsx @@ -17,6 +17,8 @@ const ScannerViewComponent = forwardRef<{}, ScannerViewProps>(({ useImperativeHandle(ref, () => ({ pausePreview: () => scannerViewRef.current && Commands.pausePreview(scannerViewRef.current), resumePreview: () => scannerViewRef.current && Commands.resumePreview(scannerViewRef.current), + startScanning: () => scannerViewRef.current && Commands.startScanning(scannerViewRef.current), + stopScanning: () => scannerViewRef.current && Commands.stopScanning(scannerViewRef.current), }), [scannerViewRef]) const onQrScanned = useCallback((event: ScannerViewQRScanEvent) => { diff --git a/src/ScannerViewTypes.ts b/src/ScannerViewTypes.ts index 7e6e42c..86179e1 100644 --- a/src/ScannerViewTypes.ts +++ b/src/ScannerViewTypes.ts @@ -6,7 +6,9 @@ import { type ScannerViewCommands = | 'pausePreview' - | 'resumePreview'; + | 'resumePreview' + | 'startScanning' + | 'stopScanning'; interface RNScannerViewUIManager extends UIManagerStatic { getViewManagerConfig: (name: string) => { From d6ae3ed74877a702fce1e7105948282503ce748a Mon Sep 17 00:00:00 2001 From: Ravindra Gupta Date: Wed, 22 Nov 2023 09:42:28 -0500 Subject: [PATCH 21/28] Updated isActive handling to resume/pause preview --- ios/ReactNativeScannerView.mm | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/ios/ReactNativeScannerView.mm b/ios/ReactNativeScannerView.mm index 6f979b0..858a404 100644 --- a/ios/ReactNativeScannerView.mm +++ b/ios/ReactNativeScannerView.mm @@ -162,18 +162,23 @@ - (CGPoint)mapObject:(NSDictionary *)object { } - (void)setIsActive:(BOOL)active { - if (active == _session.isRunning) { - return; + isActive = active; + + // Enable/Disable Preview Layer + if (isActive) { + [self resumePreview]; + } else { + [self pausePreview]; } + if (isActive == _session.isRunning) { + return; + } // Start/Stop session - isActive = active; if (isActive) { [_session startRunning]; - [self resumePreview]; } else { [_session stopRunning]; - [self pausePreview]; } } From accd48be3cb8b6e8ece3f15165acc14fb1dda97d Mon Sep 17 00:00:00 2001 From: Ravindra Gupta Date: Thu, 23 Nov 2023 17:19:47 +0530 Subject: [PATCH 22/28] Stop scanner on unmount --- src/ScannerViewBase.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/ScannerViewBase.tsx b/src/ScannerViewBase.tsx index a6841c3..7e60a83 100644 --- a/src/ScannerViewBase.tsx +++ b/src/ScannerViewBase.tsx @@ -21,6 +21,17 @@ const ScannerViewComponent = forwardRef<{}, ScannerViewProps>(({ stopScanning: () => scannerViewRef.current && Commands.stopScanning(scannerViewRef.current), }), [scannerViewRef]) + useEffect(() => { + const localRef = scannerViewRef.current; + return () => { + // Somehow when the Scanner Component unmounts, the Native Module is not deallocated and keeps on running in the background state + // So manually stopping the camera to avoid this situation. + if (localRef) { + Commands.stopScanning(localRef); + } + }; + }, []); + const onQrScanned = useCallback((event: ScannerViewQRScanEvent) => { onQrScannedProp?.(event); }, [onQrScannedProp]); From b9449d2053503b7361945b5e729bb429983f8a38 Mon Sep 17 00:00:00 2001 From: Ravindra Gupta Date: Fri, 1 Dec 2023 10:00:56 +0530 Subject: [PATCH 23/28] Fix import issue for useEffect --- src/ScannerViewBase.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScannerViewBase.tsx b/src/ScannerViewBase.tsx index 7e60a83..079656b 100644 --- a/src/ScannerViewBase.tsx +++ b/src/ScannerViewBase.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef, useCallback, useImperativeHandle, useRef } from 'react'; +import React, { forwardRef, useCallback, useImperativeHandle, useRef, useEffect } from 'react'; import type { HostComponent } from 'react-native'; import ReactNativeScannerView, { Commands } from './ReactNativeScannerViewNativeComponent'; From e0a62f16d29aacaf0cdae3fd1f0c00eaeb3f9dc1 Mon Sep 17 00:00:00 2001 From: Tausif Ahmed Date: Fri, 27 Sep 2024 15:43:34 -0400 Subject: [PATCH 24/28] Data Matrix Barcode support in iOS --- ios/ReactNativeScannerView.mm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ios/ReactNativeScannerView.mm b/ios/ReactNativeScannerView.mm index 858a404..cddb8f6 100644 --- a/ios/ReactNativeScannerView.mm +++ b/ios/ReactNativeScannerView.mm @@ -36,7 +36,8 @@ + (NSArray *)metadataObjectTypes AVMetadataObjectTypeCode128Code, AVMetadataObjectTypePDF417Code, AVMetadataObjectTypeQRCode, - AVMetadataObjectTypeAztecCode]; + AVMetadataObjectTypeAztecCode, + AVMetadataObjectTypeDataMatrixCode]; } + (ComponentDescriptorProvider)componentDescriptorProvider From f84a44457f2c5bf2adf3943fcdb872563a958881 Mon Sep 17 00:00:00 2001 From: Subhodip Pal Date: Fri, 18 Oct 2024 17:08:07 +0530 Subject: [PATCH 25/28] [ILX-49850-2] update package --- .../ReactNativeScannerView.kt | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt index c4ef681..12162bf 100644 --- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt +++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt @@ -25,6 +25,7 @@ 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 class ReactNativeScannerView(context: Context) : LinearLayout(context) { @@ -33,6 +34,7 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { private var mCameraProvider: ProcessCameraProvider? = null private var analysisUseCase: ImageAnalysis = ImageAnalysis.Builder() .build() + private lateinit var cameraControl: CameraControl private lateinit var options: BarcodeScannerOptions private lateinit var scanner: BarcodeScanner @@ -219,12 +221,34 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { surfacePreview, analysisUseCase ) + + val camera = cameraProvider.bindToLifecycle( + (reactApplicationContext.currentActivity as AppCompatActivity), + cameraSelector, + surfacePreview, + analysisUseCase + ) + cameraControl = camera.cameraControl + } catch (exc: Exception) { isCameraRunning = false } }, ContextCompat.getMainExecutor(context)) } + fun enableFlashlight() { + cameraControl.enableTorch(true) + } + + fun disableFlashlight() { + cameraControl.enableTorch(false) + } + + fun releaseCamera() { + cameraExecutor.shutdown() + mCameraProvider?.unbindAll() + } + private fun stopCamera() { } @@ -264,11 +288,11 @@ class ReactNativeScannerView(context: Context) : LinearLayout(context) { } } - fun startScanning() { + fun startScanning() { setIsActive(true) } - fun stopScanning() { + fun stopScanning() { setIsActive(false) } } From 6be9ed0edb7d3f05636c6162a76932d2b4f6f14c Mon Sep 17 00:00:00 2001 From: Subhodip Pal Date: Fri, 18 Oct 2024 17:29:01 +0530 Subject: [PATCH 26/28] [ILX-49850-2] Upgrade react-native-scanner --- .../ReactNativeScannerViewEvent.kt | 11 +++- .../ReactNativeScannerViewManager.kt | 14 ++++- example/package.json | 2 +- ios/ReactNativeScannerView.mm | 62 +++++++++++++++++-- package.json | 4 +- src/ReactNativeScannerViewNativeComponent.ts | 6 ++ 6 files changed, 88 insertions(+), 11 deletions(-) diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewEvent.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewEvent.kt index addec40..a1987cc 100644 --- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewEvent.kt +++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewEvent.kt @@ -19,7 +19,16 @@ class ReactNativeScannerViewEvent( ) : Event(surfaceId, viewId) { override fun getEventName(): String { - return "topOnQrScanned" + return "onQrScanned" + } + + override fun dispatchModern(rctEventEmitter: RCTModernEventEmitter) { + super.dispatchModern(rctEventEmitter) // if we don't call this, the react native part won't receive the event but because of this line event call two times + rctEventEmitter.receiveEvent( + -1, + viewTag, eventName, + Arguments.createMap() + ) } override fun getEventData(): WritableMap { diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt index 8614548..0095dc4 100644 --- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt +++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerViewManager.kt @@ -29,6 +29,18 @@ class ReactNativeScannerViewManager(private val mCallerContext: ReactApplication return NAME } + override fun enableFlashlight(view: ReactNativeScannerView?) { + view?.enableFlashlight() + } + + override fun disableFlashlight(view: ReactNativeScannerView?) { + view?.disableFlashlight() + } + + override fun releaseCamera(view: ReactNativeScannerView?) { + view?.releaseCamera() + } + override fun createViewInstance(reactContext: ThemedReactContext): ReactNativeScannerView { val scannerView = ReactNativeScannerView(mCallerContext) scannerView.setUpCamera() @@ -46,7 +58,7 @@ class ReactNativeScannerViewManager(private val mCallerContext: ReactApplication override fun getExportedCustomDirectEventTypeConstants(): Map { return MapBuilder.of( - "topOnQrScanned", + "onQrScanned", MapBuilder.of("registrationName", "onQrScanned") ) } diff --git a/example/package.json b/example/package.json index 896bb97..318f564 100644 --- a/example/package.json +++ b/example/package.json @@ -10,7 +10,7 @@ "test": "jest" }, "dependencies": { - "@pushpendersingh/react-native-scanner": "^1.1.0-beta.2", + "@pushpendersingh/react-native-scanner": "^1.1.0", "react": "18.2.0", "react-native": "0.72.3", "react-native-permissions": "^3.7.3" diff --git a/ios/ReactNativeScannerView.mm b/ios/ReactNativeScannerView.mm index cddb8f6..b27c8ba 100644 --- a/ios/ReactNativeScannerView.mm +++ b/ios/ReactNativeScannerView.mm @@ -72,8 +72,15 @@ - (instancetype)initWithFrame:(CGRect)frame _prevLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session]; _prevLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; [_view.layer addSublayer:_prevLayer]; - [_session startRunning]; - + + // Create a dispatch queue. + dispatch_queue_t sessionQueue = dispatch_queue_create("session queue", DISPATCH_QUEUE_SERIAL); + + // Use dispatch_async to call the startRunning method on the sessionQueue. + dispatch_async(sessionQueue, ^{ + [self->_session startRunning]; + }); + self.contentView = _view; } @@ -154,6 +161,49 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects: } } +(void)releaseCamera { + + NSLog(@"%@", @"Release Camera"); + + if (_session != nil) { + // Stop the session + [_session stopRunning]; + + // Release the session, input, output, and preview layer + _session = nil; + _input = nil; + _output = nil; + _prevLayer = nil; + + } +} + +- (void)enableFlashlight { + if ([_device hasTorch] && [_device isTorchModeSupported:AVCaptureTorchModeOn]) { + NSError *error = nil; + if ([_device lockForConfiguration:&error]) { + [_device setTorchMode:AVCaptureTorchModeOn]; + [_device unlockForConfiguration]; + } else { + // Handle error + NSLog(@"%@", [error localizedDescription]); + } + } +} + +- (void)disableFlashlight { + if ([_device hasTorch] && [_device isTorchModeSupported:AVCaptureTorchModeOff]) { + NSError *error = nil; + if ([_device lockForConfiguration:&error]) { + [_device setTorchMode:AVCaptureTorchModeOff]; + [_device unlockForConfiguration]; + } else { + // Handle error + NSLog(@"%@", [error localizedDescription]); + } + } +} + - (CGPoint)mapObject:(NSDictionary *)object { if (object == nil) { return CGPointMake(0, 0); @@ -194,15 +244,15 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & [super updateProps:props oldProps:oldProps]; } +- (void)handleCommand:(nonnull const NSString *)commandName args:(nonnull const NSArray *)args { + RCTReactNativeScannerViewHandleCommand(self, commandName, args); +} + - (void)updateLayoutMetrics:(const facebook::react::LayoutMetrics &)layoutMetrics oldLayoutMetrics:(const facebook::react::LayoutMetrics &)oldLayoutMetrics{ [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics]; _prevLayer.frame = [_view.layer bounds]; } -- (void)handleCommand:(nonnull const NSString *)commandName args:(nonnull const NSArray *)args { - RCTReactNativeScannerViewHandleCommand(self, commandName, args); -} - - (void)pausePreview { if ([[_prevLayer connection] isEnabled]) { [[_prevLayer connection] setEnabled:NO]; diff --git a/package.json b/package.json index aea7b98..1d02212 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@pushpendersingh/react-native-scanner", - "version": "1.1.0-beta.2", + "version": "1.2.0", "description": "A QR code & Barcode Scanner for React Native Projects.", "main": "lib/commonjs/index", "module": "lib/module/index", @@ -86,7 +86,7 @@ "engines": { "node": ">= 16.0.0" }, - "packageManager": "^yarn@1.22.15", + "packageManager": "^yarn@1.22.22", "jest": { "preset": "react-native", "modulePathIgnorePatterns": [ diff --git a/src/ReactNativeScannerViewNativeComponent.ts b/src/ReactNativeScannerViewNativeComponent.ts index 44d14ab..f20791c 100644 --- a/src/ReactNativeScannerViewNativeComponent.ts +++ b/src/ReactNativeScannerViewNativeComponent.ts @@ -26,6 +26,9 @@ export interface NativeProps extends ViewProps { } interface NativeCommands { + enableFlashlight: (viewRef: React.ElementRef>) => Promise; + disableFlashlight: (viewRef: React.ElementRef>) => Promise; + releaseCamera: (viewRef: React.ElementRef>) => Promise; pausePreview: (viewRef: React.ElementRef>) => void; resumePreview: (viewRef: React.ElementRef>) => void; startScanning: (viewRef: React.ElementRef>) => void; @@ -34,6 +37,9 @@ interface NativeCommands { export const Commands: NativeCommands = codegenNativeCommands({ supportedCommands: [ + 'enableFlashlight', + 'disableFlashlight', + 'releaseCamera', 'pausePreview', 'resumePreview', 'startScanning', From 62bef62d254d85ce9ce933402f21403fe21c96d6 Mon Sep 17 00:00:00 2001 From: Subhodip Pal Date: Mon, 21 Oct 2024 14:51:32 +0530 Subject: [PATCH 27/28] [ILX-49850-2] resolve error --- ios/ReactNativeScannerView.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/ReactNativeScannerView.mm b/ios/ReactNativeScannerView.mm index b27c8ba..f0bb995 100644 --- a/ios/ReactNativeScannerView.mm +++ b/ios/ReactNativeScannerView.mm @@ -161,7 +161,7 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects: } } -(void)releaseCamera { +- (void)releaseCamera { NSLog(@"%@", @"Release Camera"); From 092d794837b3e2ca6e66c9e01cdfdf61e45cc1fd Mon Sep 17 00:00:00 2001 From: Subhodip Pal Date: Mon, 21 Oct 2024 16:24:21 +0530 Subject: [PATCH 28/28] [ILX-49850-2] Upgrade react-native-scanner --- .github/ISSUE_TEMPLATE/bug_report.md | 34 ++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++ .github/ISSUE_TEMPLATE/question.md | 11 ++ .github/actions/setup/action.yml | 28 --- .github/workflows/check-repro.yml | 69 +++++++ .github/workflows/ci.yml | 151 --------------- .github/workflows/semantic-pr.yml | 13 ++ .github/workflows/stale.yml | 22 +++ .github/workflows/triage.yaml | 27 +++ .github/workflows/versions.yml | 19 ++ README.md | 217 +++++++++++++++++++--- 11 files changed, 402 insertions(+), 209 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/question.md delete mode 100644 .github/actions/setup/action.yml create mode 100644 .github/workflows/check-repro.yml delete mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/semantic-pr.yml create mode 100644 .github/workflows/stale.yml create mode 100644 .github/workflows/triage.yaml create mode 100644 .github/workflows/versions.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..efdf569 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,34 @@ +--- +name: 🐛 Report a bug +about: Create a report to help us improve +title: '' +labels: 'bug' +assignees: '' +--- + +### Current behaviour + + +### Expected behaviour + + +### How to reproduce? + + + +### Preview + + +### What have you tried so far? + + +### Your Environment + +| software | version | +| ------------------------------------- | ------- | +| ios | x | +| android | x | +| react-native | x.x.x | +| @pushpendersingh/react-native-scanner | x.x.x | +| node | x.x.x | +| npm or yarn | x.x.x | diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..f7a79fe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: 🗣 Feature request +about: Suggest an idea for this project +title: '' +labels: 'feature request' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000..588bc22 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,11 @@ +--- +name: 💬 Question +about: You need help with @pushpendersingh/react-native-scanner. +title: '' +labels: 'question' +assignees: '' + +--- + +### Ask your Question + diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml deleted file mode 100644 index f918c91..0000000 --- a/.github/actions/setup/action.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Setup -description: Setup Node.js and install dependencies - -runs: - using: composite - steps: - - name: Setup Node.js - uses: actions/setup-node@v3 - with: - node-version-file: .nvmrc - - - name: Cache dependencies - id: yarn-cache - uses: actions/cache@v3 - with: - path: | - **/node_modules - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('**/package.json') }} - restore-keys: | - ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - ${{ runner.os }}-yarn- - - - name: Install dependencies - if: steps.yarn-cache.outputs.cache-hit != 'true' - run: | - yarn install --cwd example --frozen-lockfile - yarn install --frozen-lockfile - shell: bash diff --git a/.github/workflows/check-repro.yml b/.github/workflows/check-repro.yml new file mode 100644 index 0000000..f0dc2b6 --- /dev/null +++ b/.github/workflows/check-repro.yml @@ -0,0 +1,69 @@ +name: Check for repro +on: + issues: + types: [opened, edited] + issue_comment: + types: [created, edited] + +jobs: + check-repro: + if: ${{ github.event.label.name == 'bug' }} + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v3 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const user = context.payload.sender.login; + const body = context.payload.comment + ? context.payload.comment.body + : context.payload.issue.body; + const regex = new RegExp( + `https?:\\/\\/((github\\.com\\/${user}\\/[^/]+\\/?[\\s\\n]+)|(snack\\.expo\\.dev\\/.+))`, + 'gm' + ); + if (regex.test(body)) { + await github.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['repro provided'], + }); + try { + await github.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'needs repro', + }); + } catch (error) { + if (!/Label does not exist/.test(error.message)) { + throw error; + } + } + } else { + if (context.eventName !== 'issues') { + return; + } + const body = "Hey! Thanks for opening the issue. The issue doesn't seem to contain a link to a repro (a [snack.expo.dev](https://snack.expo.dev) link or link to a GitHub repo under your username).\n\nCan you provide a [minimal repro](https://stackoverflow.com/help/minimal-reproducible-example) which demonstrates the issue? A repro will help us debug the issue faster. Please try to keep the repro as small as possible and make sure that we can run it without additional setup."; + const comments = await github.issues.listComments({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + }); + if (comments.data.some(comment => comment.body === body)) { + return; + } + await github.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body, + }); + await github.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['needs repro'], + }); + } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 39efde8..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,151 +0,0 @@ -name: CI -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Setup - uses: ./.github/actions/setup - - - name: Lint files - run: yarn lint - - - name: Typecheck files - run: yarn typecheck - - test: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Setup - uses: ./.github/actions/setup - - - name: Run unit tests - run: yarn test --maxWorkers=2 --coverage - - build-library: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Setup - uses: ./.github/actions/setup - - - name: Build package - run: yarn prepack - - build-android: - runs-on: ubuntu-latest - env: - TURBO_CACHE_DIR: .turbo/android - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Setup - uses: ./.github/actions/setup - - - name: Cache turborepo for Android - uses: actions/cache@v3 - with: - path: ${{ env.TURBO_CACHE_DIR }} - key: ${{ runner.os }}-turborepo-android-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-turborepo-android- - - - name: Check turborepo cache for Android - run: | - TURBO_CACHE_STATUS=$(node -p "($(yarn --silent turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:android').cache.status") - - if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then - echo "turbo_cache_hit=1" >> $GITHUB_ENV - fi - - - name: Install JDK - if: env.turbo_cache_hit != 1 - uses: actions/setup-java@v3 - with: - distribution: 'zulu' - java-version: '11' - - - name: Finalize Android SDK - if: env.turbo_cache_hit != 1 - run: | - /bin/bash -c "yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/null" - - - name: Cache Gradle - if: env.turbo_cache_hit != 1 - uses: actions/cache@v3 - with: - path: | - ~/.gradle/wrapper - ~/.gradle/caches - key: ${{ runner.os }}-gradle-${{ hashFiles('example/android/gradle/wrapper/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Build example for Android - run: | - yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" - - build-ios: - runs-on: macos-latest - env: - TURBO_CACHE_DIR: .turbo/ios - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Setup - uses: ./.github/actions/setup - - - name: Cache turborepo for iOS - uses: actions/cache@v3 - with: - path: ${{ env.TURBO_CACHE_DIR }} - key: ${{ runner.os }}-turborepo-ios-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-turborepo-ios- - - - name: Check turborepo cache for iOS - run: | - TURBO_CACHE_STATUS=$(node -p "($(yarn --silent turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:ios').cache.status") - - if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then - echo "turbo_cache_hit=1" >> $GITHUB_ENV - fi - - - name: Cache cocoapods - if: env.turbo_cache_hit != 1 - id: cocoapods-cache - uses: actions/cache@v3 - with: - path: | - **/ios/Pods - key: ${{ runner.os }}-cocoapods-${{ hashFiles('example/ios/Podfile.lock') }} - restore-keys: | - ${{ runner.os }}-cocoapods- - - - name: Install cocoapods - if: env.turbo_cache_hit != 1 && steps.cocoapods-cache.outputs.cache-hit != 'true' - run: | - yarn example pods - env: - NO_FLIPPER: 1 - - - name: Build example for iOS - run: | - yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" diff --git a/.github/workflows/semantic-pr.yml b/.github/workflows/semantic-pr.yml new file mode 100644 index 0000000..46cf7f1 --- /dev/null +++ b/.github/workflows/semantic-pr.yml @@ -0,0 +1,13 @@ +name: "Semantic Pull Request" +on: [pull_request] + +jobs: + main: + name: Validate PR title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v4.5.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + validateSingleCommit: true diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..33ebabc --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,22 @@ + +name: Close stale issues and PRs +on: + schedule: + - cron: '30 1 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 30 + days-before-close: 7 + any-of-labels: 'needs more info,needs repro,needs response' + exempt-issue-labels: 'repro provided,keep open' + exempt-pr-labels: 'keep open' + stale-issue-label: 'stale' + stale-pr-label: 'stale' + stale-issue-message: 'Hello 👋, this issue has been open for more than a month without a repro or any activity. If the issue is still present in the latest version, please provide a repro or leave a comment within 7 days to keep it open, otherwise it will be closed automatically. If you found a solution or workaround for the issue, please comment here for others to find. If this issue is critical for you, please consider sending a pull request to fix it.' + stale-pr-message: 'Hello 👋, this pull request has been open for more than a month with no activity on it. If you think this is still necessary with the latest version, please comment and ping a maintainer to get this reviewed, otherwise it will be closed automatically in 7 days.' diff --git a/.github/workflows/triage.yaml b/.github/workflows/triage.yaml new file mode 100644 index 0000000..43e1b42 --- /dev/null +++ b/.github/workflows/triage.yaml @@ -0,0 +1,27 @@ +name: Triage +on: + issues: + types: [labeled] + +jobs: + needs-more-info: + runs-on: ubuntu-latest + if: github.event.label.name == 'needs more info' + steps: + - uses: actions/checkout@master + - uses: actions/github@v1.0.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + args: comment "Hey! Thanks for opening the issue. Can you provide more information about the issue? Please fill the issue template when opening the issue without deleting any section. We need all the information we can, to be able to help. Make sure to at least provide - Current behaviour, Expected behaviour, A way to reproduce the issue with minimal code (link to [snack.expo.dev](https://snack.expo.dev)) or a repo on GitHub, and the information about your environment (such as the platform of the device, versions of all the packages etc.)." + + needs-repro: + runs-on: ubuntu-latest + if: github.event.label.name == 'needs repro' + steps: + - uses: actions/checkout@master + - uses: actions/github@v1.0.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + args: comment "Hey! Thanks for opening the issue. Can you provide a minimal repro which demonstrates the issue? Posting a snippet of your code in the issue is useful, but it's not usually straightforward to run. A repro will help us debug the issue faster. Please try to keep the repro as small as possible. The easiest way to provide a repro is on [snack.expo.dev](https://snack.expo.dev). If it's not possible to repro it on [snack.expo.dev](https://snack.expo.dev), then you can also provide the repro in a GitHub repository." diff --git a/.github/workflows/versions.yml b/.github/workflows/versions.yml new file mode 100644 index 0000000..009ce77 --- /dev/null +++ b/.github/workflows/versions.yml @@ -0,0 +1,19 @@ +name: Check versions +on: + issues: + types: [opened, edited] + +jobs: + check-versions: + if: ${{ github.event.label.name == 'bug' }} + runs-on: ubuntu-latest + steps: + - uses: react-navigation/check-versions-action@v1.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + required-packages: | + react-native + @pushpendersingh/react-native-scanner + optional-packages: | + npm + yarn diff --git a/README.md b/README.md index fa06eaa..9a8d2e5 100644 --- a/README.md +++ b/README.md @@ -1,68 +1,65 @@ # @pushpendersingh/react-native-scanner - A QR code & Barcode Scanner for React Native Projects. - For React Native developers that need to scan barcodes and QR codes in their apps, this package is a useful resource. It supports React Native's new Fabric Native architecture and was created in Kotlin and Objective-C. - With this package, users can quickly and easily scan barcodes and QR codes with their device's camera. Using this package, several types of codes can be scanned, and it is simple to use. If you want to provide your React Native app the ability to read barcodes and QR codes, you should definitely give this package some thought. +The `@pushpendersingh/react-native-scanner` package also includes a flashlight feature that can be turned on and off. This can be useful when scanning QR codes in low light conditions. + ## Getting started ### Requirements - #### IOS - Open your project's `Info.plist` and add the following lines inside the outermost `` tag: - ```xml NSCameraUsageDescription Your message to user when the camera is accessed for the first time ``` + Open your project's `Podfile` and add enable the new architecture: + ``` :fabric_enabled => true, ``` + Run below command to enable the new architecture in IOS folder + ``` bundle install && RCT_NEW_ARCH_ENABLED=1 bundle exec pod install ``` - ### Android - Open your project's `AndroidManifest.xml` and add the following lines inside the `` tag: - ```xml ``` + Open your project's `gradle.properties` and add enable the new architecture: + ``` newArchEnabled=true ``` - ### To install and start using @pushpendersingh/react-native-scanner - ```sh npm install @pushpendersingh/react-native-scanner ``` - ### Supported Formats - -| 1D product | 1D industrial | 2D | -|:----------------------|:--------------|:---------------| -| UPC-A | Code 39 | QR Code | -| UPC-E | Code 93 | Data Matrix | -| EAN-8 | Code 128 | Aztec | -| EAN-13 | Codabar | PDF 417 | -| | ITF | | - +| 1D product | 1D industrial | 2D | +| :--------- | :------------ | :---------- | +| UPC-A | Code 39 | QR Code | +| UPC-E | Code 93 | Data Matrix | +| EAN-8 | Code 128 | Aztec | +| EAN-13 | Codabar | PDF 417 | +| | ITF | | ## Usage To use @pushpendersingh/react-native-scanner, `import` the `@pushpendersingh/react-native-scanner` module and use the `` tag. More usage examples can be seen under the `examples/` folder. +
+ Basic usage + Here is an example of basic usage: ```js @@ -74,19 +71,14 @@ import { Text, SafeAreaView } from 'react-native'; - import { request, PERMISSIONS, openSettings, RESULTS } from 'react-native-permissions'; import { ReactNativeScannerView } from "@pushpendersingh/react-native-scanner"; - export default function App() { - const { height, width } = useWindowDimensions(); const [isCameraPermissionGranted, setIsCameraPermissionGranted] = useState(false); - useEffect(() => { checkCameraPermission(); }, []); - const checkCameraPermission = async () => { request(Platform.OS === 'ios' ? PERMISSIONS.IOS.CAMERA : PERMISSIONS.ANDROID.CAMERA) .then(async (result: any) => { @@ -108,7 +100,6 @@ export default function App() { } }) }; - if (isCameraPermissionGranted) { return ( @@ -130,19 +121,185 @@ export default function App() { } ``` +
+ +## Flashlight Feature + +
+ Flashlight Feature + + To use the flashlight feature, add the following code to your project: + +```jsx +import React, {useEffect, useRef, useState} from 'react'; +import { + Alert, + Platform, + useWindowDimensions, + Text, + SafeAreaView, + TouchableOpacity, +} from 'react-native'; + +import { + request, + PERMISSIONS, + openSettings, + RESULTS, +} from 'react-native-permissions'; +import { + ReactNativeScannerView, + Commands, +} from '@pushpendersingh/react-native-scanner'; + +export default function App() { + const {height, width} = useWindowDimensions(); + const [isCameraPermissionGranted, setIsCameraPermissionGranted] = + useState(false); + const cameraRef = useRef(null); + + useEffect(() => { + checkCameraPermission(); + + return () => { + if(cameraRef.current) { + Commands.releaseCamera(cameraRef.current); + } + }; + }, []); + + const enableFlashlight = () => { + Commands.enableFlashlight(cameraRef.current); + }; + + const disableFlashlight = () => { + Commands.disableFlashlight(cameraRef.current); + }; + + const checkCameraPermission = async () => { + request( + Platform.OS === 'ios' + ? PERMISSIONS.IOS.CAMERA + : PERMISSIONS.ANDROID.CAMERA, + ).then(async (result: any) => { + switch (result) { + case RESULTS.UNAVAILABLE: + break; + case RESULTS.DENIED: + Alert.alert( + 'Permission Denied', + 'You need to grant camera permission first', + ); + openSettings(); + break; + case RESULTS.GRANTED: + setIsCameraPermissionGranted(true); + break; + case RESULTS.BLOCKED: + Alert.alert( + 'Permission Blocked', + 'You need to grant camera permission first', + ); + openSettings(); + break; + } + }); + }; + + if (isCameraPermissionGranted) { + return ( + + (cameraRef.current = ref)} + style={{height, width}} + onQrScanned={(value: any) => { + console.log(value.nativeEvent); + }} + /> + + + Turn ON + + + + Turn OFF + + + ); + } else { + return ( + + You need to grant camera permission first + + ); + } +} +``` + +
+ ## Props #### `onQrScanned` (required) - propType: `func.isRequired` default: `(e) => (console.log('QR code scanned!', e))` In the event that a QR code or barcode is detected in the camera's view, this specified method will be called. +## Native Commands + +The `@pushpendersingh/react-native-scanner` package also includes a few native commands that can be used to control the camera and flashlight. + +### Commands + +#### `enableFlashlight` + +This command is used to turn on the flashlight. +```js +if(cameraRef.current) { + Commands.enableFlashlight(cameraRef.current); +} +``` + +#### `disableFlashlight` + +This command is used to turn off the flashlight. +```js +if(cameraRef.current) { + Commands.disableFlashlight(cameraRef.current); +} +``` + +#### `releaseCamera` + +This command is used to release the camera. + +```js +if(cameraRef.current) { + Commands.releaseCamera(cameraRef.current); +} +``` + ## Contributing See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow. - ## License - MIT