@cleanuidev/react-native-scanner 1.0.0-beta.2 → 1.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +241 -33
- package/Scanner.podspec +1 -1
- package/android/src/main/java/com/scanner/ScannerView.kt +58 -2
- package/android/src/main/java/com/scanner/ScannerViewManager.kt +63 -30
- package/ios/CameraManager.swift +143 -7
- package/ios/ScannerView.mm +5 -1
- package/ios/ScannerViewImpl.swift +30 -0
- package/lib/module/ScannerViewNativeComponent.ts +2 -1
- package/lib/typescript/src/ScannerViewNativeComponent.d.ts +2 -2
- package/lib/typescript/src/ScannerViewNativeComponent.d.ts.map +1 -1
- package/package.json +27 -3
- package/src/ScannerViewNativeComponent.ts +2 -1
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
# 📱 React Native Scanner
|
|
4
4
|
|
|
5
|
-
**A powerful, native barcode and QR code scanner for React Native
|
|
5
|
+
**A powerful, native barcode and QR code scanner for React Native with configurable target area scanning. Limit scan area and restrict scanning to a specific region.**
|
|
6
6
|
|
|
7
|
-

|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/@cleanuidev/react-native-scanner)
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
**Built with ❤️ by [CleanUI.dev](https://cleanui.dev)**
|
|
15
15
|
|
|
16
|
-
[Features](#-features) • [Installation](#-installation) • [
|
|
16
|
+
[Features](#-features) • [Why Choose](#-why-choose-this-library) • [Comparison](#-comparison-with-other-libraries) • [Quick Start](#-quick-start) • [Installation](#-installation) • [FAQ](#-frequently-asked-questions)
|
|
17
17
|
|
|
18
18
|
</div>
|
|
19
19
|
|
|
@@ -26,16 +26,66 @@
|
|
|
26
26
|
| Feature | Description |
|
|
27
27
|
|:------:|:-----------|
|
|
28
28
|
| 🚀 **Native Performance** | Built with CameraX & ML Kit (Android) and AVFoundation & Vision (iOS) for optimal performance |
|
|
29
|
-
| 🎯 **
|
|
29
|
+
| 🎯 **Target Area Scanning** | Scan barcodes within configurable target areas for precise detection |
|
|
30
30
|
| 🔦 **Torch Control** | Built-in flashlight/torch control |
|
|
31
31
|
| 📊 **Multiple Formats** | Support for QR codes, Code128, Code39, EAN, UPC, and more |
|
|
32
|
-
| 🎨 **Customizable** | Configurable
|
|
32
|
+
| 🎨 **Customizable** | Configurable target area colors, barcode frame visualization, and scanning behavior |
|
|
33
33
|
| 📱 **Cross Platform** | Android & iOS support (new Fabric architecture) |
|
|
34
34
|
|
|
35
35
|
</div>
|
|
36
36
|
|
|
37
37
|
---
|
|
38
38
|
|
|
39
|
+
## 🎯 Why Choose @cleanuidev/react-native-scanner?
|
|
40
|
+
|
|
41
|
+
- **🎯 Target Area Scanning**: Unlike other libraries, built-in support for limiting scan area, restricting scanning region, and scanning within configurable target areas for precise detection
|
|
42
|
+
- **🚀 High Performance**: Uses native CameraX & ML Kit (Android) and AVFoundation & Vision (iOS) for optimal performance
|
|
43
|
+
- **📱 New Architecture Ready**: Full support for React Native's new architecture (Fabric) on both platforms
|
|
44
|
+
- **🔧 Easy Integration**: Simple API with sensible defaults - get started in minutes
|
|
45
|
+
- **📊 Multiple Scan Strategies**: Process one, all, largest, or sorted barcodes with built-in strategies
|
|
46
|
+
- **🎨 Highly Customizable**: Configurable target areas, barcode frames, and scanning behavior
|
|
47
|
+
- **📦 Lightweight**: Minimal dependencies, optimized bundle size
|
|
48
|
+
- **✅ Active Maintenance**: Regularly updated with bug fixes and new features
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 🆚 Comparison with Other Libraries
|
|
53
|
+
|
|
54
|
+
| Feature | @cleanuidev/react-native-scanner | react-native-vision-camera | expo-camera |
|
|
55
|
+
|---------|--------------------------------|---------------------------|-------------|
|
|
56
|
+
| **Target Area Scanning / Limit Scan Area** | ✅ Built-in (limit scan area, restrict scan region) | ❌ Manual implementation | ❌ Manual implementation |
|
|
57
|
+
| **New Architecture Support** | ✅ Full Fabric support | ✅ Yes | ✅ Yes |
|
|
58
|
+
| **Native Performance** | ✅ CameraX + ML Kit / AVFoundation + Vision | ✅ Yes | ✅ Yes |
|
|
59
|
+
| **Expo Support** | ✅ Dev builds | ✅ Dev builds | ✅ Expo Go |
|
|
60
|
+
| **Barcode Frame Visualization** | ✅ Built-in | ⚠️ Custom | ❌ No |
|
|
61
|
+
| **Multiple Scan Strategies** | ✅ 4 strategies (ONE, ALL, BIGGEST, SORT_BY_BIGGEST) | ⚠️ Custom | ❌ No |
|
|
62
|
+
| **Setup Complexity** | ⭐⭐ Simple | ⭐⭐⭐ Moderate | ⭐ Easy |
|
|
63
|
+
| **Bundle Size** | Small | Medium | Small |
|
|
64
|
+
| **Active Maintenance** | ✅ Active | ✅ Active | ✅ Active |
|
|
65
|
+
| **License** | MIT | MIT | MIT |
|
|
66
|
+
| **Best For** | Barcode/QR scanning with target areas | General camera + scanning | Expo projects |
|
|
67
|
+
|
|
68
|
+
> **💡 When to choose this library**: If you need barcode/QR code scanning with the ability to limit scan area, restrict scanning region, or scan within target areas, this library provides a simpler API and built-in features compared to general-purpose camera libraries.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## ⚡ Quick Start
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm install @cleanuidev/react-native-scanner
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
import ScannerView, { BarcodeFormat } from '@cleanuidev/react-native-scanner';
|
|
80
|
+
|
|
81
|
+
<ScannerView
|
|
82
|
+
barcodeTypes={[BarcodeFormat.QR_CODE]}
|
|
83
|
+
onBarcodeScanned={(e) => console.log(e.nativeEvent)}
|
|
84
|
+
/>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
39
89
|
## 📦 Installation
|
|
40
90
|
|
|
41
91
|
### Install Beta Version
|
|
@@ -53,9 +103,9 @@ yarn add @cleanuidev/react-native-scanner@beta
|
|
|
53
103
|
To install a specific beta version:
|
|
54
104
|
|
|
55
105
|
```bash
|
|
56
|
-
npm install @cleanuidev/react-native-scanner@1.0.0-beta.
|
|
106
|
+
npm install @cleanuidev/react-native-scanner@1.0.0-beta.4
|
|
57
107
|
# or
|
|
58
|
-
yarn add @cleanuidev/react-native-scanner@1.0.0-beta.
|
|
108
|
+
yarn add @cleanuidev/react-native-scanner@1.0.0-beta.4
|
|
59
109
|
```
|
|
60
110
|
|
|
61
111
|
> **Note**: Once the library reaches stable release (1.0.0), you can install it without the `@beta` tag:
|
|
@@ -67,7 +117,71 @@ yarn add @cleanuidev/react-native-scanner@1.0.0-beta.1
|
|
|
67
117
|
|
|
68
118
|
## Platform Setup
|
|
69
119
|
|
|
70
|
-
###
|
|
120
|
+
### Expo Setup
|
|
121
|
+
|
|
122
|
+
> **⚠️ Important**: This library uses native code and requires an **Expo development build**. It cannot run in Expo Go.
|
|
123
|
+
|
|
124
|
+
#### Prerequisites
|
|
125
|
+
|
|
126
|
+
- Expo SDK 53+ (recommended)
|
|
127
|
+
- EAS CLI installed: `npm install -g eas-cli`
|
|
128
|
+
- Expo development build configured
|
|
129
|
+
|
|
130
|
+
#### Installation
|
|
131
|
+
|
|
132
|
+
1. **Install the package:**
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
npx expo install @cleanuidev/react-native-scanner
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
2. **Configure app.json or app.config.js:**
|
|
139
|
+
|
|
140
|
+
Add camera permissions directly to your Expo configuration:
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"expo": {
|
|
145
|
+
"ios": {
|
|
146
|
+
"infoPlist": {
|
|
147
|
+
"NSCameraUsageDescription": "This app needs camera access to scan barcodes and QR codes"
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
"android": {
|
|
151
|
+
"permissions": [
|
|
152
|
+
"android.permission.CAMERA",
|
|
153
|
+
"android.permission.WAKE_LOCK"
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
3. **Create a development build:**
|
|
161
|
+
|
|
162
|
+
For iOS:
|
|
163
|
+
```bash
|
|
164
|
+
eas build --profile development --platform ios
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
For Android:
|
|
168
|
+
```bash
|
|
169
|
+
eas build --profile development --platform android
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Or build locally:
|
|
173
|
+
```bash
|
|
174
|
+
npx expo run:ios
|
|
175
|
+
# or
|
|
176
|
+
npx expo run:android
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
5. **Install the development build on your device:**
|
|
180
|
+
|
|
181
|
+
- For EAS builds: Download and install the build from the EAS dashboard
|
|
182
|
+
- For local builds: The build will be installed automatically
|
|
183
|
+
|
|
184
|
+
### Android Setup (Bare React Native)
|
|
71
185
|
|
|
72
186
|
Add the following permissions to your `android/app/src/main/AndroidManifest.xml`:
|
|
73
187
|
|
|
@@ -79,7 +193,7 @@ Add the following permissions to your `android/app/src/main/AndroidManifest.xml`
|
|
|
79
193
|
<uses-feature android:name="android.hardware.camera.flash" android:required="false" />
|
|
80
194
|
```
|
|
81
195
|
|
|
82
|
-
### iOS Setup
|
|
196
|
+
### iOS Setup (Bare React Native)
|
|
83
197
|
|
|
84
198
|
For iOS, add camera usage description to your `ios/YourApp/Info.plist`:
|
|
85
199
|
|
|
@@ -129,7 +243,7 @@ const styles = StyleSheet.create({
|
|
|
129
243
|
});
|
|
130
244
|
```
|
|
131
245
|
|
|
132
|
-
### Scanner with
|
|
246
|
+
### Scanner with Target Area - Limit Scan Area Example
|
|
133
247
|
|
|
134
248
|
```tsx
|
|
135
249
|
import React, { useState } from 'react';
|
|
@@ -139,12 +253,12 @@ import ScannerView, { BarcodeFormat } from '@cleanuidev/react-native-scanner';
|
|
|
139
253
|
export default function FocusAreaScanner() {
|
|
140
254
|
const [torchEnabled, setTorchEnabled] = useState(false);
|
|
141
255
|
|
|
142
|
-
//
|
|
256
|
+
// Target area configuration - limit scan area to specific region
|
|
143
257
|
const focusAreaConfig = {
|
|
144
|
-
enabled: true, //
|
|
145
|
-
showOverlay: true, // Show the
|
|
146
|
-
size: 300, // Size of the
|
|
147
|
-
color: '#00FF00', // Color of the
|
|
258
|
+
enabled: true, // Limit scan area - only scan barcodes within the target area
|
|
259
|
+
showOverlay: true, // Show overlay outside the target area
|
|
260
|
+
size: 300, // Size of the target area (square)
|
|
261
|
+
color: '#00FF00', // Color of the target area border
|
|
148
262
|
};
|
|
149
263
|
|
|
150
264
|
// Barcode frames configuration
|
|
@@ -198,7 +312,7 @@ export default function FocusAreaScanner() {
|
|
|
198
312
|
| Prop | Type | Default | Description |
|
|
199
313
|
|------|------|---------|-------------|
|
|
200
314
|
| `barcodeTypes` | `BarcodeFormat[]` | `[BarcodeFormat.QR_CODE]` | Array of barcode formats to scan |
|
|
201
|
-
| `focusArea` | `FocusAreaConfig` | - |
|
|
315
|
+
| `focusArea` | `FocusAreaConfig` | - | Limit scan area and restrict scanning region - target area configuration for precise scanning |
|
|
202
316
|
| `barcodeFrames` | `BarcodeFramesConfig` | - | Barcode frame visualization configuration |
|
|
203
317
|
| `torch` | `boolean` | `false` | Enable/disable torch/flashlight |
|
|
204
318
|
| `zoom` | `number` | `1.0` | Camera zoom level |
|
|
@@ -213,10 +327,10 @@ export default function FocusAreaScanner() {
|
|
|
213
327
|
|
|
214
328
|
```tsx
|
|
215
329
|
type FocusAreaConfig = {
|
|
216
|
-
enabled?: boolean; //
|
|
217
|
-
size?: FrameSize; // Size of the
|
|
218
|
-
color?: string; // Color of
|
|
219
|
-
showOverlay?: boolean; // Whether to draw the
|
|
330
|
+
enabled?: boolean; // Limit scan area - whether to restrict scanning to target area only
|
|
331
|
+
size?: FrameSize; // Size of the target area (scan region limit)
|
|
332
|
+
color?: string; // Color of target area border
|
|
333
|
+
showOverlay?: boolean; // Whether to draw overlay outside the target area
|
|
220
334
|
};
|
|
221
335
|
```
|
|
222
336
|
|
|
@@ -226,7 +340,7 @@ type FocusAreaConfig = {
|
|
|
226
340
|
type BarcodeFramesConfig = {
|
|
227
341
|
enabled?: boolean; // Whether to draw frames around detected barcodes
|
|
228
342
|
color?: string; // Color of barcode frames
|
|
229
|
-
onlyInFocusArea?: boolean; // Only show frames for barcodes in
|
|
343
|
+
onlyInFocusArea?: boolean; // Only show frames for barcodes in target area
|
|
230
344
|
};
|
|
231
345
|
```
|
|
232
346
|
|
|
@@ -311,16 +425,16 @@ BarcodeFormat.ITF // ITF (Interleaved 2 of 5)
|
|
|
311
425
|
}
|
|
312
426
|
```
|
|
313
427
|
|
|
314
|
-
##
|
|
428
|
+
## Target Area Configuration - Limit Scan Area
|
|
315
429
|
|
|
316
|
-
The
|
|
430
|
+
The target area feature provides precise control over where barcodes are scanned. You can limit scan area, restrict scanning region, and confine barcode detection to a specific area on the screen:
|
|
317
431
|
|
|
318
|
-
### Basic
|
|
432
|
+
### Basic Target Area
|
|
319
433
|
|
|
320
434
|
```tsx
|
|
321
435
|
<ScannerView
|
|
322
436
|
focusArea={{
|
|
323
|
-
showOverlay: true, // Show
|
|
437
|
+
showOverlay: true, // Show overlay outside the target area
|
|
324
438
|
size: 300, // 300x300 pixel square
|
|
325
439
|
color: '#00FF00', // Green border
|
|
326
440
|
}}
|
|
@@ -328,30 +442,65 @@ The focus area feature provides precise control over where barcodes are scanned:
|
|
|
328
442
|
/>
|
|
329
443
|
```
|
|
330
444
|
|
|
331
|
-
###
|
|
445
|
+
### Limit Scan Area - Restricted Scanning
|
|
446
|
+
|
|
447
|
+
Limit scan area and restrict scanning to a specific region:
|
|
332
448
|
|
|
333
449
|
```tsx
|
|
334
450
|
<ScannerView
|
|
335
451
|
focusArea={{
|
|
336
|
-
enabled: true, //
|
|
337
|
-
showOverlay: true, // Show
|
|
452
|
+
enabled: true, // Limit scan area - only scan barcodes within the target area
|
|
453
|
+
showOverlay: true, // Show overlay outside the target area
|
|
338
454
|
size: 300, // 300x300 pixel square
|
|
339
455
|
color: '#00FF00', // Green border
|
|
340
456
|
}}
|
|
341
|
-
// Only scans within the
|
|
457
|
+
// Only scans within the limited scan area
|
|
458
|
+
/>
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
This configuration restricts scanning region and limits scan area to the defined target area only.
|
|
462
|
+
|
|
463
|
+
### Rectangular Target Area
|
|
464
|
+
|
|
465
|
+
```tsx
|
|
466
|
+
<ScannerView
|
|
467
|
+
focusArea={{
|
|
468
|
+
enabled: true,
|
|
469
|
+
showOverlay: true,
|
|
470
|
+
size: { width: 300, height: 200 }, // Rectangular target area
|
|
471
|
+
color: '#00FF00',
|
|
472
|
+
}}
|
|
473
|
+
/>
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Positioning Target Area with Coordinates
|
|
477
|
+
|
|
478
|
+
You can position the target area anywhere on the screen using percentage-based coordinates (0-100):
|
|
479
|
+
|
|
480
|
+
```tsx
|
|
481
|
+
<ScannerView
|
|
482
|
+
focusArea={{
|
|
483
|
+
enabled: true,
|
|
484
|
+
showOverlay: true,
|
|
485
|
+
size: 300,
|
|
486
|
+
position: { x: 50, y: 50 }, // Center position (default)
|
|
487
|
+
color: '#00FF00',
|
|
488
|
+
}}
|
|
342
489
|
/>
|
|
343
490
|
```
|
|
344
491
|
|
|
345
|
-
|
|
492
|
+
**Example: Position target area at top center**
|
|
346
493
|
|
|
347
494
|
```tsx
|
|
348
495
|
<ScannerView
|
|
349
496
|
focusArea={{
|
|
350
497
|
enabled: true,
|
|
351
498
|
showOverlay: true,
|
|
352
|
-
size:
|
|
499
|
+
size: 250,
|
|
500
|
+
position: { x: 50, y: 25 }, // Top center
|
|
353
501
|
color: '#00FF00',
|
|
354
502
|
}}
|
|
503
|
+
onBarcodeScanned={handleBarcodeScanned}
|
|
355
504
|
/>
|
|
356
505
|
```
|
|
357
506
|
|
|
@@ -371,7 +520,7 @@ The scanner can display visual frames around detected barcodes to help users see
|
|
|
371
520
|
/>
|
|
372
521
|
```
|
|
373
522
|
|
|
374
|
-
### Show Frames Only in
|
|
523
|
+
### Show Frames Only in Target Area
|
|
375
524
|
|
|
376
525
|
```tsx
|
|
377
526
|
<ScannerView
|
|
@@ -383,7 +532,7 @@ The scanner can display visual frames around detected barcodes to help users see
|
|
|
383
532
|
barcodeFrames={{
|
|
384
533
|
enabled: true,
|
|
385
534
|
color: '#FF0000',
|
|
386
|
-
onlyInFocusArea: true, // Only show frames for barcodes in
|
|
535
|
+
onlyInFocusArea: true, // Only show frames for barcodes in target area
|
|
387
536
|
}}
|
|
388
537
|
/>
|
|
389
538
|
```
|
|
@@ -551,6 +700,52 @@ const requestCameraPermission = async () => {
|
|
|
551
700
|
|
|
552
701
|
See the `example/` directory for complete working examples, including the "New Props Example" that demonstrates the updated prop structure.
|
|
553
702
|
|
|
703
|
+
## ❓ Frequently Asked Questions
|
|
704
|
+
|
|
705
|
+
### Is this library compatible with Expo?
|
|
706
|
+
Yes! This library works with Expo development builds (SDK 53+). It does not work with Expo Go due to native code requirements. See the [Expo Setup](#expo-setup) section for detailed instructions.
|
|
707
|
+
|
|
708
|
+
### Does it support React Native's new architecture?
|
|
709
|
+
Yes! Full support for Fabric (new architecture) on both Android and iOS. The library is built with the new architecture in mind.
|
|
710
|
+
|
|
711
|
+
### How does it compare to react-native-vision-camera?
|
|
712
|
+
This library focuses specifically on barcode/QR code scanning with built-in target area support and multiple scan strategies. `react-native-vision-camera` is a general-purpose camera library that requires more setup for barcode scanning. If you only need barcode scanning, this library provides a simpler API and built-in features.
|
|
713
|
+
|
|
714
|
+
### Can I limit the scan area or restrict scanning to a specific region?
|
|
715
|
+
Yes! You can limit the scan area and restrict scanning to a specific region. The target area is optional. By default, you can scan the entire camera view. Set `focusArea.enabled: true` to limit scan area and restrict scanning to a specific region only.
|
|
716
|
+
|
|
717
|
+
### Is it possible to limit scan area?
|
|
718
|
+
Yes! This library provides built-in support for limiting scan area. You can restrict scanning to a specific region using the `focusArea` prop with `enabled: true`. This allows you to limit scan area to a defined region on the screen, perfect for precise barcode detection.
|
|
719
|
+
|
|
720
|
+
### Can I scan barcodes outside the target area?
|
|
721
|
+
Yes! The target area is optional. By default, you can scan the entire camera view. Set `focusArea.enabled: true` to restrict scanning to a specific area.
|
|
722
|
+
|
|
723
|
+
### What barcode formats are supported?
|
|
724
|
+
QR Code, Code128, Code39, EAN-13, EAN-8, UPC-A, UPC-E, Data Matrix, PDF417, Aztec, and ITF (Interleaved 2 of 5). See the [Barcode Formats](#barcode-formats) section for the complete list.
|
|
725
|
+
|
|
726
|
+
### Is it production-ready?
|
|
727
|
+
The library is currently in beta (1.0.0-beta.4) but is stable and actively maintained. Production use is recommended with proper testing. We're working towards a stable 1.0.0 release.
|
|
728
|
+
|
|
729
|
+
### Does it work with React Native 0.83+?
|
|
730
|
+
Yes! The library supports React Native 0.83 and newer versions, including full support for the new architecture.
|
|
731
|
+
|
|
732
|
+
### Can I customize the target area appearance?
|
|
733
|
+
Yes! You can customize the target area size, position, color, and overlay. See the [Target Area Configuration](#target-area-configuration) section for details.
|
|
734
|
+
|
|
735
|
+
### How do I handle multiple barcodes?
|
|
736
|
+
The library supports multiple scan strategies: process all barcodes, only the first one, only the largest, or all sorted by size. See the [Barcode Scan Strategy](#barcode-scan-strategy) section.
|
|
737
|
+
|
|
738
|
+
## 💼 Use Cases
|
|
739
|
+
|
|
740
|
+
- **Retail & E-commerce**: Product barcode scanning for inventory and checkout (limit scan area for precise product detection)
|
|
741
|
+
- **Inventory Management**: Stock tracking and warehouse management systems (restrict scan region for accurate scanning)
|
|
742
|
+
- **Event Management**: QR code ticket scanning and attendee check-in (limit scan area for ticket validation)
|
|
743
|
+
- **Authentication**: QR code-based login and two-factor authentication (restrict scanning region for security)
|
|
744
|
+
- **Payment Systems**: QR code payment processing and transaction scanning (limit scan area for payment codes)
|
|
745
|
+
- **Document Management**: Document barcode scanning and tracking (restrict scan region for document processing)
|
|
746
|
+
- **Asset Tracking**: QR/barcode-based asset management systems (limit scan area for asset identification)
|
|
747
|
+
- **Healthcare**: Medical device and medication barcode scanning (restrict scanning region for medical accuracy)
|
|
748
|
+
|
|
554
749
|
## Contributing
|
|
555
750
|
|
|
556
751
|
1. Fork the repository
|
|
@@ -567,6 +762,19 @@ For bug reports, feature requests, and general questions:
|
|
|
567
762
|
- 📝 [Open an issue](https://github.com/cleanui-dev/react-native-scanner/issues) on GitHub
|
|
568
763
|
- 💬 Use GitHub Discussions for questions and community help
|
|
569
764
|
|
|
765
|
+
### Support the Maintainer
|
|
766
|
+
|
|
767
|
+
If you find this library useful, consider supporting the maintainer/developer directly through donations:
|
|
768
|
+
|
|
769
|
+
- 💰 [Donate via PayPal](https://paypal.me/rahulgwebdev) - Support the maintainer directly and help keep this project maintained and improved
|
|
770
|
+
- ⭐ Star the repository - Show your appreciation and help others discover this library
|
|
771
|
+
|
|
772
|
+
**Your donations help:**
|
|
773
|
+
- 🐛 Maintain and fix bugs
|
|
774
|
+
- ✨ Add new features and improvements
|
|
775
|
+
- 📚 Keep documentation up to date
|
|
776
|
+
- ⚡ Ensure long-term sustainability of the project
|
|
777
|
+
|
|
570
778
|
### Commercial Support & Consulting
|
|
571
779
|
|
|
572
780
|
Need professional help with implementation, custom development, or enterprise support?
|
package/Scanner.podspec
CHANGED
|
@@ -11,7 +11,7 @@ Pod::Spec.new do |s|
|
|
|
11
11
|
s.authors = package["author"]
|
|
12
12
|
|
|
13
13
|
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
-
s.source = { :git => "https://github.com/
|
|
14
|
+
s.source = { :git => "https://github.com/cleanuidev/react-native-scanner.git", :tag => "#{s.version}" }
|
|
15
15
|
|
|
16
16
|
s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
|
|
17
17
|
s.private_header_files = "ios/**/*.h"
|
|
@@ -222,6 +222,7 @@ class ScannerView : FrameLayout {
|
|
|
222
222
|
|
|
223
223
|
if (!hasCameraPermission()) {
|
|
224
224
|
Log.e(TAG, "Camera permission not granted")
|
|
225
|
+
emitOnScannerError("Camera permission not granted", "CAMERA_PERMISSION_DENIED")
|
|
225
226
|
return
|
|
226
227
|
}
|
|
227
228
|
|
|
@@ -331,8 +332,16 @@ class ScannerView : FrameLayout {
|
|
|
331
332
|
// Set up auto-focus on frame center (if focus area is enabled)
|
|
332
333
|
setupAutoFocusOnFrame()
|
|
333
334
|
|
|
335
|
+
// Emit onLoad event after camera is successfully started
|
|
336
|
+
emitOnLoadEvent(success = true)
|
|
337
|
+
|
|
334
338
|
} catch (exc: Exception) {
|
|
335
339
|
Log.e(TAG, "Use case binding failed", exc)
|
|
340
|
+
// Emit onScannerError event for camera initialization failure
|
|
341
|
+
emitOnScannerError(
|
|
342
|
+
error = exc.message ?: "Camera binding failed",
|
|
343
|
+
code = "CAMERA_INITIALIZATION_FAILED"
|
|
344
|
+
)
|
|
336
345
|
}
|
|
337
346
|
}
|
|
338
347
|
|
|
@@ -450,12 +459,14 @@ class ScannerView : FrameLayout {
|
|
|
450
459
|
// Debounce: Prevent rapid duplicate emissions
|
|
451
460
|
// If we've emitted recently (within debounce interval), skip this emission
|
|
452
461
|
// This prevents multiple alerts when pauseScanning is set but detection callbacks are still in flight
|
|
462
|
+
Log.d(TAG, "📊 Debounce check: interval=${barcodeEmissionDebounceInterval}ms, timeSinceLast=${timeSinceLastEmission}ms")
|
|
453
463
|
if (timeSinceLastEmission < barcodeEmissionDebounceInterval) {
|
|
454
|
-
Log.d(TAG, "⏭️ Skipping barcode emission (debounced, last emission was ${timeSinceLastEmission}ms ago)")
|
|
464
|
+
Log.d(TAG, "⏭️ Skipping barcode emission (debounced, last emission was ${timeSinceLastEmission}ms ago, interval=${barcodeEmissionDebounceInterval}ms)")
|
|
455
465
|
return@addOnSuccessListener
|
|
456
466
|
}
|
|
457
467
|
|
|
458
468
|
lastBarcodeEmissionTime = currentTime
|
|
469
|
+
Log.d(TAG, "✅ Emitting barcode (interval=${barcodeEmissionDebounceInterval}ms, timeSinceLast=${timeSinceLastEmission}ms)")
|
|
459
470
|
|
|
460
471
|
// Create array of barcode events
|
|
461
472
|
val barcodeEvents = Arguments.createArray()
|
|
@@ -502,6 +513,11 @@ class ScannerView : FrameLayout {
|
|
|
502
513
|
}
|
|
503
514
|
}
|
|
504
515
|
.addOnFailureListener { e ->
|
|
516
|
+
// Emit onScannerError event for barcode detection failure
|
|
517
|
+
emitOnScannerError(
|
|
518
|
+
error = e.message ?: "Barcode detection failed",
|
|
519
|
+
code = "BARCODE_DETECTION_FAILED"
|
|
520
|
+
)
|
|
505
521
|
Log.e(TAG, "Barcode scanning failed", e)
|
|
506
522
|
}
|
|
507
523
|
.addOnCompleteListener {
|
|
@@ -613,8 +629,48 @@ class ScannerView : FrameLayout {
|
|
|
613
629
|
|
|
614
630
|
fun setBarcodeEmissionInterval(intervalSeconds: Double) {
|
|
615
631
|
// Convert seconds to milliseconds, ensure non-negative
|
|
632
|
+
val previousInterval = barcodeEmissionDebounceInterval
|
|
633
|
+
Log.d(TAG, "📊 [ScannerView] setBarcodeEmissionInterval called with: ${intervalSeconds}s")
|
|
616
634
|
barcodeEmissionDebounceInterval = max(0, (intervalSeconds * 1000).toLong())
|
|
617
|
-
Log.d(TAG, "
|
|
635
|
+
Log.d(TAG, "📊 ScannerView.setBarcodeEmissionInterval: ${intervalSeconds}s -> ${barcodeEmissionDebounceInterval}ms (was ${previousInterval}ms)")
|
|
636
|
+
|
|
637
|
+
if (barcodeEmissionDebounceInterval == 0L) {
|
|
638
|
+
Log.d(TAG, "📊 ⚠️ Debouncing DISABLED (interval = 0ms)")
|
|
639
|
+
} else {
|
|
640
|
+
Log.d(TAG, "📊 ✅ Debouncing ENABLED (interval = ${barcodeEmissionDebounceInterval}ms)")
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
private fun emitOnLoadEvent(success: Boolean, error: String? = null) {
|
|
645
|
+
val ctx = reactContext
|
|
646
|
+
if (ctx is ThemedReactContext) {
|
|
647
|
+
ctx.runOnUiQueueThread {
|
|
648
|
+
val eventData = Arguments.createMap().apply {
|
|
649
|
+
putBoolean("success", success)
|
|
650
|
+
if (error != null) {
|
|
651
|
+
putString("error", error)
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
ctx.getJSModule(RCTEventEmitter::class.java)
|
|
655
|
+
.receiveEvent(this@ScannerView.id, "onLoad", eventData)
|
|
656
|
+
Log.d(TAG, "✅ onLoad event emitted: success=$success, error=$error")
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
private fun emitOnScannerError(error: String, code: String) {
|
|
662
|
+
val ctx = reactContext
|
|
663
|
+
if (ctx is ThemedReactContext) {
|
|
664
|
+
ctx.runOnUiQueueThread {
|
|
665
|
+
val eventData = Arguments.createMap().apply {
|
|
666
|
+
putString("error", error)
|
|
667
|
+
putString("code", code)
|
|
668
|
+
}
|
|
669
|
+
ctx.getJSModule(RCTEventEmitter::class.java)
|
|
670
|
+
.receiveEvent(this@ScannerView.id, "onScannerError", eventData)
|
|
671
|
+
Log.d(TAG, "✅ onScannerError event emitted: code=$code, error=$error")
|
|
672
|
+
}
|
|
673
|
+
}
|
|
618
674
|
}
|
|
619
675
|
|
|
620
676
|
fun setBarcodeScanStrategy(strategy: String) {
|
|
@@ -6,12 +6,20 @@ import com.facebook.react.bridge.*
|
|
|
6
6
|
import com.facebook.react.module.annotations.ReactModule
|
|
7
7
|
import com.facebook.react.uimanager.SimpleViewManager
|
|
8
8
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
9
|
+
import com.facebook.react.uimanager.ViewManagerDelegate
|
|
9
10
|
import com.facebook.react.uimanager.annotations.ReactProp
|
|
10
11
|
import com.facebook.react.uimanager.events.RCTModernEventEmitter
|
|
12
|
+
import com.facebook.react.viewmanagers.ScannerViewManagerInterface
|
|
13
|
+
import com.facebook.react.viewmanagers.ScannerViewManagerDelegate
|
|
11
14
|
|
|
12
15
|
@ReactModule(name = ScannerViewManager.NAME)
|
|
13
|
-
class ScannerViewManager : SimpleViewManager<ScannerView>()
|
|
16
|
+
class ScannerViewManager : SimpleViewManager<ScannerView>(),
|
|
17
|
+
ScannerViewManagerInterface<ScannerView> {
|
|
18
|
+
private val mDelegate: ViewManagerDelegate<ScannerView> = ScannerViewManagerDelegate(this)
|
|
14
19
|
|
|
20
|
+
override fun getDelegate(): ViewManagerDelegate<ScannerView> {
|
|
21
|
+
return mDelegate
|
|
22
|
+
}
|
|
15
23
|
override fun getName(): String {
|
|
16
24
|
return NAME
|
|
17
25
|
}
|
|
@@ -22,7 +30,7 @@ class ScannerViewManager : SimpleViewManager<ScannerView>() {
|
|
|
22
30
|
|
|
23
31
|
// Barcode configuration
|
|
24
32
|
@ReactProp(name = "barcodeTypes")
|
|
25
|
-
fun setBarcodeTypes(view: ScannerView?, types: ReadableArray?) {
|
|
33
|
+
override fun setBarcodeTypes(view: ScannerView?, types: ReadableArray?) {
|
|
26
34
|
if (types != null) {
|
|
27
35
|
val typeList = mutableListOf<String>()
|
|
28
36
|
for (i in 0 until types.size()) {
|
|
@@ -36,33 +44,33 @@ class ScannerViewManager : SimpleViewManager<ScannerView>() {
|
|
|
36
44
|
|
|
37
45
|
// Focus area configuration
|
|
38
46
|
@ReactProp(name = "focusArea")
|
|
39
|
-
fun setFocusArea(view: ScannerView?, focusArea: ReadableMap?) {
|
|
47
|
+
override fun setFocusArea(view: ScannerView?, focusArea: ReadableMap?) {
|
|
40
48
|
if (focusArea != null) {
|
|
41
|
-
val enabled = focusArea.getBoolean("enabled")
|
|
42
|
-
val showOverlay = focusArea.getBoolean("showOverlay")
|
|
49
|
+
val enabled = focusArea.getBoolean("enabled")
|
|
50
|
+
val showOverlay = focusArea.getBoolean("showOverlay")
|
|
43
51
|
val borderColor = focusArea.getString("borderColor")
|
|
44
52
|
val tintColor = focusArea.getString("tintColor")
|
|
45
53
|
val size = focusArea.getDynamic("size")
|
|
46
54
|
val position = focusArea.getMap("position")
|
|
47
|
-
|
|
55
|
+
|
|
48
56
|
// Set focus area properties
|
|
49
57
|
view?.setFocusAreaEnabled(enabled)
|
|
50
58
|
view?.setEnableFrame(showOverlay)
|
|
51
|
-
|
|
59
|
+
|
|
52
60
|
if (borderColor != null) {
|
|
53
61
|
view?.setBorderColor(borderColor)
|
|
54
62
|
}
|
|
55
|
-
|
|
63
|
+
|
|
56
64
|
if (tintColor != null) {
|
|
57
65
|
view?.setTintColor(tintColor)
|
|
58
66
|
}
|
|
59
|
-
|
|
67
|
+
|
|
60
68
|
if (position != null) {
|
|
61
69
|
val x = position.getDouble("x").toFloat()
|
|
62
70
|
val y = position.getDouble("y").toFloat()
|
|
63
71
|
view?.setPosition(x, y)
|
|
64
72
|
}
|
|
65
|
-
|
|
73
|
+
|
|
66
74
|
if (size != null) {
|
|
67
75
|
val frameSize: FrameSize = when {
|
|
68
76
|
size.type == ReadableType.Number -> FrameSize.Square(size.asInt())
|
|
@@ -76,40 +84,40 @@ class ScannerViewManager : SimpleViewManager<ScannerView>() {
|
|
|
76
84
|
}
|
|
77
85
|
view?.setFrameSize(frameSize)
|
|
78
86
|
}
|
|
79
|
-
|
|
87
|
+
|
|
80
88
|
Log.d("ScannerViewManager", "Focus area configured: enabled=$enabled, showOverlay=$showOverlay, borderColor=$borderColor, tintColor=$tintColor, position=$position")
|
|
81
89
|
}
|
|
82
90
|
}
|
|
83
91
|
|
|
84
92
|
// Barcode frames configuration
|
|
85
93
|
@ReactProp(name = "barcodeFrames")
|
|
86
|
-
fun setBarcodeFrames(view: ScannerView?, barcodeFrames: ReadableMap?) {
|
|
94
|
+
override fun setBarcodeFrames(view: ScannerView?, barcodeFrames: ReadableMap?) {
|
|
87
95
|
if (barcodeFrames != null) {
|
|
88
96
|
val enabled = barcodeFrames.getBoolean("enabled") ?: false
|
|
89
97
|
val color = barcodeFrames.getString("color")
|
|
90
98
|
val onlyInFocusArea = barcodeFrames.getBoolean("onlyInFocusArea") ?: false
|
|
91
|
-
|
|
99
|
+
|
|
92
100
|
// Set barcode frames properties
|
|
93
101
|
view?.setBarcodeFramesEnabled(enabled)
|
|
94
102
|
view?.setShowBarcodeFramesOnlyInFrame(onlyInFocusArea)
|
|
95
|
-
|
|
103
|
+
|
|
96
104
|
if (color != null) {
|
|
97
105
|
view?.setBarcodeFramesColor(color)
|
|
98
106
|
}
|
|
99
|
-
|
|
107
|
+
|
|
100
108
|
Log.d("ScannerViewManager", "Barcode frames configured: enabled=$enabled, onlyInFocusArea=$onlyInFocusArea, color=$color")
|
|
101
109
|
}
|
|
102
110
|
}
|
|
103
111
|
|
|
104
112
|
// Torch control
|
|
105
113
|
@ReactProp(name = "torch")
|
|
106
|
-
fun setTorch(view: ScannerView
|
|
107
|
-
view
|
|
114
|
+
override fun setTorch(view: ScannerView, value: Boolean) {
|
|
115
|
+
view.setTorch(value)
|
|
108
116
|
}
|
|
109
117
|
|
|
110
118
|
// Event handlers
|
|
111
119
|
@ReactProp(name = "onBarcodeScanned")
|
|
112
|
-
|
|
120
|
+
fun setOnBarcodeScanned(view: ScannerView?, onBarcodeScanned: Boolean?) {
|
|
113
121
|
// This prop is used to register the event handler
|
|
114
122
|
// The actual event emission happens in ScannerView.processImage()
|
|
115
123
|
Log.d("ScannerViewManager", "onBarcodeScanned event handler registered")
|
|
@@ -128,33 +136,49 @@ class ScannerViewManager : SimpleViewManager<ScannerView>() {
|
|
|
128
136
|
}
|
|
129
137
|
|
|
130
138
|
@ReactProp(name = "zoom")
|
|
131
|
-
fun setZoom(view: ScannerView?, zoom: Double) {
|
|
139
|
+
override fun setZoom(view: ScannerView?, zoom: Double) {
|
|
132
140
|
view?.setZoom(zoom.toFloat())
|
|
133
141
|
}
|
|
134
142
|
|
|
135
143
|
@ReactProp(name = "pauseScanning")
|
|
136
|
-
fun setPauseScanning(view: ScannerView
|
|
137
|
-
if (
|
|
138
|
-
view
|
|
144
|
+
override fun setPauseScanning(view: ScannerView, value: Boolean) {
|
|
145
|
+
if (value) {
|
|
146
|
+
view.pauseScanning()
|
|
139
147
|
} else {
|
|
140
|
-
view
|
|
148
|
+
view.resumeScanning()
|
|
141
149
|
}
|
|
142
150
|
}
|
|
143
151
|
|
|
144
152
|
@ReactProp(name = "barcodeScanStrategy")
|
|
145
|
-
fun setBarcodeScanStrategy(view: ScannerView?, strategy: String?) {
|
|
153
|
+
override fun setBarcodeScanStrategy(view: ScannerView?, strategy: String?) {
|
|
146
154
|
view?.setBarcodeScanStrategy(strategy ?: "ALL")
|
|
147
155
|
}
|
|
148
156
|
|
|
149
157
|
@ReactProp(name = "keepScreenOn")
|
|
150
|
-
fun setKeepScreenOn(view: ScannerView
|
|
151
|
-
view
|
|
158
|
+
override fun setKeepScreenOn(view: ScannerView, value: Boolean) {
|
|
159
|
+
view.setKeepScreenOnEnabled(value)
|
|
152
160
|
}
|
|
153
161
|
|
|
154
162
|
@ReactProp(name = "barcodeEmissionInterval")
|
|
155
|
-
fun setBarcodeEmissionInterval(view: ScannerView?, interval: Double) {
|
|
156
|
-
|
|
157
|
-
|
|
163
|
+
override fun setBarcodeEmissionInterval(view: ScannerView?, interval: Double) {
|
|
164
|
+
Log.d("ScannerViewManager", "📊 [@ReactProp] barcodeEmissionInterval received: $interval")
|
|
165
|
+
|
|
166
|
+
// WithDefault<Double, 0.5> in TypeScript means codegen handles the default automatically
|
|
167
|
+
// If negative, clamp to 0 (minimum value)
|
|
168
|
+
// If 0, disable debouncing
|
|
169
|
+
// Otherwise, use the provided value (0 or positive)
|
|
170
|
+
val actualInterval = if (interval < 0) {
|
|
171
|
+
Log.d("ScannerViewManager", "📊 Clamping negative interval ($interval) to 0.0 (minimum)")
|
|
172
|
+
0.0 // Negative values clamped to 0 (minimum)
|
|
173
|
+
} else {
|
|
174
|
+
if (interval == 0.0) {
|
|
175
|
+
Log.d("ScannerViewManager", "📊 Using interval: ${interval}s (explicitly set to 0, will disable debouncing)")
|
|
176
|
+
} else {
|
|
177
|
+
Log.d("ScannerViewManager", "📊 Using interval: ${interval}s")
|
|
178
|
+
}
|
|
179
|
+
interval // Use provided value (0 or positive)
|
|
180
|
+
}
|
|
181
|
+
Log.d("ScannerViewManager", "📊 Final interval to set: ${actualInterval}s")
|
|
158
182
|
view?.setBarcodeEmissionInterval(actualInterval)
|
|
159
183
|
}
|
|
160
184
|
|
|
@@ -169,7 +193,16 @@ class ScannerViewManager : SimpleViewManager<ScannerView>() {
|
|
|
169
193
|
"bubbled" to "onBarcodeScanned"
|
|
170
194
|
)
|
|
171
195
|
)
|
|
172
|
-
|
|
196
|
+
map["onLoad"] = mapOf(
|
|
197
|
+
"phasedRegistrationNames" to mapOf(
|
|
198
|
+
"bubbled" to "onLoad"
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
map["onScannerError"] = mapOf(
|
|
202
|
+
"phasedRegistrationNames" to mapOf(
|
|
203
|
+
"bubbled" to "onScannerError"
|
|
204
|
+
)
|
|
205
|
+
)
|
|
173
206
|
return map
|
|
174
207
|
}
|
|
175
208
|
}
|
package/ios/CameraManager.swift
CHANGED
|
@@ -47,6 +47,9 @@ class CameraManager: NSObject, CameraControlProtocol {
|
|
|
47
47
|
private var desiredTorchLevel: Float = 1.0
|
|
48
48
|
private var desiredZoomLevel: CGFloat = 1.0
|
|
49
49
|
|
|
50
|
+
/// Observer for subject area change (refocus when scene changes, like native Camera app)
|
|
51
|
+
private var subjectAreaObserver: NSObjectProtocol?
|
|
52
|
+
|
|
50
53
|
// MARK: - Initialization
|
|
51
54
|
|
|
52
55
|
override init() {
|
|
@@ -81,9 +84,10 @@ class CameraManager: NSObject, CameraControlProtocol {
|
|
|
81
84
|
self.isSessionRunning = true
|
|
82
85
|
}
|
|
83
86
|
|
|
84
|
-
// Apply
|
|
87
|
+
// Apply settings after session is running (native camera app behavior)
|
|
85
88
|
self.applyZoom()
|
|
86
89
|
self.applyTorch()
|
|
90
|
+
self.setupFocusAndExposure()
|
|
87
91
|
|
|
88
92
|
DispatchQueue.main.async {
|
|
89
93
|
self.delegate?.cameraManagerDidStart(self)
|
|
@@ -94,6 +98,7 @@ class CameraManager: NSObject, CameraControlProtocol {
|
|
|
94
98
|
|
|
95
99
|
/// Stop the camera session
|
|
96
100
|
func stopCamera() {
|
|
101
|
+
unregisterSubjectAreaChangeObserver()
|
|
97
102
|
sessionQueue.async { [weak self] in
|
|
98
103
|
guard let self = self, self.isSessionRunning else { return }
|
|
99
104
|
|
|
@@ -126,6 +131,17 @@ class CameraManager: NSObject, CameraControlProtocol {
|
|
|
126
131
|
}
|
|
127
132
|
}
|
|
128
133
|
|
|
134
|
+
/// Set focus and exposure point of interest for better scanning of small barcodes/QR codes.
|
|
135
|
+
/// Call this after the camera has started (e.g. when focus area is known).
|
|
136
|
+
/// - Parameters:
|
|
137
|
+
/// - normalizedX: X in range 0...1 (0.5 = center)
|
|
138
|
+
/// - normalizedY: Y in range 0...1 (0.5 = center)
|
|
139
|
+
func setFocusPointOfInterest(normalizedX: CGFloat, normalizedY: CGFloat) {
|
|
140
|
+
sessionQueue.async { [weak self] in
|
|
141
|
+
self?.applyFocusPointOfInterest(normalizedX: normalizedX, normalizedY: normalizedY)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
129
145
|
/// Check if torch is available
|
|
130
146
|
/// - Returns: True if torch is available
|
|
131
147
|
func isTorchAvailable() -> Bool {
|
|
@@ -144,7 +160,14 @@ class CameraManager: NSObject, CameraControlProtocol {
|
|
|
144
160
|
/// Configure the capture session
|
|
145
161
|
private func configureCaptureSession() {
|
|
146
162
|
captureSession.beginConfiguration()
|
|
147
|
-
|
|
163
|
+
// Prefer full HD for sharper small barcode/QR capture when supported (e.g. iPhone 15)
|
|
164
|
+
if captureSession.canSetSessionPreset(.hd1920x1080) {
|
|
165
|
+
captureSession.sessionPreset = .hd1920x1080
|
|
166
|
+
print("[CameraManager] Session preset: hd1920x1080")
|
|
167
|
+
} else {
|
|
168
|
+
captureSession.sessionPreset = .high
|
|
169
|
+
print("[CameraManager] Session preset: high")
|
|
170
|
+
}
|
|
148
171
|
|
|
149
172
|
// Setup camera input
|
|
150
173
|
guard setupCameraInput() else {
|
|
@@ -178,7 +201,16 @@ class CameraManager: NSObject, CameraControlProtocol {
|
|
|
178
201
|
print("[CameraManager] No camera device available")
|
|
179
202
|
return false
|
|
180
203
|
}
|
|
181
|
-
|
|
204
|
+
|
|
205
|
+
// Bias autofocus towards near objects (ideal for barcodes/QR codes held close to camera).
|
|
206
|
+
// Must be configured before adding the device to an active session to be effective.
|
|
207
|
+
lockDeviceForConfiguration(device) { device in
|
|
208
|
+
if device.isAutoFocusRangeRestrictionSupported {
|
|
209
|
+
device.autoFocusRangeRestriction = .near
|
|
210
|
+
print("[CameraManager] ✅ Auto-focus range restriction set to NEAR")
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
182
214
|
do {
|
|
183
215
|
let input = try AVCaptureDeviceInput(device: device)
|
|
184
216
|
|
|
@@ -261,6 +293,103 @@ class CameraManager: NSObject, CameraControlProtocol {
|
|
|
261
293
|
}
|
|
262
294
|
}
|
|
263
295
|
|
|
296
|
+
// MARK: - Focus and Exposure (for small barcode/QR scanning)
|
|
297
|
+
|
|
298
|
+
/// Enable continuous autofocus and continuous auto-exposure (native Camera app behavior).
|
|
299
|
+
/// Applied after the session is running so the device is fully active.
|
|
300
|
+
private func setupFocusAndExposure() {
|
|
301
|
+
guard let device = currentDevice else { return }
|
|
302
|
+
lockDeviceForConfiguration(device) { device in
|
|
303
|
+
// Continuous autofocus: camera keeps refocusing so small codes stay sharp
|
|
304
|
+
if device.isFocusModeSupported(.continuousAutoFocus) {
|
|
305
|
+
device.focusMode = .continuousAutoFocus
|
|
306
|
+
print("[CameraManager] ✅ Continuous autofocus enabled")
|
|
307
|
+
} else if device.isFocusModeSupported(.autoFocus) {
|
|
308
|
+
device.focusMode = .autoFocus
|
|
309
|
+
print("[CameraManager] ✅ Auto focus enabled (continuous not supported)")
|
|
310
|
+
}
|
|
311
|
+
// Continuous auto-exposure: adapts to lighting for better small code readability
|
|
312
|
+
if device.isExposureModeSupported(.continuousAutoExposure) {
|
|
313
|
+
device.exposureMode = .continuousAutoExposure
|
|
314
|
+
print("[CameraManager] ✅ Continuous auto-exposure enabled")
|
|
315
|
+
}
|
|
316
|
+
// Default focus point at center (device coords); overridden by setFocusPointOfInterest
|
|
317
|
+
let center = CGPoint(x: 0.5, y: 0.5)
|
|
318
|
+
if device.isFocusPointOfInterestSupported && device.isFocusModeSupported(.continuousAutoFocus) {
|
|
319
|
+
device.focusPointOfInterest = center
|
|
320
|
+
}
|
|
321
|
+
if device.isExposurePointOfInterestSupported && device.isExposureModeSupported(.continuousAutoExposure) {
|
|
322
|
+
device.exposurePointOfInterest = center
|
|
323
|
+
}
|
|
324
|
+
// Native Camera behavior: refocus when scene changes (lighting, movement, etc.)
|
|
325
|
+
if device.isSubjectAreaChangeMonitoringEnabled == false {
|
|
326
|
+
device.isSubjectAreaChangeMonitoringEnabled = true
|
|
327
|
+
print("[CameraManager] ✅ Subject area change monitoring enabled")
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// Observe scene changes and re-apply continuous focus (like native Camera app)
|
|
331
|
+
registerSubjectAreaChangeObserverIfNeeded()
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/// When the scene changes, re-apply continuous focus so the camera refocuses.
|
|
335
|
+
private func registerSubjectAreaChangeObserverIfNeeded() {
|
|
336
|
+
guard subjectAreaObserver == nil else { return }
|
|
337
|
+
subjectAreaObserver = NotificationCenter.default.addObserver(
|
|
338
|
+
forName: .AVCaptureDeviceSubjectAreaDidChange,
|
|
339
|
+
object: currentDevice,
|
|
340
|
+
queue: .main
|
|
341
|
+
) { [weak self] _ in
|
|
342
|
+
self?.sessionQueue.async {
|
|
343
|
+
self?.reapplyContinuousFocus()
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
print("[CameraManager] Subject area change observer registered")
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private func unregisterSubjectAreaChangeObserver() {
|
|
350
|
+
if let observer = subjectAreaObserver {
|
|
351
|
+
NotificationCenter.default.removeObserver(observer)
|
|
352
|
+
subjectAreaObserver = nil
|
|
353
|
+
print("[CameraManager] Subject area change observer removed")
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/// Re-apply continuous focus and exposure (called when scene changes).
|
|
358
|
+
private func reapplyContinuousFocus() {
|
|
359
|
+
guard let device = currentDevice else { return }
|
|
360
|
+
lockDeviceForConfiguration(device) { device in
|
|
361
|
+
if device.isFocusModeSupported(.continuousAutoFocus) {
|
|
362
|
+
device.focusMode = .continuousAutoFocus
|
|
363
|
+
}
|
|
364
|
+
if device.isExposureModeSupported(.continuousAutoExposure) {
|
|
365
|
+
device.exposureMode = .continuousAutoExposure
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/// Set focus and exposure point of interest (e.g. center of focus area) so the camera
|
|
371
|
+
/// prioritizes that region. The inputs are expected to already be in device coordinates
|
|
372
|
+
/// (0,0 = top-left, 1,1 = bottom-right) as returned by AVCaptureVideoPreviewLayer helpers.
|
|
373
|
+
private func applyFocusPointOfInterest(normalizedX: CGFloat, normalizedY: CGFloat) {
|
|
374
|
+
guard let device = currentDevice else { return }
|
|
375
|
+
let devicePoint = CGPoint(x: normalizedX, y: normalizedY)
|
|
376
|
+
lockDeviceForConfiguration(device) { device in
|
|
377
|
+
if device.isFocusPointOfInterestSupported {
|
|
378
|
+
device.focusPointOfInterest = devicePoint
|
|
379
|
+
if device.isFocusModeSupported(.continuousAutoFocus) {
|
|
380
|
+
device.focusMode = .continuousAutoFocus
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if device.isExposurePointOfInterestSupported {
|
|
384
|
+
device.exposurePointOfInterest = devicePoint
|
|
385
|
+
if device.isExposureModeSupported(.continuousAutoExposure) {
|
|
386
|
+
device.exposureMode = .continuousAutoExposure
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
print("[CameraManager] Focus point of interest set (view: \(normalizedX), \(normalizedY) → device: \(devicePoint.x), \(devicePoint.y))")
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
264
393
|
/// Setup video output
|
|
265
394
|
/// - Returns: True if setup successful
|
|
266
395
|
@discardableResult
|
|
@@ -296,12 +425,18 @@ class CameraManager: NSObject, CameraControlProtocol {
|
|
|
296
425
|
/// Get the default camera device (back camera)
|
|
297
426
|
/// - Returns: The camera device or nil
|
|
298
427
|
private func getDefaultCameraDevice() -> AVCaptureDevice? {
|
|
299
|
-
// Prefer
|
|
428
|
+
// Prefer the physical wide‑angle back camera for barcode scanning.
|
|
429
|
+
// Using the virtual multi‑camera (e.g. builtInTripleCamera) on newer iPhones
|
|
430
|
+
// can cause macro / close‑range focus issues for small barcodes.
|
|
431
|
+
if let wideBack = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) {
|
|
432
|
+
print("[CameraManager] ✅ Using back wide‑angle camera: \(wideBack.localizedName)")
|
|
433
|
+
return wideBack
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Fallback: look for any other back camera with torch (dual, telephoto, etc.)
|
|
300
437
|
let deviceTypes: [AVCaptureDevice.DeviceType] = [
|
|
301
|
-
.builtInTripleCamera,
|
|
302
438
|
.builtInDualCamera,
|
|
303
439
|
.builtInDualWideCamera,
|
|
304
|
-
.builtInWideAngleCamera,
|
|
305
440
|
.builtInTelephotoCamera,
|
|
306
441
|
.builtInUltraWideCamera,
|
|
307
442
|
]
|
|
@@ -313,7 +448,7 @@ class CameraManager: NSObject, CameraControlProtocol {
|
|
|
313
448
|
)
|
|
314
449
|
|
|
315
450
|
if let torchBack = discovery.devices.first(where: { $0.hasTorch }) {
|
|
316
|
-
print("[CameraManager] ✅ Using back camera with torch: \(torchBack.localizedName)")
|
|
451
|
+
print("[CameraManager] ✅ Using fallback back camera with torch: \(torchBack.localizedName)")
|
|
317
452
|
return torchBack
|
|
318
453
|
}
|
|
319
454
|
|
|
@@ -371,6 +506,7 @@ class CameraManager: NSObject, CameraControlProtocol {
|
|
|
371
506
|
}
|
|
372
507
|
|
|
373
508
|
deinit {
|
|
509
|
+
unregisterSubjectAreaChangeObserver()
|
|
374
510
|
stopCamera()
|
|
375
511
|
print("[CameraManager] Deinitialized")
|
|
376
512
|
}
|
package/ios/ScannerView.mm
CHANGED
|
@@ -263,7 +263,11 @@ using namespace facebook::react;
|
|
|
263
263
|
|
|
264
264
|
// Barcode emission interval
|
|
265
265
|
if (oldViewProps.barcodeEmissionInterval != newViewProps.barcodeEmissionInterval) {
|
|
266
|
-
|
|
266
|
+
// Handle negative values (clamp to 0)
|
|
267
|
+
double interval = newViewProps.barcodeEmissionInterval;
|
|
268
|
+
double actualInterval = interval < 0 ? 0.0 : interval;
|
|
269
|
+
|
|
270
|
+
NSNumber *intervalValue = @(actualInterval);
|
|
267
271
|
if ([_scannerImpl respondsToSelector:@selector(setBarcodeEmissionInterval:)]) {
|
|
268
272
|
[_scannerImpl performSelector:@selector(setBarcodeEmissionInterval:) withObject:intervalValue];
|
|
269
273
|
}
|
|
@@ -146,6 +146,8 @@ class ScannerViewImpl: UIView {
|
|
|
146
146
|
@objc func configureFocusArea(_ config: [String: Any]) {
|
|
147
147
|
focusAreaConfig = FocusAreaConfig.from(dict: config)
|
|
148
148
|
focusAreaOverlay.updateFocusArea(config: focusAreaConfig)
|
|
149
|
+
// Update camera focus point to focus area center for better small barcode scanning
|
|
150
|
+
setupFocusPointOfInterest()
|
|
149
151
|
print("[ScannerViewImpl] Focus area configured")
|
|
150
152
|
}
|
|
151
153
|
|
|
@@ -520,10 +522,38 @@ extension ScannerViewImpl: CameraManagerDelegate {
|
|
|
520
522
|
// Check bounds multiple times with delay to catch layout
|
|
521
523
|
self.attemptToAddPreviewLayer(attempt: 0)
|
|
522
524
|
|
|
525
|
+
// Set focus point of interest (center or focus area center) for better small barcode/QR scanning
|
|
526
|
+
self.setupFocusPointOfInterest()
|
|
527
|
+
|
|
523
528
|
self.emitLoadEvent(success: true)
|
|
524
529
|
}
|
|
525
530
|
}
|
|
526
531
|
|
|
532
|
+
/// Set the camera focus point of interest to the focus area center (or screen center)
|
|
533
|
+
/// so continuous autofocus prioritizes that region and small barcodes/QR codes stay sharp.
|
|
534
|
+
private func setupFocusPointOfInterest() {
|
|
535
|
+
guard bounds.width > 0, bounds.height > 0 else { return }
|
|
536
|
+
guard let previewLayer = self.previewLayer else { return }
|
|
537
|
+
|
|
538
|
+
// 1) Get center point in view coordinates (either focus area center or view center)
|
|
539
|
+
let centerPointInView: CGPoint
|
|
540
|
+
if focusAreaConfig.enabled, let focusRect = focusAreaOverlay.getFocusAreaFrame() {
|
|
541
|
+
centerPointInView = CGPoint(x: focusRect.midX, y: focusRect.midY)
|
|
542
|
+
} else {
|
|
543
|
+
centerPointInView = CGPoint(x: bounds.midX, y: bounds.midY)
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// 2) Convert from view/layer space into device focus coordinates using AVFoundation helper
|
|
547
|
+
let pointInLayer = previewLayer.convert(centerPointInView, from: self.layer)
|
|
548
|
+
let devicePoint = previewLayer.captureDevicePointConverted(fromLayerPoint: pointInLayer)
|
|
549
|
+
|
|
550
|
+
// 3) Pass device-normalized coordinates (0,0 top-left, 1,1 bottom-right) to camera manager
|
|
551
|
+
cameraManager.setFocusPointOfInterest(
|
|
552
|
+
normalizedX: devicePoint.x,
|
|
553
|
+
normalizedY: devicePoint.y
|
|
554
|
+
)
|
|
555
|
+
}
|
|
556
|
+
|
|
527
557
|
private func attemptToAddPreviewLayer(attempt: Int) {
|
|
528
558
|
guard !isPreviewLayerAdded else { return }
|
|
529
559
|
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
import type {
|
|
7
7
|
DirectEventHandler,
|
|
8
8
|
Double,
|
|
9
|
+
WithDefault,
|
|
9
10
|
} from 'react-native/Libraries/Types/CodegenTypesNamespace';
|
|
10
11
|
|
|
11
12
|
// Define codegen types locally (no longer exported from react-native in 0.83)
|
|
@@ -111,7 +112,7 @@ export interface NativeProps extends ViewProps {
|
|
|
111
112
|
* Prevents rapid duplicate detections. Set to 0 to disable debouncing.
|
|
112
113
|
* @default 0.5
|
|
113
114
|
*/
|
|
114
|
-
barcodeEmissionInterval?: Double
|
|
115
|
+
barcodeEmissionInterval?: WithDefault<Double, 0.5>;
|
|
115
116
|
|
|
116
117
|
onBarcodeScanned?: DirectEventHandler<BarcodeScannedEventPayload>;
|
|
117
118
|
onScannerError?: DirectEventHandler<ScannerErrorEventPayload>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ViewProps, type NativeSyntheticEvent } from 'react-native';
|
|
2
|
-
import type { DirectEventHandler, Double } from 'react-native/Libraries/Types/CodegenTypesNamespace';
|
|
2
|
+
import type { DirectEventHandler, Double, WithDefault } from 'react-native/Libraries/Types/CodegenTypesNamespace';
|
|
3
3
|
export interface BarcodeScannedEventPayload {
|
|
4
4
|
barcodes: {
|
|
5
5
|
data: string;
|
|
@@ -81,7 +81,7 @@ export interface NativeProps extends ViewProps {
|
|
|
81
81
|
* Prevents rapid duplicate detections. Set to 0 to disable debouncing.
|
|
82
82
|
* @default 0.5
|
|
83
83
|
*/
|
|
84
|
-
barcodeEmissionInterval?: Double
|
|
84
|
+
barcodeEmissionInterval?: WithDefault<Double, 0.5>;
|
|
85
85
|
onBarcodeScanned?: DirectEventHandler<BarcodeScannedEventPayload>;
|
|
86
86
|
onScannerError?: DirectEventHandler<ScannerErrorEventPayload>;
|
|
87
87
|
onLoad?: DirectEventHandler<OnLoadEventPayload>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScannerViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/ScannerViewNativeComponent.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,oBAAoB,EAC1B,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EACV,kBAAkB,EAClB,MAAM,
|
|
1
|
+
{"version":3,"file":"ScannerViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/ScannerViewNativeComponent.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,oBAAoB,EAC1B,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EACV,kBAAkB,EAClB,MAAM,EACN,WAAW,EACZ,MAAM,oDAAoD,CAAC;AAK5D,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE;YACZ,IAAI,EAAE,MAAM,CAAC;YACb,GAAG,EAAE,MAAM,CAAC;YACZ,KAAK,EAAE,MAAM,CAAC;YACd,MAAM,EAAE,MAAM,CAAC;SAChB,CAAC;QACF,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,EAAE,CAAC;CACL;AAED,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAGD,MAAM,MAAM,mBAAmB,GAC7B,oBAAoB,CAAC,0BAA0B,CAAC,CAAC;AACnD,MAAM,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,wBAAwB,CAAC,CAAC;AAC/E,MAAM,MAAM,WAAW,GAAG,oBAAoB,CAAC,kBAAkB,CAAC,CAAC;AAGnE,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAExB;;;;OAIG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAE5B;;OAEG;IACH,aAAa,CAAC,EAAE,mBAAmB,CAAC;IAEpC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAEnD,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,0BAA0B,CAAC,CAAC;IAClE,cAAc,CAAC,EAAE,kBAAkB,CAAC,wBAAwB,CAAC,CAAC;IAC9D,MAAM,CAAC,EAAE,kBAAkB,CAAC,kBAAkB,CAAC,CAAC;CACjD;;AAED,wBAAkE"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cleanuidev/react-native-scanner",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
4
|
-
"description": "scanner",
|
|
3
|
+
"version": "1.0.0-beta.4",
|
|
4
|
+
"description": "High-performance native barcode and QR code scanner for React Native. Scan barcodes in configurable target areas for precise detection. Built with CameraX & ML Kit (Android) and AVFoundation & Vision (iOS).",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
7
7
|
"exports": {
|
|
@@ -45,7 +45,31 @@
|
|
|
45
45
|
"keywords": [
|
|
46
46
|
"react-native",
|
|
47
47
|
"ios",
|
|
48
|
-
"android"
|
|
48
|
+
"android",
|
|
49
|
+
"barcode-scanner",
|
|
50
|
+
"qr-code-scanner",
|
|
51
|
+
"barcode",
|
|
52
|
+
"qr-code",
|
|
53
|
+
"qrcode",
|
|
54
|
+
"scanner",
|
|
55
|
+
"camera",
|
|
56
|
+
"camerax",
|
|
57
|
+
"ml-kit",
|
|
58
|
+
"avfoundation",
|
|
59
|
+
"vision",
|
|
60
|
+
"react-native-scanner",
|
|
61
|
+
"barcode-detection",
|
|
62
|
+
"qr-detection",
|
|
63
|
+
"mobile-scanner",
|
|
64
|
+
"native-scanner",
|
|
65
|
+
"fabric",
|
|
66
|
+
"new architecture",
|
|
67
|
+
"focus area",
|
|
68
|
+
"region scanning",
|
|
69
|
+
"frame scanning",
|
|
70
|
+
"area scanning",
|
|
71
|
+
"targeted-scanning",
|
|
72
|
+
"precise-scanning"
|
|
49
73
|
],
|
|
50
74
|
"repository": {
|
|
51
75
|
"type": "git",
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
import type {
|
|
7
7
|
DirectEventHandler,
|
|
8
8
|
Double,
|
|
9
|
+
WithDefault,
|
|
9
10
|
} from 'react-native/Libraries/Types/CodegenTypesNamespace';
|
|
10
11
|
|
|
11
12
|
// Define codegen types locally (no longer exported from react-native in 0.83)
|
|
@@ -111,7 +112,7 @@ export interface NativeProps extends ViewProps {
|
|
|
111
112
|
* Prevents rapid duplicate detections. Set to 0 to disable debouncing.
|
|
112
113
|
* @default 0.5
|
|
113
114
|
*/
|
|
114
|
-
barcodeEmissionInterval?: Double
|
|
115
|
+
barcodeEmissionInterval?: WithDefault<Double, 0.5>;
|
|
115
116
|
|
|
116
117
|
onBarcodeScanned?: DirectEventHandler<BarcodeScannedEventPayload>;
|
|
117
118
|
onScannerError?: DirectEventHandler<ScannerErrorEventPayload>;
|