Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
13 changes: 7 additions & 6 deletions README.md

Large diffs are not rendered by default.

72 changes: 58 additions & 14 deletions android/src/main/java/com/rncamerakit/CKCamera.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<CodeFormat>? = null

private fun getActivity() : Activity {
return currentContext.currentActivity!!
Expand Down Expand Up @@ -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 ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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<CodeFormat>()

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()
Expand All @@ -723,6 +763,10 @@ class CKCamera(context: ThemedReactContext) : FrameLayout(context), LifecycleObs
return false
}

private fun convertAllowedBarcodeTypes(): Set<Int> {
return allowedBarcodeTypes?.map { it.toBarcodeType() }?.toSet() ?: emptySet()
}

companion object {

private const val TAG = "CameraKit"
Expand Down
7 changes: 7 additions & 0 deletions android/src/main/java/com/rncamerakit/CodeFormat.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -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
Expand All @@ -45,12 +47,17 @@ 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
Barcode.FORMAT_AZTEC -> AZTEC
Barcode.FORMAT_DATA_MATRIX -> DATA_MATRIX
else -> UNKNOWN
}

fun fromName(name: String?): CodeFormat? {
return values().firstOrNull { it.code == name }
}
}
}
5 changes: 5 additions & 0 deletions android/src/newarch/java/com/rncamerakit/CKCameraManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ class CKCameraManager(context: ReactApplicationContext) : SimpleViewManager<CKCa
view.setShutterPhotoSound(enabled);
}

@ReactProp(name = "allowedBarcodeTypes")
override fun setAllowedBarcodeTypes(view: CKCamera, types: ReadableArray?) {
view.setAllowedBarcodeTypes(types)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to do the conversion to Array<CodeFormat> straight here, then forward result to the view

  • it gets rid of incorrect values right away
  • it avoids doing it on every single barcode scanned

}

@ReactProp(name = "scanThrottleDelay")
override fun setScanThrottleDelay(view: CKCamera?, value: Int) {
view?.setScanThrottleDelay(value)
Expand Down
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2223,7 +2223,7 @@ PODS:
- React-perflogger (= 0.81.0)
- React-utils (= 0.81.0)
- SocketRocket
- ReactNativeCameraKit (16.1.1):
- ReactNativeCameraKit (16.1.3):
- boost
- DoubleConversion
- fast_float
Expand Down Expand Up @@ -2552,7 +2552,7 @@ SPEC CHECKSUMS:
ReactAppDependencyProvider: c91900fa724baee992f01c05eeb4c9e01a807f78
ReactCodegen: a55799cae416c387aeaae3aabc1bc0289ac19cee
ReactCommon: 116d6ee71679243698620d8cd9a9042541e44aa6
ReactNativeCameraKit: b01e637c97fb6eefe43eff31917d1410fc77e1f8
ReactNativeCameraKit: 269e2abf46202f729ac49a07829d0b6d1b5a91ad
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Yoga: 00013dd9cde63a2d98e8002fcc4f5ddb66c10782

Expand Down
5 changes: 2 additions & 3 deletions example/src/BarcodeScreenExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,13 @@ const BarcodeExample = ({ onBack }: { onBack: () => 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);

}}
/>
</View>
Expand Down Expand Up @@ -233,8 +233,7 @@ const styles = StyleSheet.create({
backBtnContainer: {
alignItems: 'flex-start',
},
captureButtonContainer: {
},
captureButtonContainer: {},
textNumberContainer: {
position: 'absolute',
top: 0,
Expand Down
1 change: 1 addition & 0 deletions ios/ReactNativeCameraKit/CKCameraManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
15 changes: 14 additions & 1 deletion ios/ReactNativeCameraKit/CKCameraViewComponentView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
24 changes: 17 additions & 7 deletions ios/ReactNativeCameraKit/CameraView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@
// scanner
private var lastBarcodeDetectedTime: TimeInterval = 0
private var scannerInterfaceView: ScannerInterfaceView
private var supportedBarcodeType: [CodeFormat] = {
return CodeFormat.allCases
}()

// camera
private var ratioOverlayView: RatioOverlayView?
Expand All @@ -50,6 +47,7 @@
@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?
Expand Down Expand Up @@ -82,12 +80,14 @@
}
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 : [])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we still need supportedBarcodeType class variable?

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
}
}
Expand Down Expand Up @@ -252,9 +252,11 @@
}

// 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)
})
Expand Down Expand Up @@ -428,7 +430,7 @@
return temporaryFileURL
}

private func onBarcodeRead(barcode: String, codeFormat:CodeFormat) {

Check warning on line 433 in ios/ReactNativeCameraKit/CameraView.swift

View workflow job for this annotation

GitHub Actions / Lint iOS

Colons should be next to the identifier when specifying a type and next to the key in dictionary literals (colon)
// Throttle barcode detection
let now = Date.timeIntervalSinceReferenceDate
guard lastBarcodeDetectedTime + Double(scanThrottleDelay) / 1000 < now else {
Expand All @@ -437,9 +439,17 @@

lastBarcodeDetectedTime = now

onReadCode?(["codeStringValue": barcode,"codeFormat":codeFormat.rawValue])

Check warning on line 442 in ios/ReactNativeCameraKit/CameraView.swift

View workflow job for this annotation

GitHub Actions / Lint iOS

There should be no space before and one after any comma (comma)

Check warning on line 442 in ios/ReactNativeCameraKit/CameraView.swift

View workflow job for this annotation

GitHub Actions / Lint iOS

Colons should be next to the identifier when specifying a type and next to the key in dictionary literals (colon)
}

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) {
Expand All @@ -450,4 +460,4 @@
camera.zoomPinchChange(pinchScale: pinchRecognizer.scale)
}
}
}

Check warning on line 463 in ios/ReactNativeCameraKit/CameraView.swift

View workflow job for this annotation

GitHub Actions / Lint iOS

File should contain 400 lines or less: currently contains 463 (file_length)
20 changes: 19 additions & 1 deletion ios/ReactNativeCameraKit/CodeFormat.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,30 @@
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"
case upce = "upc-e"
case qr = "qr"

Check warning on line 20 in ios/ReactNativeCameraKit/CodeFormat.swift

View workflow job for this annotation

GitHub Actions / Lint iOS

Enum element name 'qr' should be between 3 and 40 characters long (identifier_name)
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 {

Check warning on line 29 in ios/ReactNativeCameraKit/CodeFormat.swift

View workflow job for this annotation

GitHub Actions / Lint iOS

Function should have complexity 10 or less; currently complexity is 16 (cyclomatic_complexity)
if #available(iOS 15.4, *) {
if (type == .codabar) {

Check warning on line 31 in ios/ReactNativeCameraKit/CodeFormat.swift

View workflow job for this annotation

GitHub Actions / Lint iOS

`if`, `for`, `guard`, `switch`, `while`, and `catch` statements shouldn't unnecessarily wrap their conditionals or arguments in parentheses (control_statement)
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
Expand All @@ -36,15 +45,22 @@
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 {

Check warning on line 54 in ios/ReactNativeCameraKit/CodeFormat.swift

View workflow job for this annotation

GitHub Actions / Lint iOS

Function should have complexity 10 or less; currently complexity is 16 (cyclomatic_complexity)
if #available(iOS 15.4, *) {
if (self == .codabar) {

Check warning on line 56 in ios/ReactNativeCameraKit/CodeFormat.swift

View workflow job for this annotation

GitHub Actions / Lint iOS

`if`, `for`, `guard`, `switch`, `while`, and `catch` statements shouldn't unnecessarily wrap their conditionals or arguments in parentheses (control_statement)
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
Expand All @@ -54,7 +70,9 @@
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

Check warning on line 74 in ios/ReactNativeCameraKit/CodeFormat.swift

View workflow job for this annotation

GitHub Actions / Lint iOS

Fallthroughs can only be used if the `case` contains at least one other statement (no_fallthrough_only)
default: return .init(rawValue: "unknown")
}
}
}
4 changes: 3 additions & 1 deletion src/Camera.android.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -15,6 +15,8 @@ const Camera = React.forwardRef<CameraApi, CameraProps>((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);
Expand Down
Loading
Loading