@cleanuidev/react-native-scanner 1.0.0-beta.1

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.
Files changed (52) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +609 -0
  3. package/Scanner.podspec +20 -0
  4. package/android/build.gradle +90 -0
  5. package/android/gradle.properties +5 -0
  6. package/android/src/main/AndroidManifest.xml +8 -0
  7. package/android/src/main/java/com/scanner/CameraInfoModule.kt +253 -0
  8. package/android/src/main/java/com/scanner/ScannerPackage.kt +21 -0
  9. package/android/src/main/java/com/scanner/ScannerView.kt +783 -0
  10. package/android/src/main/java/com/scanner/ScannerViewManager.kt +181 -0
  11. package/android/src/main/java/com/scanner/utils/BarcodeFrameManager.kt +170 -0
  12. package/android/src/main/java/com/scanner/views/BarcodeFrameOverlayView.kt +43 -0
  13. package/android/src/main/java/com/scanner/views/FocusAreaView.kt +124 -0
  14. package/ios/BarcodeDetectionManager.swift +229 -0
  15. package/ios/BarcodeFrameManager.swift +175 -0
  16. package/ios/BarcodeFrameOverlayView.swift +102 -0
  17. package/ios/CameraManager.swift +396 -0
  18. package/ios/CoordinateTransformer.swift +140 -0
  19. package/ios/FocusAreaOverlayView.swift +161 -0
  20. package/ios/Models.swift +341 -0
  21. package/ios/Protocols.swift +194 -0
  22. package/ios/ScannerView.h +14 -0
  23. package/ios/ScannerView.mm +358 -0
  24. package/ios/ScannerViewImpl.swift +580 -0
  25. package/ios/react-native-scanner-Bridging-Header.h +26 -0
  26. package/lib/module/CameraInfoModule.js +8 -0
  27. package/lib/module/CameraInfoModule.js.map +1 -0
  28. package/lib/module/ScannerViewNativeComponent.ts +121 -0
  29. package/lib/module/hooks/useCameraInfo.js +106 -0
  30. package/lib/module/hooks/useCameraInfo.js.map +1 -0
  31. package/lib/module/index.js +13 -0
  32. package/lib/module/index.js.map +1 -0
  33. package/lib/module/package.json +1 -0
  34. package/lib/module/types.js +47 -0
  35. package/lib/module/types.js.map +1 -0
  36. package/lib/typescript/package.json +1 -0
  37. package/lib/typescript/src/CameraInfoModule.d.ts +8 -0
  38. package/lib/typescript/src/CameraInfoModule.d.ts.map +1 -0
  39. package/lib/typescript/src/ScannerViewNativeComponent.d.ts +91 -0
  40. package/lib/typescript/src/ScannerViewNativeComponent.d.ts.map +1 -0
  41. package/lib/typescript/src/hooks/useCameraInfo.d.ts +25 -0
  42. package/lib/typescript/src/hooks/useCameraInfo.d.ts.map +1 -0
  43. package/lib/typescript/src/index.d.ts +8 -0
  44. package/lib/typescript/src/index.d.ts.map +1 -0
  45. package/lib/typescript/src/types.d.ts +145 -0
  46. package/lib/typescript/src/types.d.ts.map +1 -0
  47. package/package.json +178 -0
  48. package/src/CameraInfoModule.ts +11 -0
  49. package/src/ScannerViewNativeComponent.ts +121 -0
  50. package/src/hooks/useCameraInfo.ts +190 -0
  51. package/src/index.tsx +30 -0
  52. package/src/types.ts +177 -0
@@ -0,0 +1,580 @@
1
+ //
2
+ // ScannerViewImpl.swift
3
+ // react-native-scanner
4
+ //
5
+ // Main scanner view implementation
6
+ //
7
+
8
+ import UIKit
9
+ import AVFoundation
10
+ import Vision
11
+
12
+ /// Main scanner view that manages camera, detection, and overlays
13
+ @objc(ScannerViewImpl)
14
+ @objcMembers
15
+ class ScannerViewImpl: UIView {
16
+
17
+ // MARK: - Public Properties
18
+
19
+ /// Delegate for scanner events
20
+ @objc public weak var delegate: ScannerViewDelegate?
21
+
22
+ // MARK: - Private Properties
23
+
24
+ /// Camera manager for AVFoundation operations
25
+ private let cameraManager: CameraManager
26
+
27
+ /// Barcode detection manager for Vision framework operations
28
+ private let barcodeDetectionManager: BarcodeDetectionManager
29
+
30
+ /// Frame manager for barcode frame lifecycle
31
+ private let frameManager: BarcodeFrameManager
32
+
33
+ /// Overlay view for focus area
34
+ private let focusAreaOverlay: FocusAreaOverlayView
35
+
36
+ /// Overlay view for barcode frames
37
+ private let barcodeFrameOverlay: BarcodeFrameOverlayView
38
+
39
+ /// Reference to the preview layer
40
+ private var previewLayer: AVCaptureVideoPreviewLayer?
41
+
42
+ // Configuration state
43
+ private var focusAreaConfig: FocusAreaConfig
44
+ private var barcodeFramesConfig: BarcodeFramesConfig
45
+ private var scanStrategy: BarcodeScanStrategy
46
+ private var isPaused: Bool
47
+ private var keepScreenOn: Bool
48
+ private var isPreviewLayerAdded: Bool = false
49
+
50
+ // Remember torch state so we can re-apply after camera starts
51
+ private var requestedTorchEnabled: Bool = false
52
+
53
+ // Track visibility so we can stop the camera when this view isn't on-screen (no JS hooks needed)
54
+ private var isViewVisible: Bool = false
55
+
56
+ // Debounce mechanism to prevent multiple rapid barcode detections
57
+ private var lastBarcodeEmissionTime: TimeInterval = 0
58
+ private var barcodeEmissionDebounceInterval: TimeInterval = 0.5 // Default 500ms debounce
59
+
60
+ // MARK: - Initialization
61
+
62
+ @objc override init(frame: CGRect) {
63
+ // Initialize managers and configuration
64
+ self.cameraManager = CameraManager()
65
+ self.barcodeDetectionManager = BarcodeDetectionManager()
66
+ self.frameManager = BarcodeFrameManager()
67
+ self.focusAreaOverlay = FocusAreaOverlayView()
68
+ self.barcodeFrameOverlay = BarcodeFrameOverlayView()
69
+
70
+ // Initialize configuration with defaults
71
+ self.focusAreaConfig = .defaultConfig
72
+ self.barcodeFramesConfig = .defaultConfig
73
+ self.scanStrategy = .defaultStrategy
74
+ self.isPaused = false
75
+ self.keepScreenOn = true
76
+
77
+ super.init(frame: frame)
78
+
79
+ print("[ScannerViewImpl] 🚀 Initializing ScannerViewImpl with frame: \(frame)")
80
+ setupView()
81
+ setupDelegates()
82
+ setupCamera()
83
+ print("[ScannerViewImpl] ✅ ScannerViewImpl initialization complete")
84
+ }
85
+
86
+ @objc convenience init(frame: CGRect, delegate: ScannerViewDelegate?) {
87
+ self.init(frame: frame)
88
+ self.delegate = delegate
89
+ print("[ScannerViewImpl] ✅ Delegate set via convenience initializer")
90
+ }
91
+
92
+ required init?(coder: NSCoder) {
93
+ fatalError("init(coder:) has not been implemented")
94
+ }
95
+
96
+ // MARK: - Setup Methods
97
+
98
+ /// Setup the view hierarchy and layout
99
+ private func setupView() {
100
+ backgroundColor = .black
101
+
102
+ // Add subviews in order (bottom to top)
103
+ addSubview(focusAreaOverlay)
104
+ addSubview(barcodeFrameOverlay)
105
+
106
+ // Configure autoresizing masks
107
+ focusAreaOverlay.autoresizingMask = [.flexibleWidth, .flexibleHeight]
108
+ barcodeFrameOverlay.autoresizingMask = [.flexibleWidth, .flexibleHeight]
109
+
110
+ print("[ScannerViewImpl] View hierarchy setup complete")
111
+ }
112
+
113
+ /// Setup delegates for all managers
114
+ private func setupDelegates() {
115
+ cameraManager.delegate = self
116
+ barcodeDetectionManager.delegate = self
117
+ frameManager.delegate = self
118
+
119
+ // Setup frame manager callback
120
+ frameManager.onFramesChanged = { [weak self] frames in
121
+ self?.barcodeFrameOverlay.setBarcodeBoxes(frames)
122
+ }
123
+
124
+ print("[ScannerViewImpl] Delegates configured")
125
+ }
126
+
127
+ /// Setup the camera and start preview
128
+ private func setupCamera() {
129
+ // Don't start camera here. Fabric/Navigation may create the view before it is on-screen.
130
+ // We'll start/stop based on actual visibility in `updateVisibility`.
131
+ print("[ScannerViewImpl] 📷 Camera will start when view becomes visible")
132
+ }
133
+
134
+ // MARK: - Public Configuration Methods
135
+
136
+ /// Set barcode formats to detect
137
+ /// - Parameter formats: Array of format strings
138
+ @objc func setBarcodeTypes(_ formats: [String]) {
139
+ let barcodeFormats = formats.compactMap { BarcodeFormat(rawValue: $0) }
140
+ barcodeDetectionManager.setBarcodeFormats(barcodeFormats)
141
+ print("[ScannerViewImpl] Barcode types set: \(formats)")
142
+ }
143
+
144
+ /// Configure focus area
145
+ /// - Parameter config: Focus area configuration dictionary
146
+ @objc func configureFocusArea(_ config: [String: Any]) {
147
+ focusAreaConfig = FocusAreaConfig.from(dict: config)
148
+ focusAreaOverlay.updateFocusArea(config: focusAreaConfig)
149
+ print("[ScannerViewImpl] Focus area configured")
150
+ }
151
+
152
+ /// Configure barcode frames
153
+ /// - Parameter config: Barcode frames configuration dictionary
154
+ @objc func configureBarcodeFrames(_ config: [String: Any]) {
155
+ barcodeFramesConfig = BarcodeFramesConfig.from(dict: config)
156
+ barcodeFrameOverlay.updateBarcodeFrames(config: barcodeFramesConfig)
157
+ print("[ScannerViewImpl] Barcode frames configured")
158
+ }
159
+
160
+ /// Set torch enabled/disabled
161
+ /// - Parameter enabled: Whether torch should be enabled
162
+ @objc func setTorchEnabled(_ enabled: NSNumber) {
163
+ let value = enabled.boolValue
164
+ print("[ScannerViewImpl] Torch requested: \(value)")
165
+ requestedTorchEnabled = value
166
+ cameraManager.setTorch(enabled: value)
167
+ }
168
+
169
+ /// Set zoom level
170
+ /// - Parameter zoom: The zoom level
171
+ @objc func setZoomLevel(_ zoom: NSNumber) {
172
+ cameraManager.setZoom(level: CGFloat(zoom.doubleValue))
173
+ }
174
+
175
+ /// Pause or resume scanning
176
+ /// - Parameter paused: Whether scanning should be paused
177
+ @objc func setPauseScanning(_ paused: NSNumber) {
178
+ let value = paused.boolValue
179
+ isPaused = value
180
+ if value {
181
+ barcodeDetectionManager.pauseScanning()
182
+ frameManager.clearAllFrames()
183
+ // Reset debounce timer when pausing to prevent stale emissions
184
+ lastBarcodeEmissionTime = 0
185
+ } else {
186
+ barcodeDetectionManager.resumeScanning()
187
+ // Reset debounce timer when resuming to allow immediate detection
188
+ lastBarcodeEmissionTime = 0
189
+ }
190
+ print("[ScannerViewImpl] Scanning \(value ? "paused" : "resumed")")
191
+ }
192
+
193
+ /// Set scan strategy
194
+ /// - Parameter strategy: Strategy name as string
195
+ @objc func setBarcodeScanStrategy(_ strategy: String) {
196
+ if let scanStrat = BarcodeScanStrategy(rawValue: strategy) {
197
+ scanStrategy = scanStrat
198
+ barcodeDetectionManager.setScanStrategy(scanStrat)
199
+ print("[ScannerViewImpl] Scan strategy set to: \(strategy)")
200
+ }
201
+ }
202
+
203
+ /// Set keep screen on
204
+ /// - Parameter keepOn: Whether to keep screen on
205
+ @objc func setKeepScreenOn(_ keepOn: NSNumber) {
206
+ let value = keepOn.boolValue
207
+ keepScreenOn = value
208
+ UIApplication.shared.isIdleTimerDisabled = value
209
+ print("[ScannerViewImpl] Keep screen on: \(value)")
210
+ }
211
+
212
+ /// Set barcode emission debounce interval
213
+ /// - Parameter interval: Minimum interval in seconds between barcode emissions (0 to disable)
214
+ @objc func setBarcodeEmissionInterval(_ interval: NSNumber) {
215
+ let value = interval.doubleValue
216
+ barcodeEmissionDebounceInterval = max(0.0, value) // Ensure non-negative
217
+ print("[ScannerViewImpl] Barcode emission interval set to: \(barcodeEmissionDebounceInterval)s")
218
+ }
219
+
220
+ // MARK: - Lifecycle Methods
221
+
222
+ override func layoutSubviews() {
223
+ super.layoutSubviews()
224
+
225
+ print("[ScannerViewImpl] 📐 layoutSubviews called with bounds: \(bounds)")
226
+
227
+ // Add preview layer if we have a valid frame and haven't added it yet
228
+ if !isPreviewLayerAdded && bounds.width > 0 && bounds.height > 0 {
229
+ if let preview = cameraManager.getPreviewLayer() {
230
+ self.previewLayer = preview
231
+ preview.frame = bounds
232
+ layer.insertSublayer(preview, at: 0)
233
+ isPreviewLayerAdded = true
234
+ print("[ScannerViewImpl] ✅ Preview layer added in layoutSubviews with frame: \(bounds)")
235
+ } else {
236
+ print("[ScannerViewImpl] ⚠️ Preview layer not ready yet in layoutSubviews")
237
+ }
238
+ }
239
+
240
+ // Update preview layer frame if already added
241
+ if isPreviewLayerAdded {
242
+ previewLayer?.frame = bounds
243
+ }
244
+
245
+ // Update overlay frames
246
+ focusAreaOverlay.frame = bounds
247
+ barcodeFrameOverlay.frame = bounds
248
+ }
249
+
250
+ override func didMoveToWindow() {
251
+ super.didMoveToWindow()
252
+
253
+ let nowVisible = (self.window != nil) && !self.isHidden && self.alpha > 0.01
254
+ updateVisibility(nowVisible)
255
+ }
256
+
257
+ override func didMoveToSuperview() {
258
+ super.didMoveToSuperview()
259
+ let nowVisible = (self.window != nil) && !self.isHidden && self.alpha > 0.01
260
+ updateVisibility(nowVisible)
261
+ }
262
+
263
+ override var isHidden: Bool {
264
+ didSet {
265
+ let nowVisible = (self.window != nil) && !self.isHidden && self.alpha > 0.01
266
+ updateVisibility(nowVisible)
267
+ }
268
+ }
269
+
270
+ override var alpha: CGFloat {
271
+ didSet {
272
+ let nowVisible = (self.window != nil) && !self.isHidden && self.alpha > 0.01
273
+ updateVisibility(nowVisible)
274
+ }
275
+ }
276
+
277
+ private func updateVisibility(_ visible: Bool) {
278
+ guard visible != isViewVisible else { return }
279
+ isViewVisible = visible
280
+
281
+ if visible {
282
+ print("[ScannerViewImpl] 👁️ View became visible -> starting camera")
283
+ if keepScreenOn {
284
+ UIApplication.shared.isIdleTimerDisabled = true
285
+ }
286
+ cameraManager.startCamera()
287
+ } else {
288
+ print("[ScannerViewImpl] 🙈 View not visible -> stopping camera")
289
+ cameraManager.stopCamera()
290
+ // release keep-screen-on when off-screen
291
+ UIApplication.shared.isIdleTimerDisabled = false
292
+ }
293
+ }
294
+
295
+ deinit {
296
+ cameraManager.stopCamera()
297
+ frameManager.shutdown()
298
+ UIApplication.shared.isIdleTimerDisabled = false
299
+ print("[ScannerViewImpl] Deinitialized")
300
+ }
301
+
302
+ // MARK: - Private Helper Methods
303
+
304
+ /// Process detected barcodes according to strategy and filters
305
+ /// - Parameter observations: Raw barcode observations from Vision
306
+ /// - Returns: Filtered and processed barcode results
307
+ private func processBarcodeObservations(_ observations: [VNBarcodeObservation]) -> [BarcodeDetectionResult] {
308
+ guard !observations.isEmpty else { return [] }
309
+
310
+ // Step 1: Filter by focus area if enabled
311
+ let filteredObservations = filterByFocusArea(observations)
312
+ guard !filteredObservations.isEmpty else { return [] }
313
+
314
+ // Step 2: Apply scan strategy
315
+ let strategyObservations = applyScanStrategy(filteredObservations)
316
+ guard !strategyObservations.isEmpty else { return [] }
317
+
318
+ // Step 3: Transform coordinates and create results
319
+ var results: [BarcodeDetectionResult] = []
320
+ for observation in strategyObservations {
321
+ // Transform bounding box from Vision to View coordinates
322
+ let viewRect = CoordinateTransformer.transformVisionRectToViewRect(
323
+ observation.boundingBox,
324
+ viewSize: bounds.size,
325
+ previewLayer: previewLayer
326
+ )
327
+
328
+ // Get the barcode format string
329
+ let formatString = observation.symbology.rawValue
330
+
331
+ // Create result
332
+ if let result = BarcodeDetectionResult.from(
333
+ observation: observation,
334
+ boundingBox: viewRect,
335
+ format: formatString
336
+ ) {
337
+ results.append(result)
338
+ }
339
+ }
340
+
341
+ return results
342
+ }
343
+
344
+ /// Filter barcodes by focus area if enabled
345
+ /// - Parameter observations: Barcode observations to filter
346
+ /// - Returns: Filtered observations
347
+ private func filterByFocusArea(_ observations: [VNBarcodeObservation]) -> [VNBarcodeObservation] {
348
+ guard focusAreaConfig.enabled, let focusRect = focusAreaOverlay.getFocusAreaFrame() else {
349
+ return observations
350
+ }
351
+
352
+ return observations.filter { observation in
353
+ // Transform barcode rect to view coordinates
354
+ let viewRect = CoordinateTransformer.transformVisionRectToViewRect(
355
+ observation.boundingBox,
356
+ viewSize: bounds.size,
357
+ previewLayer: previewLayer
358
+ )
359
+
360
+ // Check if barcode is within focus area
361
+ return focusRect.contains(viewRect)
362
+ }
363
+ }
364
+
365
+ /// Apply scan strategy to barcode observations
366
+ /// - Parameter observations: Barcode observations
367
+ /// - Returns: Processed observations according to strategy
368
+ private func applyScanStrategy(_ observations: [VNBarcodeObservation]) -> [VNBarcodeObservation] {
369
+ guard !observations.isEmpty else { return [] }
370
+
371
+ switch scanStrategy {
372
+ case .one:
373
+ // Return only the first barcode
374
+ return [observations[0]]
375
+
376
+ case .all:
377
+ // Return all barcodes
378
+ return observations
379
+
380
+ case .biggest:
381
+ // Return only the largest barcode by area
382
+ let observationsWithArea = observations.compactMap { observation -> (VNBarcodeObservation, CGFloat)? in
383
+ let rect = observation.boundingBox
384
+ let area = rect.width * rect.height
385
+ return (observation, area)
386
+ }
387
+
388
+ if let biggest = observationsWithArea.max(by: { $0.1 < $1.1 }) {
389
+ return [biggest.0]
390
+ }
391
+ return []
392
+
393
+ case .sortByBiggest:
394
+ // Sort all barcodes by area (largest first)
395
+ let observationsWithArea = observations.compactMap { observation -> (VNBarcodeObservation, CGFloat)? in
396
+ let rect = observation.boundingBox
397
+ let area = rect.width * rect.height
398
+ return (observation, area)
399
+ }
400
+
401
+ return observationsWithArea
402
+ .sorted { $0.1 > $1.1 }
403
+ .map { $0.0 }
404
+ }
405
+ }
406
+
407
+ /// Update barcode frame display
408
+ /// - Parameter observations: Barcode observations to display
409
+ private func updateBarcodeFrameDisplay(_ observations: [VNBarcodeObservation]) {
410
+ guard barcodeFramesConfig.enabled else {
411
+ frameManager.clearAllFrames()
412
+ return
413
+ }
414
+
415
+ let focusRect = focusAreaOverlay.getFocusAreaFrame()
416
+
417
+ var frameDict: [String: CGRect] = [:]
418
+
419
+ for observation in observations {
420
+ guard let barcodeValue = observation.payloadStringValue else { continue }
421
+
422
+ // Transform to view coordinates
423
+ let viewRect = CoordinateTransformer.transformVisionRectToViewRect(
424
+ observation.boundingBox,
425
+ viewSize: bounds.size,
426
+ previewLayer: previewLayer
427
+ )
428
+
429
+ // Filter by focus area if onlyInFocusArea is enabled
430
+ if barcodeFramesConfig.onlyInFocusArea, let focusRect {
431
+ if focusRect.contains(viewRect) {
432
+ frameDict[barcodeValue] = viewRect
433
+ }
434
+ } else {
435
+ frameDict[barcodeValue] = viewRect
436
+ }
437
+ }
438
+
439
+ frameManager.updateBarcodeFrames(frameDict)
440
+ }
441
+
442
+ /// Emit barcodes detected event to React Native
443
+ /// - Parameter results: Detected barcode results
444
+ private func emitBarcodesDetected(_ results: [BarcodeDetectionResult]) {
445
+ // Debounce: Prevent rapid duplicate emissions
446
+ let currentTime = Date().timeIntervalSince1970
447
+ let timeSinceLastEmission = currentTime - lastBarcodeEmissionTime
448
+
449
+ // If we've emitted recently (within debounce interval), skip this emission
450
+ // This prevents multiple alerts when pauseScanning is set but detection callbacks are still in flight
451
+ guard timeSinceLastEmission >= barcodeEmissionDebounceInterval else {
452
+ print("[ScannerViewImpl] ⏭️ Skipping barcode emission (debounced, last emission was \(timeSinceLastEmission)s ago)")
453
+ return
454
+ }
455
+
456
+ lastBarcodeEmissionTime = currentTime
457
+ let barcodesArray = results.map { $0.toDictionary() }
458
+ delegate?.scannerDidDetectBarcodes(barcodesArray)
459
+ print("[ScannerViewImpl] ✅ Barcode emitted (debounced)")
460
+ }
461
+
462
+ /// Emit error event to React Native
463
+ /// - Parameter error: The error that occurred
464
+ private func emitError(_ error: ScannerError) {
465
+ delegate?.scannerDidEncounterError(error.toDictionary())
466
+ }
467
+
468
+ /// Emit load event to React Native
469
+ /// - Parameter success: Whether loading was successful
470
+ /// - Parameter error: Optional error message
471
+ private func emitLoadEvent(success: Bool, error: String? = nil) {
472
+ let payload = LoadEventPayload(success: success, error: error)
473
+ delegate?.scannerDidLoad(payload.toDictionary())
474
+ }
475
+ }
476
+
477
+ // MARK: - CameraManagerDelegate
478
+
479
+ extension ScannerViewImpl: CameraManagerDelegate {
480
+ func cameraManager(_ manager: CameraManager, didOutput sampleBuffer: CMSampleBuffer) {
481
+ // Don't process if scanning is paused
482
+ guard !isPaused else { return }
483
+
484
+ // Pass sample buffer to barcode detection
485
+ barcodeDetectionManager.detectBarcodes(in: sampleBuffer) { [weak self] observations in
486
+ guard let self = self, !self.isPaused else { return }
487
+
488
+ // Process observations
489
+ let results = self.processBarcodeObservations(observations)
490
+
491
+ // Update barcode frames display
492
+ self.updateBarcodeFrameDisplay(observations)
493
+
494
+ // Emit results if any
495
+ if !results.isEmpty {
496
+ self.emitBarcodesDetected(results)
497
+ }
498
+ }
499
+ }
500
+
501
+ func cameraManagerDidFail(_ manager: CameraManager, error: Error) {
502
+ print("[ScannerViewImpl] Camera failed: \(error.localizedDescription)")
503
+ let scannerError = ScannerError.from(
504
+ code: .cameraInitializationFailed,
505
+ message: error.localizedDescription
506
+ )
507
+ emitError(scannerError)
508
+ }
509
+
510
+ func cameraManagerDidStart(_ manager: CameraManager) {
511
+ print("[ScannerViewImpl] ✅ Camera started successfully")
512
+
513
+ // Re-apply torch after the session is fully running (fixes "torch set too early" cases)
514
+ manager.setTorch(enabled: requestedTorchEnabled)
515
+
516
+ // Try to add preview layer on main thread
517
+ DispatchQueue.main.async { [weak self] in
518
+ guard let self = self else { return }
519
+
520
+ // Check bounds multiple times with delay to catch layout
521
+ self.attemptToAddPreviewLayer(attempt: 0)
522
+
523
+ self.emitLoadEvent(success: true)
524
+ }
525
+ }
526
+
527
+ private func attemptToAddPreviewLayer(attempt: Int) {
528
+ guard !isPreviewLayerAdded else { return }
529
+
530
+ if self.bounds.width > 0 && self.bounds.height > 0 {
531
+ if let preview = self.cameraManager.getPreviewLayer() {
532
+ self.previewLayer = preview
533
+ preview.frame = self.bounds
534
+ self.layer.insertSublayer(preview, at: 0)
535
+ self.isPreviewLayerAdded = true
536
+ print("[ScannerViewImpl] ✅ Preview layer added with frame: \(self.bounds)")
537
+ }
538
+ } else if attempt < 10 {
539
+ // Try again after a short delay (max 10 attempts = ~2 seconds)
540
+ print("[ScannerViewImpl] ⏳ Attempt \(attempt + 1): Waiting for valid bounds (current: \(self.bounds))")
541
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
542
+ self?.attemptToAddPreviewLayer(attempt: attempt + 1)
543
+ }
544
+ } else {
545
+ print("[ScannerViewImpl] ❌ Failed to add preview layer after 10 attempts - bounds never became valid")
546
+ }
547
+ }
548
+ }
549
+
550
+ // MARK: - BarcodeDetectionDelegate
551
+
552
+ extension ScannerViewImpl: BarcodeDetectionDelegate {
553
+ func barcodeDetectionManager(_ manager: BarcodeDetectionManager,
554
+ didDetect observations: [VNBarcodeObservation]) {
555
+ // This is only called when using the delegate-based detection
556
+ // We're using the completion-based approach in cameraManager:didOutput:
557
+ // But we can handle it here as well if needed
558
+ }
559
+
560
+ func barcodeDetectionManager(_ manager: BarcodeDetectionManager,
561
+ didFailWith error: Error) {
562
+ print("[ScannerViewImpl] Barcode detection failed: \(error.localizedDescription)")
563
+ let scannerError = ScannerError.from(
564
+ code: .barcodeDetectionFailed,
565
+ message: error.localizedDescription
566
+ )
567
+ emitError(scannerError)
568
+ }
569
+ }
570
+
571
+ // MARK: - BarcodeFrameManagerDelegate
572
+
573
+ extension ScannerViewImpl: BarcodeFrameManagerDelegate {
574
+ func barcodeFrameManager(_ manager: BarcodeFrameManager,
575
+ didUpdateFrames frames: [CGRect]) {
576
+ // Frames are automatically updated via the closure callback
577
+ // This delegate method is here for additional functionality if needed
578
+ }
579
+ }
580
+
@@ -0,0 +1,26 @@
1
+ //
2
+ // react-native-scanner-Bridging-Header.h
3
+ // react-native-scanner
4
+ //
5
+ // Bridging header for Objective-C to Swift
6
+ //
7
+
8
+ #ifndef react_native_scanner_Bridging_Header_h
9
+ #define react_native_scanner_Bridging_Header_h
10
+
11
+ // React Native imports
12
+ #import <React/RCTViewComponentView.h>
13
+ #import <React/RCTConversions.h>
14
+
15
+ // UIKit and Foundation
16
+ #import <UIKit/UIKit.h>
17
+ #import <Foundation/Foundation.h>
18
+
19
+ // AVFoundation
20
+ #import <AVFoundation/AVFoundation.h>
21
+
22
+ // Vision Framework
23
+ #import <Vision/Vision.h>
24
+
25
+ #endif /* react_native_scanner_Bridging_Header_h */
26
+
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+
3
+ import { NativeModules } from 'react-native';
4
+ const {
5
+ CameraInfoModule
6
+ } = NativeModules;
7
+ export default CameraInfoModule;
8
+ //# sourceMappingURL=CameraInfoModule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["NativeModules","CameraInfoModule"],"sourceRoot":"../../src","sources":["CameraInfoModule.ts"],"mappings":";;AAAA,SAASA,aAAa,QAAQ,cAAc;AAG5C,MAAM;EAAEC;AAAiB,CAAC,GAAGD,aAAa;AAO1C,eAAeC,gBAAgB","ignoreList":[]}