@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 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
- ![React Native Barcode Scanner Demo - QR Code and Barcode Scanning with Focus Area](./preview.gif)
7
+ ![React Native Barcode Scanner Demo - QR Code and Barcode Scanning with Target Area](./preview.gif)
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/@cleanuidev/react-native-scanner?label=beta&color=blue)](https://www.npmjs.com/package/@cleanuidev/react-native-scanner)
10
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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) • [Documentation](#-documentation)
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
- | 🎯 **Focus Area** | Configurable focus area with optional overlay for precise scanning |
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 focus area colors, barcode frame visualization, and scanning behavior |
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.1
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.1
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
- ### Android Setup
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 Focus Area
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
- // Focus area configuration
256
+ // Target area configuration - limit scan area to specific region
143
257
  const focusAreaConfig = {
144
- enabled: true, // Only scan barcodes within the focus area
145
- showOverlay: true, // Show the focus area overlay
146
- size: 300, // Size of the focus area (square)
147
- color: '#00FF00', // Color of the focus area border
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` | - | Focus area configuration |
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; // Whether to restrict scanning to focus area only
217
- size?: FrameSize; // Size of the focus area
218
- color?: string; // Color of focus area border
219
- showOverlay?: boolean; // Whether to draw the focus area overlay
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 focus area
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
- ## Focus Area Configuration
428
+ ## Target Area Configuration - Limit Scan Area
315
429
 
316
- The focus area feature provides precise control over where barcodes are scanned:
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 Focus Area
432
+ ### Basic Target Area
319
433
 
320
434
  ```tsx
321
435
  <ScannerView
322
436
  focusArea={{
323
- showOverlay: true, // Show visual overlay
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
- ### Focus Area with Restricted Scanning
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, // Only scan within focus area
337
- showOverlay: true, // Show visual overlay
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 focus area
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
- ### Rectangular Focus Area
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: { width: 300, height: 200 }, // Rectangular focus area
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 Focus Area
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 focus area
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/rahulgwebdev/react-native-scanner.git", :tag => "#{s.version}" }
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, "Barcode emission interval set to: ${barcodeEmissionDebounceInterval}ms")
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") ?: false
42
- val showOverlay = focusArea.getBoolean("showOverlay") ?: false
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?, enabled: Boolean?) {
107
- view?.setTorch(enabled ?: false)
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
- fun setOnBarcodeScanned(view: ScannerView?, onBarcodeScanned: Boolean?) {
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?, pause: Boolean?) {
137
- if (pause == true) {
138
- view?.pauseScanning()
144
+ override fun setPauseScanning(view: ScannerView, value: Boolean) {
145
+ if (value) {
146
+ view.pauseScanning()
139
147
  } else {
140
- view?.resumeScanning()
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?, keepOn: Boolean?) {
151
- view?.setKeepScreenOnEnabled(keepOn ?: true)
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
- // Use 0.5 as default if interval is 0 or negative (React Native may pass 0 for undefined)
157
- val actualInterval = if (interval > 0) interval else 0.5
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
- // Add more events here if needed
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
  }
@@ -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 any desired settings now that the session/device exists
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
- captureSession.sessionPreset = .high
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 a BACK camera that supports torch.
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
  }
@@ -263,7 +263,11 @@ using namespace facebook::react;
263
263
 
264
264
  // Barcode emission interval
265
265
  if (oldViewProps.barcodeEmissionInterval != newViewProps.barcodeEmissionInterval) {
266
- NSNumber *intervalValue = @(newViewProps.barcodeEmissionInterval);
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":"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"}
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.2",
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>;