Compare commits

..

No commits in common. "master" and "v17.0.1" have entirely different histories.

22 changed files with 790 additions and 2578 deletions

1
.gitignore vendored
View File

@ -85,7 +85,6 @@ example/.yarn/*
ios/build/
ios/DerivedData/
dist/
.codegen/
### Xcode ###
*.xcodeproj/*
!*.xcodeproj/project.pbxproj

View File

@ -1,5 +1,4 @@
node_modules/
.codegen/
old-example/
example/
example-js-code/

View File

@ -180,7 +180,7 @@ The library is published to npm as `react-native-camera-kit` with the `files` ar
**Last synchronized upstream commit**: 8e5149a6e6d3902ae87dad50da0d06ec2c61d2b8
**Upstream version**: 17.0.1
**Fork version**: 17.0.3
**Fork version**: 17.0.1
**Last sync date**: 2026-01-17
**Sync status**: success
@ -202,7 +202,7 @@ The library is published to npm as `react-native-camera-kit` with the `files` ar
### Fork-Specific Code Preserved
All QR-only Android architecture preserved:
- `android/build.gradle` - Uses `implementation 'com.github.limpbrains:qr:v0.0.3'`
- `android/build.gradle` - Uses `implementation 'com.github.limpbrains:qr:v0.0.2'`
- `android/src/main/java/com/rncamerakit/QRCodeAnalyzer.kt` - Uses `QRDecoder.decode()` from limpbrains/qr
- `android/src/main/java/com/rncamerakit/CodeFormat.kt` - Simplified enum (no ML Kit conversions)
- `android/src/main/java/com/rncamerakit/CKCamera.kt` - String callback `onQRCodeDetected(String)`

View File

@ -237,40 +237,6 @@ Additionally, the Camera can be used for barcode scanning
| `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)}` |
### detectQRCodeInImage(base64)
Detect and decode a QR code from a static image. Takes a base64-encoded image string. Returns a promise that resolves with the decoded string, or `null` if the image is valid but contains no QR code. Rejects only on actual errors (invalid base64, undecodable image data, detector failure).
```ts
import { detectQRCodeInImage } from 'react-native-camera-kit-no-google';
try {
const decoded = await detectQRCodeInImage(base64ImageData);
if (decoded === null) {
// Image was valid but no QR code was found
} else {
console.log('QR code:', decoded);
}
} catch (e) {
// Invalid image data or internal detection failure — e.message has details
}
```
Rejection error codes: `E_INVALID_IMAGE` (bad base64 or undecodable image) and `E_QR_DETECTION_FAILED` (Android: QR decoder error; iOS: Vision framework error or detector init failure).
This API is exclusive to this fork and is not available in the original react-native-camera-kit.
It does **not** require camera permissions — it works purely on image data. Useful for detecting QR codes from:
- Images picked from the photo library
- Clipboard images
- Files received via deep links or share extensions
| Platform | Implementation |
|----------|---------------|
| iOS (device) | Vision framework (`VNDetectBarcodesRequest`) |
| iOS (simulator) | CoreImage (`CIDetector`) |
| Android | [limpbrains/qr](https://github.com/limpbrains/qr) |
### Imperative API
_Note: Must be called on a valid camera ref_

View File

@ -66,7 +66,7 @@ dependencies {
// If you want to additionally use the CameraX Extensions library
// implementation "androidx.camera:camera-extensions:${camerax_version}"
implementation 'com.github.limpbrains:qr:v0.0.3'
implementation 'com.github.limpbrains:qr:v0.0.2'
}
repositories {
mavenCentral()

View File

@ -1,99 +0,0 @@
package com.rncamerakit
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.util.Base64
import qr.QRDecoder
object ImageQRCodeDecoder {
// Center-crop retry removes a 10% border on each side. Borders often contain
// background clutter (fingers, page edges, UI chrome) that confuses the
// detector without contributing QR data.
private const val CROP_FRACTION = 0.1
// The underlying QR decoder works best on images around ~600px on the longest
// side. Larger inputs waste CPU on pixel scanning; much smaller inputs lose
// the finder-pattern resolution. 600 is the empirical sweet spot.
private const val RETRY_MAX_DIM = 600
/**
* Decodes a QR code from a base64-encoded image.
* Returns the decoded string, or null if no QR code could be found.
* Throws IllegalArgumentException if the input is not a valid image.
*/
fun decode(base64: String): String? {
val imageBytes = Base64.decode(base64, Base64.DEFAULT)
val bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
?: throw IllegalArgumentException("Could not decode base64 image data")
return try {
decodeFromBitmap(bitmap)
} finally {
bitmap.recycle()
}
}
private fun decodeFromBitmap(bitmap: Bitmap): String? {
val rgba = bitmapToRgba(bitmap)
return try {
QRDecoder.decode(bitmap.width, bitmap.height, rgba)
} catch (e: Exception) {
// Retry with cropped-and-scaled image for hard-to-read QR codes
decodeCroppedAndScaled(bitmap)
}
}
private fun decodeCroppedAndScaled(original: Bitmap): String? {
val x = (original.width * CROP_FRACTION).toInt()
val y = (original.height * CROP_FRACTION).toInt()
val cropW = original.width - 2 * x
val cropH = original.height - 2 * y
// Image is too small to retry with a center-crop — treat as "no QR found".
if (cropW < 1 || cropH < 1) return null
val cropped = Bitmap.createBitmap(original, x, y, cropW, cropH)
try {
val longest = maxOf(cropW, cropH)
val scale = if (longest > RETRY_MAX_DIM) RETRY_MAX_DIM.toFloat() / longest else 1f
val scaled = if (scale < 1f) {
Bitmap.createScaledBitmap(cropped, (cropW * scale).toInt(), (cropH * scale).toInt(), true)
} else {
cropped
}
try {
val rgba = bitmapToRgba(scaled)
return try {
QRDecoder.decode(scaled.width, scaled.height, rgba)
} catch (e: Exception) {
null
}
} finally {
if (scaled !== cropped) scaled.recycle()
}
} finally {
cropped.recycle()
}
}
private fun bitmapToRgba(bitmap: Bitmap): ByteArray {
val width = bitmap.width
val height = bitmap.height
val pixels = IntArray(width * height)
bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
val rgba = ByteArray(width * height * 4)
for (i in pixels.indices) {
val pixel = pixels[i]
val offset = i * 4
rgba[offset] = ((pixel shr 16) and 0xFF).toByte() // R
rgba[offset + 1] = ((pixel shr 8) and 0xFF).toByte() // G
rgba[offset + 2] = (pixel and 0xFF).toByte() // B
rgba[offset + 3] = ((pixel shr 24) and 0xFF).toByte() // A
}
return rgba
}
}

View File

@ -2,7 +2,6 @@ package com.rncamerakit
import com.facebook.react.bridge.*
import com.facebook.react.uimanager.UIManagerHelper
import java.util.concurrent.Executors
import com.rncamerakit.NativeCameraKitModuleSpec
@ -39,8 +38,6 @@ class RNCameraKitModule(private val reactContext: ReactApplicationContext) : Nat
const val LANDSCAPE_RIGHT = 3 // ➡️
const val REACT_CLASS = "RNCameraKitModule"
private val qrDecodeExecutor = Executors.newCachedThreadPool()
}
override fun getName(): String {
@ -65,19 +62,6 @@ class RNCameraKitModule(private val reactContext: ReactApplicationContext) : Nat
override fun checkDeviceCameraAuthorizationStatus(promise: Promise?) = Unit
@ReactMethod
override fun detectQRCodeInImage(base64: String, promise: Promise) {
qrDecodeExecutor.execute {
try {
promise.resolve(ImageQRCodeDecoder.decode(base64))
} catch (e: IllegalArgumentException) {
promise.reject("E_INVALID_IMAGE", e.message ?: "Invalid image data", e)
} catch (e: Exception) {
promise.reject("E_QR_DETECTION_FAILED", e.message ?: "QR detection failed", e)
}
}
}
/**
* Captures a photo using the camera.
*

View File

@ -2,7 +2,6 @@ package com.rncamerakit
import com.facebook.react.bridge.*
import com.facebook.react.uimanager.UIManagerHelper
import java.util.concurrent.Executors
/**
* Native module for interacting with the camera in React Native applications.
@ -37,8 +36,6 @@ class RNCameraKitModule(private val reactContext: ReactApplicationContext) : Rea
const val LANDSCAPE_RIGHT = 3 // ➡️
const val REACT_CLASS = "RNCameraKitModule"
private val qrDecodeExecutor = Executors.newCachedThreadPool()
}
override fun getName(): String {
@ -63,19 +60,6 @@ class RNCameraKitModule(private val reactContext: ReactApplicationContext) : Rea
fun checkDeviceCameraAuthorizationStatus(promise: Promise?) = Unit
@ReactMethod
fun detectQRCodeInImage(base64: String, promise: Promise) {
qrDecodeExecutor.execute {
try {
promise.resolve(ImageQRCodeDecoder.decode(base64))
} catch (e: IllegalArgumentException) {
promise.reject("E_INVALID_IMAGE", e.message ?: "Invalid image data", e)
} catch (e: Exception) {
promise.reject("E_QR_DETECTION_FAILED", e.message ?: "QR detection failed", e)
}
}
}
/**
* Captures a photo using the camera.
*

View File

@ -10,7 +10,6 @@
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
<string>3B52.1</string>
</array>
</dict>
<dict>

View File

@ -1719,34 +1719,6 @@ PODS:
- React-RCTFBReactNativeSpec
- ReactCommon/turbomodule/core
- SocketRocket
- react-native-image-picker (7.2.3):
- boost
- DoubleConversion
- fast_float
- fmt
- glog
- hermes-engine
- RCT-Folly
- RCT-Folly/Fabric
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-jsi
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- React-NativeModulesApple (0.81.0):
- boost
- DoubleConversion
@ -2251,7 +2223,7 @@ PODS:
- React-perflogger (= 0.81.0)
- React-utils (= 0.81.0)
- SocketRocket
- ReactNativeCameraKit (17.0.4):
- ReactNativeCameraKit (17.0.1):
- boost
- DoubleConversion
- fast_float
@ -2324,7 +2296,6 @@ DEPENDENCIES:
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
- React-Mapbuffer (from `../node_modules/react-native/ReactCommon`)
- React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`)
- react-native-image-picker (from `../node_modules/react-native-image-picker`)
- React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
- React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`)
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
@ -2445,8 +2416,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon"
React-microtasksnativemodule:
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks"
react-native-image-picker:
:path: "../node_modules/react-native-image-picker"
React-NativeModulesApple:
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios"
React-oscompat:
@ -2553,7 +2522,6 @@ SPEC CHECKSUMS:
React-logger: 04ce9229cb57db2c2a8164eaec1105f89da7fb22
React-Mapbuffer: e402e7a0535b2213c50727553621480fe8cd8ade
React-microtasksnativemodule: a63ce5595016996a9bac1f10c70a7a7fe6506649
react-native-image-picker: b16541b587b275e81a12f9b82f215c5e9b0ccbf3
React-NativeModulesApple: b3766e1f87b08064ebc459b9e1538da2447ca874
React-oscompat: 34f3d3c06cadcbc470bc4509c717fb9b919eaa8b
React-perflogger: a1edb025fd5d44f61bf09307e248f7608d7b2dcf
@ -2584,7 +2552,7 @@ SPEC CHECKSUMS:
ReactAppDependencyProvider: c91900fa724baee992f01c05eeb4c9e01a807f78
ReactCodegen: a55799cae416c387aeaae3aabc1bc0289ac19cee
ReactCommon: 116d6ee71679243698620d8cd9a9042541e44aa6
ReactNativeCameraKit: 21c717ad3fc6f040b4293a07c4300aae3f1007d0
ReactNativeCameraKit: ee0b7f2b655e3416881772468fd4f8c70ec73c9d
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Yoga: 00013dd9cde63a2d98e8002fcc4f5ddb66c10782

View File

@ -15,8 +15,7 @@
"dependencies": {
"react": "19.1.0",
"react-native": "0.81.0",
"react-native-camera-kit": "link:../",
"react-native-image-picker": "^7.1.2"
"react-native-camera-kit": "link:../"
},
"devDependencies": {
"@babel/core": "^7.25.2",

View File

@ -3,7 +3,6 @@ import { StyleSheet, Text, View, TouchableOpacity, ScrollView, Button, Alert, Te
import BarcodeScreenExample from './BarcodeScreenExample';
import CameraExample from './CameraExample';
import DetectQRExample from './DetectQRExample';
const App = () => {
const [example, setExample] = useState<any>(undefined);
@ -24,7 +23,6 @@ const App = () => {
<Text style={styles.headerText}>React Native Camera Kit</Text>
<Button title="Camera" onPress={() => setExample(<CameraExample onBack={onBack} />)}></Button>
<Button title="Barcode Scanner" onPress={() => setExample(<BarcodeScreenExample onBack={onBack} />)}></Button>
<Button title="Detect QR from Image" onPress={() => setExample(<DetectQRExample onBack={onBack} />)}></Button>
<View>
<Text style={[styles.stressHeader, { marginTop: 12 }]}>Mount Stress Test</Text>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>

View File

@ -1,152 +0,0 @@
import { useState } from 'react';
import { StyleSheet, Text, View, TouchableOpacity, Image, ScrollView, ActivityIndicator } from 'react-native';
import { launchImageLibrary } from 'react-native-image-picker';
import { detectQRCodeInImage } from '../../src';
import SafeAreaView from './SafeAreaView';
const DetectQRExample = ({ onBack }: { onBack: () => void }) => {
const [imageUri, setImageUri] = useState<string | undefined>();
const [result, setResult] = useState<string | undefined>();
const [error, setError] = useState<string | undefined>();
const [loading, setLoading] = useState(false);
const onPickImage = async () => {
setResult(undefined);
setError(undefined);
const response = await launchImageLibrary({
mediaType: 'photo',
selectionLimit: 1,
includeBase64: true,
});
if (response.didCancel || !response.assets?.length) return;
const asset = response.assets[0];
setImageUri(asset.uri);
const base64 = asset.base64;
if (!base64) {
setError('No base64 data returned from image picker');
return;
}
setLoading(true);
try {
const decoded = await detectQRCodeInImage(base64);
setResult(decoded === null ? '(no QR code found)' : decoded);
} catch (e: any) {
setError(e.message ?? 'Detection failed');
} finally {
setLoading(false);
}
};
return (
<View style={styles.screen}>
<SafeAreaView style={styles.header}>
<TouchableOpacity onPress={onBack}>
<Text style={styles.backText}>Back</Text>
</TouchableOpacity>
<Text style={styles.title}>Detect QR from Image</Text>
<View style={{ width: 50 }} />
</SafeAreaView>
<ScrollView contentContainerStyle={styles.content}>
<TouchableOpacity style={styles.pickButton} onPress={onPickImage}>
<Text style={styles.pickButtonText}>Pick Image</Text>
</TouchableOpacity>
{imageUri && <Image source={{ uri: imageUri }} style={styles.preview} resizeMode="contain" />}
{loading && <ActivityIndicator size="large" color="#ffffff" style={{ marginTop: 20 }} />}
{result !== undefined && (
<View style={styles.resultBox}>
<Text style={styles.resultLabel}>Decoded:</Text>
<Text style={styles.resultValue} selectable>
{result}
</Text>
</View>
)}
{error !== undefined && (
<View style={[styles.resultBox, styles.errorBox]}>
<Text style={styles.resultLabel}>Error:</Text>
<Text style={styles.errorValue}>{error}</Text>
</View>
)}
</ScrollView>
</View>
);
};
export default DetectQRExample;
const styles = StyleSheet.create({
screen: {
flex: 1,
backgroundColor: '#000',
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 16,
paddingVertical: 10,
},
backText: {
color: '#fff',
fontSize: 18,
},
title: {
color: '#fff',
fontSize: 18,
fontWeight: 'bold',
},
content: {
alignItems: 'center',
padding: 24,
},
pickButton: {
backgroundColor: '#2196F3',
paddingHorizontal: 32,
paddingVertical: 14,
borderRadius: 8,
},
pickButtonText: {
color: '#fff',
fontSize: 18,
fontWeight: '600',
},
preview: {
width: 280,
height: 280,
marginTop: 24,
borderRadius: 8,
backgroundColor: '#111',
},
resultBox: {
marginTop: 20,
backgroundColor: '#1a3a1a',
borderRadius: 8,
padding: 16,
width: '100%',
},
errorBox: {
backgroundColor: '#3a1a1a',
},
resultLabel: {
color: '#aaa',
fontSize: 14,
marginBottom: 4,
},
resultValue: {
color: '#4caf50',
fontSize: 16,
},
errorValue: {
color: '#f44336',
fontSize: 16,
},
});

View File

@ -4817,11 +4817,6 @@ react-is@^19.1.0:
version "0.0.0"
uid ""
react-native-image-picker@^7.1.2:
version "7.2.3"
resolved "https://registry.yarnpkg.com/react-native-image-picker/-/react-native-image-picker-7.2.3.tgz#9c402591462af256cdd9aed796c28083a48f90cd"
integrity sha512-zKIZUlQNU3EtqizsXSH92zPeve4vpUrsqHu2kkpCxWE9TZhJFZBb+irDsBOY8J21k0+Edgt06TMQGJ+iPUIXyA==
react-native@0.81.0:
version "0.81.0"
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.81.0.tgz#ebb645f3fb2fc2ffb222d2f294ca4e81e6568f15"

View File

@ -4,10 +4,8 @@
//
import AVFoundation
import CoreImage
import Foundation
import React
import Vision
/*
* Class managing the communication between React Native and the native implementation
@ -62,48 +60,4 @@ import Vision
AVCaptureDevice.requestAccess(for: .video, completionHandler: { resolve($0) })
#endif
}
@objc public static func detectQRCodeInImage(_ base64: String,
resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock) {
DispatchQueue.global(qos: .userInitiated).async {
guard let data = Data(base64Encoded: base64, options: .ignoreUnknownCharacters) else {
reject("E_INVALID_IMAGE", "Could not decode base64 image data", nil)
return
}
#if targetEnvironment(simulator)
guard let ciImage = CIImage(data: data) else {
reject("E_INVALID_IMAGE", "Could not decode base64 image data", nil)
return
}
guard let detector = CIDetector(ofType: CIDetectorTypeQRCode,
context: nil,
options: [CIDetectorAccuracy: CIDetectorAccuracyHigh]) else {
reject("E_QR_DETECTION_FAILED", "Could not initialize QR detector", nil)
return
}
let features = detector.features(in: ciImage) as? [CIQRCodeFeature]
let value = features?.first?.messageString
resolve(value?.isEmpty == false ? value : nil)
#else
guard let uiImage = UIImage(data: data),
let cgImage = uiImage.cgImage else {
reject("E_INVALID_IMAGE", "Could not decode base64 image data", nil)
return
}
let request = VNDetectBarcodesRequest()
request.symbologies = [.qr]
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
do {
try handler.perform([request])
} catch {
reject("E_QR_DETECTION_FAILED", "Vision request failed: \(error.localizedDescription)", error)
return
}
let value = request.results?.first?.payloadStringValue
resolve(value?.isEmpty == false ? value : nil)
#endif
}
}
}

View File

@ -483,6 +483,14 @@ public class CameraView: UIView {
return allowedTypes.compactMap { CodeFormat(rawValue: $0) }
}
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) {

View File

@ -58,10 +58,6 @@ RCT_EXPORT_METHOD(capture:(NSDictionary *)options tag:(nonnull NSNumber *)tag re
[CKCameraManager requestDeviceCameraAuthorization:resolve reject:reject];
}
RCT_EXPORT_METHOD(detectQRCodeInImage:(NSString *)base64 resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
[CKCameraManager detectQRCodeInImage:base64 resolve:resolve reject:reject];
}
// Thanks to this guard, we won't compile this code when we build for the old architecture.
#ifdef RCT_NEW_ARCH_ENABLED
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:

View File

@ -7,13 +7,12 @@
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"version": "17.0.4",
"version": "17.0.1",
"description": "A high performance, fully featured, rock solid camera library for React Native applications",
"nativePackage": true,
"scripts": {
"build": "tsc --project tsconfig.json",
"clean": "rm -rf dist/ build/ .codegen/ android/app/build/",
"prepare": "tsc --project tsconfig.json",
"clean": "rm -rf dist/",
"test": "jest",
"lint": "yarn eslint -c .eslintrc.js",
"check-ios": "scripts/check-ios.sh",
@ -21,7 +20,7 @@
"release:beta": "yarn clean && yarn build && yarn publish --tag beta --verbose",
"release:local": "yarn clean && yarn build && tmp=$(mktemp) && yarn pack --filename $tmp.tar.gz && open -R $tmp.tar.gz",
"start": "watchman watch-del-all && node node_modules/react-native/local-cli/cli.js start",
"codegen": "rm -rf .codegen/ && react-native codegen --verbose --path . --platform ios --outputPath .codegen --source library && react-native codegen --verbose --path . --platform android --outputPath .codegen --source library",
"codegen": "react-native codegen --verbose --path . --platform ios --source library && react-native codegen --verbose --path . --platform android --source library",
"bootstrap": "cd example/ && bundle install && yarn && cd ios/ && bundle exec pod install",
"bootstrap-linux": "cd example/ && yarn"
},
@ -31,11 +30,8 @@
"types": "dist/index.d.ts",
"react-native": "src/index",
"files": [
"android/build.gradle",
"android/gradle",
"android/gradlew",
"android/gradlew.bat",
"android/src",
"android",
"build",
"dist",
"ios",
"src",
@ -54,11 +50,9 @@
"@react-native/eslint-config": "0.79.0",
"@react-native/metro-config": "0.79.0",
"@react-native/typescript-config": "0.79.0",
"@types/jest": "^30.0.0",
"@types/react": "^19.0.0",
"@types/react-test-renderer": "^19.0.0",
"eslint": "^8.19.0",
"jest": "^30.3.0",
"prettier": "2.8.8",
"react": "19.0.0",
"react-native": "0.79.0",

View File

@ -1,51 +0,0 @@
jest.mock('react-native', () => ({
NativeModules: { CameraKit: {} },
Platform: { OS: 'ios' },
TurboModuleRegistry: { getEnforcing: jest.fn(() => ({})) },
}));
jest.mock('../specs/NativeCameraKitModule', () => ({
__esModule: true,
default: {
detectQRCodeInImage: jest.fn(),
},
}));
import NativeCameraKitModule from '../specs/NativeCameraKitModule';
import { detectQRCodeInImage } from '../index';
const mockedDetect = NativeCameraKitModule.detectQRCodeInImage as jest.Mock;
describe('detectQRCodeInImage', () => {
beforeEach(() => {
mockedDetect.mockReset();
});
it('forwards the base64 argument to the native module', async () => {
mockedDetect.mockResolvedValueOnce('decoded-value');
await detectQRCodeInImage('base64-data');
expect(mockedDetect).toHaveBeenCalledTimes(1);
expect(mockedDetect).toHaveBeenCalledWith('base64-data');
});
it('resolves with the decoded string from the native module', async () => {
mockedDetect.mockResolvedValueOnce('https://example.com');
await expect(detectQRCodeInImage('xyz')).resolves.toBe('https://example.com');
});
it('resolves with null when no QR code was found', async () => {
mockedDetect.mockResolvedValueOnce(null);
await expect(detectQRCodeInImage('xyz')).resolves.toBeNull();
});
it('propagates native rejections (e.g. invalid image)', async () => {
const err = new Error('Could not decode base64 image data');
mockedDetect.mockRejectedValueOnce(err);
await expect(detectQRCodeInImage('xyz')).rejects.toBe(err);
});
});

View File

@ -1,7 +1,6 @@
import { NativeModules } from 'react-native';
import Camera from './Camera';
import NativeCameraKitModule from './specs/NativeCameraKitModule';
import {
CameraType,
type CameraApi,
@ -16,10 +15,6 @@ import {
const { CameraKit } = NativeModules;
export const detectQRCodeInImage = (base64: string): Promise<string | null> => {
return NativeCameraKitModule.detectQRCodeInImage(base64);
};
// Start with portrait/pointing up, increment while moving counter-clockwise
export const Orientation = {
PORTRAIT: 0, // ⬆️

View File

@ -18,7 +18,6 @@ export interface Spec extends TurboModule {
capture(options?: UnsafeObject, tag?: Double): Promise<CaptureData>;
requestDeviceCameraAuthorization: () => Promise<boolean>;
checkDeviceCameraAuthorizationStatus: () => Promise<boolean>;
detectQRCodeInImage(base64: string): Promise<string | null>;
}
export default TurboModuleRegistry.getEnforcing<Spec>('RNCameraKitModule');

2865
yarn.lock

File diff suppressed because it is too large Load Diff