Skip to content
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ To have Play Services [pre-download the model in the background after the app is
| `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)}` |
| `onReadCode` | Function | Callback when scanner successfully reads barcode. Returned event contains `codeStringValue` and `codeFormat` of the first code, and `codes` with every code detected in the same frame (`{ codeStringValue, codeFormat }[]`). Default: `null`. Ex: `onReadCode={(event) => console.log(event.nativeEvent.codes)}` |
| **Face detection** |
| `faceDetectionEnabled` | `boolean` | Enable real-time face detection. Default: `false` |
| `faceDetectionThrottleMs` | `number` | Minimum milliseconds between `onFaceDetected` emits. Default: `100` |
Expand Down
6 changes: 4 additions & 2 deletions android/src/main/java/com/rncamerakit/CKCamera.kt
Original file line number Diff line number Diff line change
Expand Up @@ -580,11 +580,13 @@ class CKCamera(context: ThemedReactContext) : FrameLayout(context), LifecycleObs
}

private fun onBarcodeRead(barcodes: List<Barcode>) {
val codeFormat = CodeFormat.fromBarcodeType(barcodes.first().format);
val codes = barcodes.map { barcode ->
ReadCodePayload(barcode.rawValue, CodeFormat.fromBarcodeType(barcode.format).code)
}
val surfaceId = UIManagerHelper.getSurfaceId(currentContext)
UIManagerHelper
.getEventDispatcherForReactTag(currentContext, id)
?.dispatchEvent(ReadCodeEvent(surfaceId, id, barcodes.first().rawValue, codeFormat.code))
?.dispatchEvent(ReadCodeEvent(surfaceId, id, codes))
}

private fun onFaceDetected(payloads: List<FacePayload>) {
Expand Down
36 changes: 30 additions & 6 deletions android/src/main/java/com/rncamerakit/events/ReadCodeEvent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,43 @@ import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event

data class ReadCodePayload(
val codeStringValue: String?,
val codeFormat: String,
)

class ReadCodeEvent(
surfaceId: Int,
viewId: Int,
private val codeStringValue: String?,
private val codeFormat: String,
private val codes: List<ReadCodePayload>,
) : Event<ReadCodeEvent>(surfaceId, viewId) {
// Kept for source compatibility with the previous single-code signature
constructor(
surfaceId: Int,
viewId: Int,
codeStringValue: String?,
codeFormat: String,
) : this(surfaceId, viewId, listOf(ReadCodePayload(codeStringValue, codeFormat)))

override fun getEventName(): String = EVENT_NAME

override fun getEventData(): WritableMap =
Arguments.createMap().apply {
putString("codeFormat", codeFormat)
putString("codeStringValue", codeStringValue)
override fun getEventData(): WritableMap {
val array = Arguments.createArray()
for (code in codes) {
val map = Arguments.createMap().apply {
putString("codeFormat", code.codeFormat)
putString("codeStringValue", code.codeStringValue)
}
array.pushMap(map)
}
// Top-level fields mirror the first code for backward compatibility
val first = codes.firstOrNull()
return Arguments.createMap().apply {
putString("codeFormat", first?.codeFormat)
putString("codeStringValue", first?.codeStringValue)
putArray("codes", array)
}
}

companion object {
const val EVENT_NAME = "topReadCode"
Expand Down
3 changes: 2 additions & 1 deletion example/src/BarcodeScreenExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,10 @@ const BarcodeExample = ({ onBack }: { onBack: () => void }) => {
barcodeFrameSize={{ width: 300, height: 150 }}
onReadCode={(event) => {
Vibration.vibrate(100);
setBarcode(event.nativeEvent.codeStringValue);
setBarcode(event.nativeEvent.codes.map((code) => code.codeStringValue).join('\n'));
console.log('barcode', event.nativeEvent.codeStringValue);
console.log('codeFormat', event.nativeEvent.codeFormat);
console.log('codes', event.nativeEvent.codes);
}}
/>
</View>
Expand Down
19 changes: 17 additions & 2 deletions ios/ReactNativeCameraKit/CKCameraViewComponentView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,25 @@ - (void)prepareView {
[event valueForKey:@"codeFormat"] == nil
? ""
: std::string([[event valueForKey:@"codeFormat"] UTF8String]);
NSArray *codesArray = [event valueForKey:@"codes"] ?: @[];
std::vector<facebook::react::CKCameraEventEmitter::OnReadCodeCodes> codes;
codes.reserve(codesArray.count);
for (NSDictionary *code in codesArray) {
NSString *value = code[@"codeStringValue"];
NSString *format = code[@"codeFormat"];
codes.push_back({
.codeStringValue = value == nil ? "" : std::string([value UTF8String]),
.codeFormat = format == nil ? "" : std::string([format UTF8String]),
});
}
facebook::react::CKCameraEventEmitter::OnReadCode payload = {
.codeStringValue = codeStringValue,
.codeFormat = codeFormat,
.codes = std::move(codes),
};
std::dynamic_pointer_cast<const facebook::react::CKCameraEventEmitter>(
strongSelf->_eventEmitter)
->onReadCode(
{.codeStringValue = codeStringValue, .codeFormat = codeFormat});
->onReadCode(payload);
}
}];
[_view setOnOrientationChange:^(NSDictionary *event) {
Expand Down
2 changes: 1 addition & 1 deletion ios/ReactNativeCameraKit/CameraProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ protocol CameraProtocol: AnyObject, FocusInterfaceViewDelegate {
func isBarcodeScannerEnabled(
_ isEnabled: Bool,
supportedBarcodeTypes: [CodeFormat],
onBarcodeRead: ((_ barcode: String, _ codeFormat: CodeFormat) -> Void)?)
onBarcodeRead: ((_ codes: [ScannedCode]) -> Void)?)

func update(scannerFrameSize: CGRect?)

Expand Down
16 changes: 11 additions & 5 deletions ios/ReactNativeCameraKit/CameraView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,8 @@ public class CameraView: UIView {
camera.isBarcodeScannerEnabled(
scanBarcode,
supportedBarcodeTypes: convertedAllowedTypes,
onBarcodeRead: { [weak self] (barcode, codeFormat) in
self?.onBarcodeRead(barcode: barcode, codeFormat: codeFormat)
onBarcodeRead: { [weak self] codes in
self?.onBarcodeRead(codes: codes)
})
}

Expand Down Expand Up @@ -482,16 +482,22 @@ public class CameraView: UIView {
return temporaryFileURL
}

private func onBarcodeRead(barcode: String, codeFormat: CodeFormat) {
private func onBarcodeRead(codes: [ScannedCode]) {
// Throttle barcode detection
let now = Date.timeIntervalSinceReferenceDate
guard lastBarcodeDetectedTime + Double(scanThrottleDelay) / 1000 < now else {
guard let first = codes.first,
lastBarcodeDetectedTime + Double(scanThrottleDelay) / 1000 < now else {
return
}

lastBarcodeDetectedTime = now

onReadCode?(["codeStringValue": barcode, "codeFormat": codeFormat.rawValue])
// Top-level fields mirror the first code for backward compatibility
onReadCode?([
"codeStringValue": first.value,
"codeFormat": first.codeFormat.rawValue,
"codes": codes.map { $0.asDictionary },
])
}

private func convertAllowedBarcodeTypes() -> [CodeFormat] {
Expand Down
12 changes: 12 additions & 0 deletions ios/ReactNativeCameraKit/CodeFormat.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,15 @@ enum CodeFormat: String, CaseIterable {
}
}
}

struct ScannedCode {
let value: String
let codeFormat: CodeFormat

var asDictionary: [String: Any] {
return [
"codeStringValue": value,
"codeFormat": codeFormat.rawValue,
]
}
}
28 changes: 16 additions & 12 deletions ios/ReactNativeCameraKit/RealCamera.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
private var maxPhotoQualityPrioritization: MaxPhotoQualityPrioritization?
private var resetFocus: (() -> Void)?
private var focusFinished: (() -> Void)?
private var onBarcodeRead: ((_ barcode: String, _ codeFormat: CodeFormat) -> Void)?
private var onBarcodeRead: ((_ codes: [ScannedCode]) -> Void)?
private var scannerFrameSize: CGRect? = nil
private var barcodeFrameSize: CGSize? = nil
private var onOrientationChange: RCTDirectEventBlock?
Expand Down Expand Up @@ -433,7 +433,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
func isBarcodeScannerEnabled(
_ isEnabled: Bool,
supportedBarcodeTypes supportedBarcodeType: [CodeFormat],
onBarcodeRead: ((_ barcode: String, _ codeFormat: CodeFormat) -> Void)?
onBarcodeRead: ((_ codes: [ScannedCode]) -> Void)?
) {
sessionQueue.async {
self.onBarcodeRead = onBarcodeRead
Expand Down Expand Up @@ -526,18 +526,22 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection
) {
// Try to retrieve the barcode from the metadata extracted
guard
let machineReadableCodeObject = metadataObjects.first
as? AVMetadataMachineReadableCodeObject,
let codeStringValue = machineReadableCodeObject.stringValue
else {
return
// Retrieve every barcode extracted from the frame, keeping AVFoundation's order
let codes: [ScannedCode] = metadataObjects.compactMap { metadataObject in
guard
let machineReadableCodeObject = metadataObject as? AVMetadataMachineReadableCodeObject,
let codeStringValue = machineReadableCodeObject.stringValue
else {
return nil
}
// Determine the barcode type and convert it to CodeFormat
let codeFormat = CodeFormat.fromAVMetadataObjectType(machineReadableCodeObject.type)
return ScannedCode(value: codeStringValue, codeFormat: codeFormat)
}
// Determine the barcode type and convert it to CodeFormat
let barcodeType = CodeFormat.fromAVMetadataObjectType(machineReadableCodeObject.type)

onBarcodeRead?(codeStringValue, barcodeType)
guard !codes.isEmpty else { return }

onBarcodeRead?(codes)
}

// MARK: - Vision orientation
Expand Down
2 changes: 1 addition & 1 deletion ios/ReactNativeCameraKit/SimulatorCamera.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ class SimulatorCamera: CameraProtocol {
func isBarcodeScannerEnabled(
_ isEnabled: Bool,
supportedBarcodeTypes: [CodeFormat],
onBarcodeRead: ((_ barcode: String, _ codeFormat: CodeFormat) -> Void)?
onBarcodeRead: ((_ codes: [ScannedCode]) -> Void)?
) {}
func update(scannerFrameSize: CGRect?) {}

Expand Down
8 changes: 8 additions & 0 deletions src/CameraProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@ import {
} from './types';
import { Orientation } from './index';

export type ReadCode = {
codeStringValue: string;
codeFormat: CodeFormat;
};

export type OnReadCodeData = {
nativeEvent: {
/** First code detected in the frame (same as `codes[0]`) */
codeStringValue: string;
codeFormat: CodeFormat;
/** All codes detected in the same frame, e.g. when several QR codes are visible at once */
codes: ReadCode[];
};
};

Expand Down
4 changes: 4 additions & 0 deletions src/specs/CameraNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNati
type OnReadCodeData = {
codeStringValue: string;
codeFormat: string;
codes: {
codeStringValue: string;
codeFormat: string;
}[];
};

type OnOrientationChangeData = {
Expand Down