diff --git a/example/.nvmrc b/.nvmrc similarity index 100% rename from example/.nvmrc rename to .nvmrc diff --git a/README.md b/README.md index 9c5a2ebea..44978739b 100644 --- a/README.md +++ b/README.md @@ -173,18 +173,19 @@ Additionally, the Camera can be used for barcode scanning ### Camera Props (Optional) | Props | Type | Description | -| ------------------------------ | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ------------------------------ | -------------------------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `ref` | Ref | Reference on the camera view | | `style` | StyleProp\ | Style to apply on the camera view | | `flashMode` | `'on'`/`'off'`/`'auto'` | Camera flash mode. Default: `auto` | | `focusMode` | `'on'`/`'off'` | Camera focus mode. Default: `on` | -| `zoomMode` | `'on'`/`'off'` | Enable the pinch to zoom gesture. Default: `on`. If `on`, you must pass `zoom` as `undefined` or avoid setting `zoomMode` to allow pinch to zoom | +| `zoomMode` | `'on'`/`'off'` | Enable the pinch to zoom gesture. Default: `on`. If `on`, you must pass `zoom` as `undefined` or avoid setting `zoomMode` to allow pinch to zoom | | `zoom` | `number` | Control the zoom. Default: `1.0` | | `maxZoom` | `number` | Maximum zoom allowed (but not beyond what camera allows). Default: `undefined` (camera default max) | | `onZoom` | Function | Callback when user makes a pinch gesture, regardless of what the `zoom` prop was set to. Returned event contains `zoom`. Ex: `onZoom={(e) => console.log(e.nativeEvent.zoom)}`. | | `torchMode` | `'on'`/`'off'` | Toggle flash light when camera is active. Default: `off` | | `cameraType` | CameraType.Back/CameraType.Front | Choose what camera to use. Default: `CameraType.Back` | | `onOrientationChange` | Function | Callback when physical device orientation changes. Returned event contains `orientation`. Ex: `onOrientationChange={(event) => console.log(event.nativeEvent.orientation)}`. Use `import { Orientation } from 'react-native-camera-kit'; if (event.nativeEvent.orientation === Orientation.PORTRAIT) { ... }` to understand the new value | +| `allowedBarcodeTypes` | string[] | Limits which barcode formats can be detected. Ex: `['code-128', 'code-39', 'code-93', 'codabar', 'ean-13', 'ean-8', 'itf', 'upc-a', 'upc-e', 'pdf-417', 'aztec', 'data-matrix', 'code-128']`. If empty or omitted, all supported formats are scanned. | | **Android only** | | `onError` | Function | Android only. Callback when camera fails to initialize. Ex: `onError={(e) => console.log(e.nativeEvent.errorMessage)}`. | | `shutterPhotoSound` | `boolean` | Android only. Enable or disable the shutter sound when capturing a photo. Default: `true` | @@ -195,13 +196,13 @@ Additionally, the Camera can be used for barcode scanning | `resetFocusWhenMotionDetected` | Boolean | Dismiss tap to focus when focus area content changes. Native iOS feature, see documentation: https://developer.apple.com/documentation/avfoundation/avcapturedevice/1624644-subjectareachangemonitoringenabl?language=objc). Default `true`. | | `resizeMode` | `'cover' / 'contain'` | Determines the scaling and cropping behavior of content within the view. `cover` (resizeAspectFill on iOS) scales the content to fill the view completely, potentially cropping content if its aspect ratio differs from the view. `contain` (resizeAspect on iOS) scales the content to fit within the view's bounds without cropping, ensuring all content is visible but may introduce letterboxing. Default behavior depends on the specific use case. | | `scanThrottleDelay` | `number` | Duration between scan detection in milliseconds. Default 2000 (2s) | -| `maxPhotoQualityPrioritization` | `'balanced'` / `'quality'` / `'speed'` | [iOS 13 and newer](https://developer.apple.com/documentation/avfoundation/avcapturephotooutput/3182995-maxphotoqualityprioritization). `'speed'` provides a 60-80% median capture time reduction vs 'quality' setting. Tested on iPhone 6S Max (66% faster) and iPhone 15 Pro Max (76% faster!). Default `balanced` | -| `onCaptureButtonPressIn` | Function | Callback when iPhone capture button is pressed in or Android volume or camera button is pressed in. Ex: `onCaptureButtonPressIn={() => console.log("volume button pressed in")}` | -| `onCaptureButtonPressOut` | Function | Callback when iPhone capture button is released or Android volume or camera button is released. Ex: `onCaptureButtonPressOut={() => console.log("volume button released")}` | +| `maxPhotoQualityPrioritization` | `'balanced'` / `'quality'` / `'speed'` | [iOS 13 and newer](https://developer.apple.com/documentation/avfoundation/avcapturephotooutput/3182995-maxphotoqualityprioritization). `'speed'` provides a 60-80% median capture time reduction vs 'quality' setting. Tested on iPhone 6S Max (66% faster) and iPhone 15 Pro Max (76% faster!). Default `balanced` | +| `onCaptureButtonPressIn` | Function | Callback when iPhone capture button is pressed in or Android volume or camera button is pressed in. Ex: `onCaptureButtonPressIn={() => console.log("volume button pressed in")}` | +| `onCaptureButtonPressOut` | Function | Callback when iPhone capture button is released or Android volume or camera button is released. Ex: `onCaptureButtonPressOut={() => console.log("volume button released")}` | | **Barcode only** | | `scanBarcode` | `boolean` | Enable barcode scanner. Default: `false` | | `showFrame` | `boolean` | Show frame in barcode scanner. Default: `false` | -| `barcodeFrameSize` | `object` | Frame size of barcode scanner. Default: `{ width: 300, height: 150 }` | +| `barcodeFrameSize` | `object` | Frame size of barcode scanner. Default: `{ width: 300, height: 150 }` | | `laserColor` | Color | Color of barcode scanner laser visualization. Default: `red` | | `frameColor` | Color | Color of barcode scanner frame visualization. Default: `yellow` | | `onReadCode` | Function | Callback when scanner successfully reads barcode. Returned event contains `codeStringValue`. Default: `null`. Ex: `onReadCode={(event) => console.log(event.nativeEvent.codeStringValue)}` | diff --git a/android/src/main/java/com/rncamerakit/CKCamera.kt b/android/src/main/java/com/rncamerakit/CKCamera.kt index 4bb3c853c..8dd5a6e0d 100644 --- a/android/src/main/java/com/rncamerakit/CKCamera.kt +++ b/android/src/main/java/com/rncamerakit/CKCamera.kt @@ -28,6 +28,7 @@ import androidx.lifecycle.LifecycleObserver import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Promise import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.ReadableArray import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.events.RCTEventEmitter import com.rncamerakit.barcode.BarcodeFrame @@ -109,6 +110,7 @@ class CKCamera(context: ThemedReactContext) : FrameLayout(context), LifecycleObs private var frameColor = Color.GREEN private var laserColor = Color.RED private var barcodeFrameSize: Size? = null + private var allowedBarcodeTypes: Array? = null private fun getActivity() : Activity { return currentContext.currentActivity!! @@ -329,33 +331,51 @@ class CKCamera(context: ThemedReactContext) : FrameLayout(context), LifecycleObs val useCases = mutableListOf(preview, imageCapture) - if (scanBarcode) { - val analyzer = QRCodeAnalyzer(analyzerBlock@{ barcodes, imageSize -> - if (barcodes.isEmpty()) { - return@analyzerBlock + if (scanBarcode) { + val analyzer = QRCodeAnalyzer({ barcodes, imageSize -> + if (barcodes.isEmpty()) return@QRCodeAnalyzer + + // 1. Filter by allowed barcode formats + val allowedTypes = convertAllowedBarcodeTypes() + val filteredByType = if (allowedTypes.isEmpty()) { + barcodes + } else { + barcodes.filter { barcode -> + barcode.format in allowedTypes + } } + if (filteredByType.isEmpty()) return@QRCodeAnalyzer + val barcodeFrame = barcodeFrame + val vf = viewFinder + + // 2. No frame? → behave like original code if (barcodeFrame == null) { - onBarcodeRead(barcodes) - return@analyzerBlock + onBarcodeRead(filteredByType) + return@QRCodeAnalyzer } - // Calculate scaling factors (image is always rotated by 90 degrees) - val scaleX = viewFinder.width.toFloat() / imageSize.height - val scaleY = viewFinder.height.toFloat() / imageSize.width + val frameRect = barcodeFrame.frameRect + + // 3. Calculate scaling factors (image is always rotated by 90 degrees) + val scaleX = vf.width.toFloat() / imageSize.height + val scaleY = vf.height.toFloat() / imageSize.width + + // 4. filter barcodes inside the frame + val filteredBarcodes = filteredByType.filter { barcode -> + val barcodeBoundingBox = barcode.boundingBox ?: return@filter false - val filteredBarcodes = barcodes.filter { barcode -> - val barcodeBoundingBox = barcode.boundingBox ?: return@filter false; val scaledBarcodeBoundingBox = Rect( (barcodeBoundingBox.left * scaleX).toInt(), (barcodeBoundingBox.top * scaleY).toInt(), (barcodeBoundingBox.right * scaleX).toInt(), (barcodeBoundingBox.bottom * scaleY).toInt() ) - barcodeFrame.frameRect.contains(scaledBarcodeBoundingBox) + frameRect.contains(scaledBarcodeBoundingBox) } + // 5. Emit if any left if (filteredBarcodes.isNotEmpty()) { onBarcodeRead(filteredBarcodes) } @@ -482,8 +502,8 @@ class CKCamera(context: ThemedReactContext) : FrameLayout(context), LifecycleObs val imageFile = File(path) val imageSize = imageFile.length() // size in bytes - imageInfo.putDouble("size", imageSize.toDouble()) - + imageInfo.putDouble("size", imageSize.toDouble()) + promise.resolve(imageInfo) } catch (ex: Exception) { Log.e(TAG, "Error while saving or decoding saved photo: ${ex.message}", ex) @@ -703,6 +723,26 @@ class CKCamera(context: ThemedReactContext) : FrameLayout(context), LifecycleObs } } + fun setAllowedBarcodeTypes(types: ReadableArray?) { + if (types == null || types.size() == 0) { + allowedBarcodeTypes = emptyArray() + return + } + + // Convert only valid CodeFormat values + val converted = mutableListOf() + + for (i in 0 until types.size()) { + val name = types.getString(i) ?: continue + val format = CodeFormat.fromName(name) + if (format != null) { + converted.add(format) + } + } + + allowedBarcodeTypes = converted.toTypedArray() + } + private fun convertDeviceHeightToSupportedAspectRatio(actualWidth: Int, actualHeight: Int): Int { val maxScreenRatio = 16 / 9f return (if (actualHeight / actualWidth > maxScreenRatio) actualWidth * maxScreenRatio else actualHeight).toInt() @@ -723,6 +763,10 @@ class CKCamera(context: ThemedReactContext) : FrameLayout(context), LifecycleObs return false } + private fun convertAllowedBarcodeTypes(): Set { + return allowedBarcodeTypes?.map { it.toBarcodeType() }?.toSet() ?: emptySet() + } + companion object { private const val TAG = "CameraKit" diff --git a/android/src/main/java/com/rncamerakit/CodeFormat.kt b/android/src/main/java/com/rncamerakit/CodeFormat.kt index 207c7151d..907c6c39e 100644 --- a/android/src/main/java/com/rncamerakit/CodeFormat.kt +++ b/android/src/main/java/com/rncamerakit/CodeFormat.kt @@ -10,6 +10,7 @@ enum class CodeFormat(val code: String) { EAN_13("ean-13"), EAN_8("ean-8"), ITF("itf"), + UPC_A("upc-a"), UPC_E("upc-e"), QR("qr"), PDF_417("pdf-417"), @@ -26,6 +27,7 @@ enum class CodeFormat(val code: String) { EAN_13 -> Barcode.FORMAT_EAN_13 EAN_8 -> Barcode.FORMAT_EAN_8 ITF -> Barcode.FORMAT_ITF + UPC_A -> Barcode.FORMAT_UPC_A UPC_E -> Barcode.FORMAT_UPC_E QR -> Barcode.FORMAT_QR_CODE PDF_417 -> Barcode.FORMAT_PDF417 @@ -45,6 +47,7 @@ enum class CodeFormat(val code: String) { Barcode.FORMAT_EAN_13 -> EAN_13 Barcode.FORMAT_EAN_8 -> EAN_8 Barcode.FORMAT_ITF -> ITF + Barcode.FORMAT_UPC_A -> UPC_A Barcode.FORMAT_UPC_E -> UPC_E Barcode.FORMAT_QR_CODE -> QR Barcode.FORMAT_PDF417 -> PDF_417 @@ -52,5 +55,9 @@ enum class CodeFormat(val code: String) { Barcode.FORMAT_DATA_MATRIX -> DATA_MATRIX else -> UNKNOWN } + + fun fromName(name: String?): CodeFormat? { + return values().firstOrNull { it.code == name } + } } } diff --git a/android/src/newarch/java/com/rncamerakit/CKCameraManager.kt b/android/src/newarch/java/com/rncamerakit/CKCameraManager.kt index 9278b1918..01a9f198c 100644 --- a/android/src/newarch/java/com/rncamerakit/CKCameraManager.kt +++ b/android/src/newarch/java/com/rncamerakit/CKCameraManager.kt @@ -144,6 +144,11 @@ class CKCameraManager(context: ReactApplicationContext) : SimpleViewManager void }) => { frameColor="white" scanBarcode showFrame + allowedBarcodeTypes={['qr', 'ean-13']} barcodeFrameSize={{ width: 300, height: 150 }} onReadCode={(event) => { Vibration.vibrate(100); setBarcode(event.nativeEvent.codeStringValue); console.log('barcode', event.nativeEvent.codeStringValue); console.log('codeFormat', event.nativeEvent.codeFormat); - }} /> @@ -233,8 +233,7 @@ const styles = StyleSheet.create({ backBtnContainer: { alignItems: 'flex-start', }, - captureButtonContainer: { - }, + captureButtonContainer: {}, textNumberContainer: { position: 'absolute', top: 0, diff --git a/ios/ReactNativeCameraKit/CKCameraManager.mm b/ios/ReactNativeCameraKit/CKCameraManager.mm index bcb992926..5d783ef36 100644 --- a/ios/ReactNativeCameraKit/CKCameraManager.mm +++ b/ios/ReactNativeCameraKit/CKCameraManager.mm @@ -30,6 +30,7 @@ @interface RCT_EXTERN_MODULE(CKCameraManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(laserColor, UIColor) RCT_EXPORT_VIEW_PROPERTY(frameColor, UIColor) RCT_EXPORT_VIEW_PROPERTY(barcodeFrameSize, NSDictionary) +RCT_EXPORT_VIEW_PROPERTY(allowedBarcodeTypes, NSArray) RCT_EXPORT_VIEW_PROPERTY(onOrientationChange, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onCaptureButtonPressIn, RCTDirectEventBlock) diff --git a/ios/ReactNativeCameraKit/CKCameraViewComponentView.mm b/ios/ReactNativeCameraKit/CKCameraViewComponentView.mm index b54beb873..3b064c3b8 100644 --- a/ios/ReactNativeCameraKit/CKCameraViewComponentView.mm +++ b/ios/ReactNativeCameraKit/CKCameraViewComponentView.mm @@ -248,7 +248,20 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & _view.barcodeFrameSize = @{@"width": @(barcodeWidth), @"height": @(barcodeHeight)}; [changedProps addObject:@"barcodeFrameSize"]; } - + // Since viewprops optional props isn't supported in all RN versions, + // we assume empty arrays mean it's not defined / ignore changes to it. + // if the user/dev wants to NOT define the prop, they can simply use scanBarcode={false} + if (!newProps.allowedBarcodeTypes.empty()) { + folly::dynamic allowedBarcodeTypesDynamic = folly::dynamic::array(); + for (const auto& type : newProps.allowedBarcodeTypes) { + allowedBarcodeTypesDynamic.push_back(type); + } + id allowedBarcodeTypes = CKConvertFollyDynamicToId(allowedBarcodeTypesDynamic); + if (allowedBarcodeTypes != nil && [allowedBarcodeTypes isKindOfClass:NSArray.class]) { + _view.allowedBarcodeTypes = allowedBarcodeTypes; + [changedProps addObject:@"allowedBarcodeTypes"]; + } + } [super updateProps:props oldProps:oldProps]; [_view didSetProps:changedProps]; diff --git a/ios/ReactNativeCameraKit/CameraView.swift b/ios/ReactNativeCameraKit/CameraView.swift index bb5c24f9b..be20e8525 100644 --- a/ios/ReactNativeCameraKit/CameraView.swift +++ b/ios/ReactNativeCameraKit/CameraView.swift @@ -22,9 +22,6 @@ public class CameraView: UIView { // scanner private var lastBarcodeDetectedTime: TimeInterval = 0 private var scannerInterfaceView: ScannerInterfaceView - private var supportedBarcodeType: [CodeFormat] = { - return CodeFormat.allCases - }() // camera private var ratioOverlayView: RatioOverlayView? @@ -50,6 +47,7 @@ public class CameraView: UIView { @objc public var frameColor: UIColor? @objc public var laserColor: UIColor? @objc public var barcodeFrameSize: NSDictionary? + @objc public var allowedBarcodeTypes: NSArray? // other @objc public var onOrientationChange: RCTDirectEventBlock? @@ -82,12 +80,14 @@ public class CameraView: UIView { } private func setupCamera() { if hasPropBeenSetup && hasPermissionBeenGranted && !hasCameraBeenSetup { + let convertedAllowedTypes = convertAllowedBarcodeTypes() + hasCameraBeenSetup = true #if targetEnvironment(macCatalyst) // Force front camera on Mac Catalyst during initial setup - camera.setup(cameraType: .front, supportedBarcodeType: scanBarcode && onReadCode != nil ? supportedBarcodeType : []) + camera.setup(cameraType: .front, supportedBarcodeType: scanBarcode && onReadCode != nil ? convertedAllowedTypes : []) #else - camera.setup(cameraType: cameraType, supportedBarcodeType: scanBarcode && onReadCode != nil ? supportedBarcodeType : []) + camera.setup(cameraType: cameraType, supportedBarcodeType: scanBarcode && onReadCode != nil ? convertedAllowedTypes : []) #endif } } @@ -252,9 +252,11 @@ public class CameraView: UIView { } // Scanner - if changedProps.contains("scanBarcode") || changedProps.contains("onReadCode") { + if changedProps.contains("scanBarcode") || changedProps.contains("onReadCode") || changedProps.contains("allowedBarcodeTypes") { + let convertedAllowedTypes: [CodeFormat] = convertAllowedBarcodeTypes() + camera.isBarcodeScannerEnabled(scanBarcode, - supportedBarcodeTypes: supportedBarcodeType, + supportedBarcodeTypes: convertedAllowedTypes, onBarcodeRead: { [weak self] (barcode, codeFormat) in self?.onBarcodeRead(barcode: barcode, codeFormat: codeFormat) }) @@ -440,6 +442,14 @@ public class CameraView: UIView { onReadCode?(["codeStringValue": barcode,"codeFormat":codeFormat.rawValue]) } + private func convertAllowedBarcodeTypes() -> [CodeFormat] { + guard let allowedTypes = allowedBarcodeTypes as? [String], !allowedTypes.isEmpty else { + return CodeFormat.allCases + } + + return allowedTypes.compactMap { CodeFormat(rawValue: $0) } + } + // MARK: - Gesture selectors @objc func handlePinchToZoomRecognizer(_ pinchRecognizer: UIPinchGestureRecognizer) { diff --git a/ios/ReactNativeCameraKit/CodeFormat.swift b/ios/ReactNativeCameraKit/CodeFormat.swift index bbf3415f1..5371f4970 100644 --- a/ios/ReactNativeCameraKit/CodeFormat.swift +++ b/ios/ReactNativeCameraKit/CodeFormat.swift @@ -12,6 +12,7 @@ enum CodeFormat: String, CaseIterable { case code128 = "code-128" case code39 = "code-39" case code93 = "code-93" + case codabar = "codabar" case ean13 = "ean-13" case ean8 = "ean-8" case itf14 = "itf-14" @@ -20,13 +21,21 @@ enum CodeFormat: String, CaseIterable { case pdf417 = "pdf-417" case aztec = "aztec" case dataMatrix = "data-matrix" + case code39Mod43 = "code-39-mod-43" + case interleaved2of5 = "interleaved-2of5" case unknown = "unknown" // Convert from AVMetadataObject.ObjectType to CodeFormat static func fromAVMetadataObjectType(_ type: AVMetadataObject.ObjectType) -> CodeFormat { + if #available(iOS 15.4, *) { + if (type == .codabar) { + return .codabar + } + } switch type { case .code128: return .code128 case .code39: return .code39 + case .code39Mod43: return .code39Mod43 case .code93: return .code93 case .ean13: return .ean13 case .ean8: return .ean8 @@ -36,15 +45,22 @@ enum CodeFormat: String, CaseIterable { case .pdf417: return .pdf417 case .aztec: return .aztec case .dataMatrix: return .dataMatrix + case .interleaved2of5: return .interleaved2of5 default: return .unknown } } // Convert from CodeFormat to AVMetadataObject.ObjectType func toAVMetadataObjectType() -> AVMetadataObject.ObjectType { + if #available(iOS 15.4, *) { + if (self == .codabar) { + return .codabar + } + } switch self { case .code128: return .code128 case .code39: return .code39 + case .code39Mod43: return .code39Mod43 case .code93: return .code93 case .ean13: return .ean13 case .ean8: return .ean8 @@ -54,7 +70,9 @@ enum CodeFormat: String, CaseIterable { case .pdf417: return .pdf417 case .aztec: return .aztec case .dataMatrix: return .dataMatrix - case .unknown: return .init(rawValue: "unknown") + case .interleaved2of5: return .interleaved2of5 + case .unknown: fallthrough + default: return .init(rawValue: "unknown") } } } diff --git a/src/Camera.android.tsx b/src/Camera.android.tsx index 17309e9f9..56cddbccd 100644 --- a/src/Camera.android.tsx +++ b/src/Camera.android.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { findNodeHandle, processColor } from 'react-native'; -import type { CameraApi } from './types'; +import { supportedCodeFormats, type CameraApi } from './types'; import type { CameraProps } from './CameraProps'; import NativeCamera from './specs/CameraNativeComponent'; import NativeCameraKitModule from './specs/NativeCameraKitModule'; @@ -15,6 +15,8 @@ const Camera = React.forwardRef((props, ref) => { props.maxZoom = props.maxZoom ?? -1; props.scanThrottleDelay = props.scanThrottleDelay ?? -1; + props.allowedBarcodeTypes = props.allowedBarcodeTypes ?? supportedCodeFormats; + React.useImperativeHandle(ref, () => ({ capture: async (options = {}) => { return await NativeCameraKitModule.capture(options, findNodeHandle(nativeRef.current) ?? undefined); diff --git a/src/Camera.ios.tsx b/src/Camera.ios.tsx index 2c5c970f4..45175acff 100644 --- a/src/Camera.ios.tsx +++ b/src/Camera.ios.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { findNodeHandle } from 'react-native'; -import type { CameraApi } from './types'; +import { supportedCodeFormats, type CameraApi } from './types'; import type { CameraProps } from './CameraProps'; import NativeCamera from './specs/CameraNativeComponent'; import NativeCameraKitModule from './specs/NativeCameraKitModule'; @@ -15,6 +15,8 @@ const Camera = React.forwardRef((props, ref) => { props.maxZoom = props.maxZoom ?? -1; props.scanThrottleDelay = props.scanThrottleDelay ?? -1; + props.allowedBarcodeTypes = props.allowedBarcodeTypes ?? supportedCodeFormats; + props.resetFocusTimeout = props.resetFocusTimeout ?? 0; props.resetFocusWhenMotionDetected = props.resetFocusWhenMotionDetected ?? true; diff --git a/src/CameraProps.ts b/src/CameraProps.ts index 923fb1c65..3c4660a47 100644 --- a/src/CameraProps.ts +++ b/src/CameraProps.ts @@ -117,4 +117,5 @@ export interface CameraProps extends ViewProps { shutterPhotoSound?: boolean; onCaptureButtonPressIn?: ({ nativeEvent: {} }) => void; onCaptureButtonPressOut?: ({ nativeEvent: {} }) => void; + allowedBarcodeTypes?: CodeFormat[]; } diff --git a/src/index.ts b/src/index.ts index 528e0956d..0f0c296ff 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,7 @@ import { type TorchMode, type ZoomMode, type ResizeMode, + type CodeFormat, } from './types'; const { CameraKit } = NativeModules; @@ -25,4 +26,13 @@ export const Orientation = { export default CameraKit; export { Camera, CameraType }; -export type { TorchMode, FlashMode, FocusMode, ZoomMode, CameraApi, CaptureData, ResizeMode }; +export type { + TorchMode, + FlashMode, + FocusMode, + ZoomMode, + CameraApi, + CaptureData, + ResizeMode, + CodeFormat, +}; diff --git a/src/specs/CameraNativeComponent.ts b/src/specs/CameraNativeComponent.ts index 62a02ccd7..9a3c530ce 100644 --- a/src/specs/CameraNativeComponent.ts +++ b/src/specs/CameraNativeComponent.ts @@ -54,6 +54,7 @@ export interface NativeProps extends ViewProps { onReadCode?: DirectEventHandler; onCaptureButtonPressIn?: DirectEventHandler<{}>; onCaptureButtonPressOut?: DirectEventHandler<{}>; + allowedBarcodeTypes?: string[]; // not mentioned in props but available on the native side shutterAnimationDuration?: WithDefault; diff --git a/src/types.ts b/src/types.ts index acae32bad..ec6fb1209 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,20 +3,46 @@ export enum CameraType { Back = 'back', } -export type CodeFormat = - | 'code-128' - | 'code-39' - | 'code-93' - | 'codabar' - | 'ean-13' - | 'ean-8' - | 'itf' - | 'upc-e' - | 'qr' - | 'pdf-417' - | 'aztec' - | 'data-matrix' - | 'unknown'; +const codeFormatAndroid = [ + 'code-128', + 'code-39', + 'code-93', + 'codabar', + 'ean-13', + 'ean-8', + 'itf', + 'upc-a', + 'upc-e', + 'qr', + 'pdf-417', + 'aztec', + 'data-matrix', + 'unknown', +] as const; + +const codeFormatIOS = [ + 'code-128', + 'code-39', + 'code-93', + 'codabar', // only iOS 15.4+ + 'ean-13', + 'ean-8', + 'itf-14', + 'upc-e', + 'qr', + 'pdf-417', + 'aztec', + 'data-matrix', + 'code-39-mod-43', + 'interleaved-2of5', +] as const; + +export const supportedCodeFormats = Array.from(new Set([...codeFormatAndroid, ...codeFormatIOS])); + +type CodeFormatAndroid = (typeof codeFormatAndroid)[number]; +type CodeFormatIOS = (typeof codeFormatIOS)[number]; + +export type CodeFormat = CodeFormatAndroid | CodeFormatIOS | 'unknown'; export type TorchMode = 'on' | 'off';