@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,181 @@
1
+ package com.scanner
2
+
3
+ import android.graphics.Color
4
+ import android.util.Log
5
+ import com.facebook.react.bridge.*
6
+ import com.facebook.react.module.annotations.ReactModule
7
+ import com.facebook.react.uimanager.SimpleViewManager
8
+ import com.facebook.react.uimanager.ThemedReactContext
9
+ import com.facebook.react.uimanager.annotations.ReactProp
10
+ import com.facebook.react.uimanager.events.RCTModernEventEmitter
11
+
12
+ @ReactModule(name = ScannerViewManager.NAME)
13
+ class ScannerViewManager : SimpleViewManager<ScannerView>() {
14
+
15
+ override fun getName(): String {
16
+ return NAME
17
+ }
18
+
19
+ public override fun createViewInstance(context: ThemedReactContext): ScannerView {
20
+ return ScannerView(context)
21
+ }
22
+
23
+ // Barcode configuration
24
+ @ReactProp(name = "barcodeTypes")
25
+ fun setBarcodeTypes(view: ScannerView?, types: ReadableArray?) {
26
+ if (types != null) {
27
+ val typeList = mutableListOf<String>()
28
+ for (i in 0 until types.size()) {
29
+ typeList.add(types.getString(i) ?: "")
30
+ }
31
+ Log.d("ScannerViewManager", "Barcode types set: $typeList")
32
+ // Note: The actual barcode type filtering would be implemented in ScannerView
33
+ // For now, ML Kit will detect all supported barcode types
34
+ }
35
+ }
36
+
37
+ // Focus area configuration
38
+ @ReactProp(name = "focusArea")
39
+ fun setFocusArea(view: ScannerView?, focusArea: ReadableMap?) {
40
+ if (focusArea != null) {
41
+ val enabled = focusArea.getBoolean("enabled") ?: false
42
+ val showOverlay = focusArea.getBoolean("showOverlay") ?: false
43
+ val borderColor = focusArea.getString("borderColor")
44
+ val tintColor = focusArea.getString("tintColor")
45
+ val size = focusArea.getDynamic("size")
46
+ val position = focusArea.getMap("position")
47
+
48
+ // Set focus area properties
49
+ view?.setFocusAreaEnabled(enabled)
50
+ view?.setEnableFrame(showOverlay)
51
+
52
+ if (borderColor != null) {
53
+ view?.setBorderColor(borderColor)
54
+ }
55
+
56
+ if (tintColor != null) {
57
+ view?.setTintColor(tintColor)
58
+ }
59
+
60
+ if (position != null) {
61
+ val x = position.getDouble("x").toFloat()
62
+ val y = position.getDouble("y").toFloat()
63
+ view?.setPosition(x, y)
64
+ }
65
+
66
+ if (size != null) {
67
+ val frameSize: FrameSize = when {
68
+ size.type == ReadableType.Number -> FrameSize.Square(size.asInt())
69
+ size.type == ReadableType.Map -> {
70
+ val frameSizeMap = size.asMap()
71
+ val width = frameSizeMap.getInt("width")
72
+ val height = frameSizeMap.getInt("height")
73
+ FrameSize.Rectangle(width, height)
74
+ }
75
+ else -> FrameSize.Square(300) // Default
76
+ }
77
+ view?.setFrameSize(frameSize)
78
+ }
79
+
80
+ Log.d("ScannerViewManager", "Focus area configured: enabled=$enabled, showOverlay=$showOverlay, borderColor=$borderColor, tintColor=$tintColor, position=$position")
81
+ }
82
+ }
83
+
84
+ // Barcode frames configuration
85
+ @ReactProp(name = "barcodeFrames")
86
+ fun setBarcodeFrames(view: ScannerView?, barcodeFrames: ReadableMap?) {
87
+ if (barcodeFrames != null) {
88
+ val enabled = barcodeFrames.getBoolean("enabled") ?: false
89
+ val color = barcodeFrames.getString("color")
90
+ val onlyInFocusArea = barcodeFrames.getBoolean("onlyInFocusArea") ?: false
91
+
92
+ // Set barcode frames properties
93
+ view?.setBarcodeFramesEnabled(enabled)
94
+ view?.setShowBarcodeFramesOnlyInFrame(onlyInFocusArea)
95
+
96
+ if (color != null) {
97
+ view?.setBarcodeFramesColor(color)
98
+ }
99
+
100
+ Log.d("ScannerViewManager", "Barcode frames configured: enabled=$enabled, onlyInFocusArea=$onlyInFocusArea, color=$color")
101
+ }
102
+ }
103
+
104
+ // Torch control
105
+ @ReactProp(name = "torch")
106
+ fun setTorch(view: ScannerView?, enabled: Boolean?) {
107
+ view?.setTorch(enabled ?: false)
108
+ }
109
+
110
+ // Event handlers
111
+ @ReactProp(name = "onBarcodeScanned")
112
+ fun setOnBarcodeScanned(view: ScannerView?, onBarcodeScanned: Boolean?) {
113
+ // This prop is used to register the event handler
114
+ // The actual event emission happens in ScannerView.processImage()
115
+ Log.d("ScannerViewManager", "onBarcodeScanned event handler registered")
116
+ }
117
+
118
+ @ReactProp(name = "onScannerError")
119
+ fun setOnScannerError(view: ScannerView?, onScannerError: Boolean?) {
120
+ // This prop is used to register the event handler
121
+ Log.d("ScannerViewManager", "onScannerError event handler registered")
122
+ }
123
+
124
+ @ReactProp(name = "onLoad")
125
+ fun setOnLoad(view: ScannerView?, onLoad: Boolean?) {
126
+ // This prop is used to register the event handler
127
+ Log.d("ScannerViewManager", "onLoad event handler registered")
128
+ }
129
+
130
+ @ReactProp(name = "zoom")
131
+ fun setZoom(view: ScannerView?, zoom: Double) {
132
+ view?.setZoom(zoom.toFloat())
133
+ }
134
+
135
+ @ReactProp(name = "pauseScanning")
136
+ fun setPauseScanning(view: ScannerView?, pause: Boolean?) {
137
+ if (pause == true) {
138
+ view?.pauseScanning()
139
+ } else {
140
+ view?.resumeScanning()
141
+ }
142
+ }
143
+
144
+ @ReactProp(name = "barcodeScanStrategy")
145
+ fun setBarcodeScanStrategy(view: ScannerView?, strategy: String?) {
146
+ view?.setBarcodeScanStrategy(strategy ?: "ALL")
147
+ }
148
+
149
+ @ReactProp(name = "keepScreenOn")
150
+ fun setKeepScreenOn(view: ScannerView?, keepOn: Boolean?) {
151
+ view?.setKeepScreenOnEnabled(keepOn ?: true)
152
+ }
153
+
154
+ @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
158
+ view?.setBarcodeEmissionInterval(actualInterval)
159
+ }
160
+
161
+ companion object {
162
+ const val NAME = "ScannerView"
163
+ }
164
+
165
+ override fun getExportedCustomBubblingEventTypeConstants(): MutableMap<String, Any> {
166
+ val map = mutableMapOf<String, Any>()
167
+ map["onBarcodeScanned"] = mapOf(
168
+ "phasedRegistrationNames" to mapOf(
169
+ "bubbled" to "onBarcodeScanned"
170
+ )
171
+ )
172
+ // Add more events here if needed
173
+ return map
174
+ }
175
+ }
176
+
177
+ // Data classes for frame configuration
178
+ sealed class FrameSize {
179
+ data class Square(val size: Int) : FrameSize()
180
+ data class Rectangle(val width: Int, val height: Int) : FrameSize()
181
+ }
@@ -0,0 +1,170 @@
1
+ package com.scanner.utils
2
+
3
+ import android.graphics.RectF
4
+ import android.util.Log
5
+ import java.util.concurrent.ConcurrentHashMap
6
+ import java.util.concurrent.Executors
7
+ import java.util.concurrent.ScheduledExecutorService
8
+ import java.util.concurrent.TimeUnit
9
+
10
+ /**
11
+ * Manages barcode frames with efficient cleanup scheduling.
12
+ * Only schedules cleanup when there are active frames to monitor.
13
+ */
14
+ class BarcodeFrameManager {
15
+ companion object {
16
+ private const val FRAME_TIMEOUT_MS = 1000L // 1 second timeout
17
+ private const val CLEANUP_DELAY_MS = 1000L // 1 second delay before cleanup
18
+ const val TAG = "BarcodeFrameManager"
19
+ }
20
+
21
+ // Thread-safe map to store active barcode frames
22
+ private val activeBarcodeFrames = ConcurrentHashMap<String, BarcodeFrame>()
23
+
24
+ // Executor for cleanup scheduling
25
+ private val cleanupExecutor: ScheduledExecutorService =
26
+ Executors.newSingleThreadScheduledExecutor()
27
+
28
+ // Current cleanup task (if any)
29
+ private var currentCleanupTask: java.util.concurrent.ScheduledFuture<*>? = null
30
+
31
+ // Callback to update the display
32
+ private var onFramesChanged: (() -> Unit)? = null
33
+
34
+ /**
35
+ * Data class representing a barcode frame with its position and last seen time
36
+ */
37
+ data class BarcodeFrame(
38
+ val rect: RectF,
39
+ val lastSeenTime: Long
40
+ )
41
+
42
+ /**
43
+ * Set callback to be called when frames change
44
+ */
45
+ fun setOnFramesChangedListener(listener: () -> Unit) {
46
+ onFramesChanged = listener
47
+ }
48
+
49
+ /**
50
+ * Update barcode frames with new detections
51
+ */
52
+ fun updateBarcodeFrames(barcodeFrames: Map<String, RectF>) {
53
+ val currentTime = System.currentTimeMillis()
54
+ val currentBarcodeValues = mutableSetOf<String>()
55
+
56
+ // Update existing frames or add new ones
57
+ barcodeFrames.forEach { (barcodeValue, rect) ->
58
+ currentBarcodeValues.add(barcodeValue)
59
+ activeBarcodeFrames[barcodeValue] = BarcodeFrame(
60
+ rect = rect,
61
+ lastSeenTime = currentTime
62
+ )
63
+ }
64
+
65
+ // Remove frames for barcodes no longer visible
66
+ val framesToRemove = activeBarcodeFrames.keys.filter { it !in currentBarcodeValues }
67
+ framesToRemove.forEach { barcodeValue ->
68
+ activeBarcodeFrames.remove(barcodeValue)
69
+ }
70
+
71
+ // Schedule cleanup if we have frames and no cleanup is currently scheduled
72
+ if (activeBarcodeFrames.isNotEmpty() && currentCleanupTask?.isDone != false) {
73
+ scheduleCleanup()
74
+ }
75
+
76
+ // Notify display update
77
+ onFramesChanged?.invoke()
78
+ }
79
+
80
+ /**
81
+ * Get current active frames for display
82
+ */
83
+ fun getActiveFrames(): List<RectF> {
84
+ return activeBarcodeFrames.values.map { it.rect }
85
+ }
86
+
87
+ /**
88
+ * Get count of active frames
89
+ */
90
+ // fun getActiveFrameCount(): Int {
91
+ // return activeBarcodeFrames.size
92
+ // }
93
+
94
+ /**
95
+ * Schedule cleanup of stale frames
96
+ */
97
+ private fun scheduleCleanup() {
98
+ // Cancel any existing cleanup task
99
+ currentCleanupTask?.cancel(false)
100
+
101
+ // Schedule new cleanup task
102
+ currentCleanupTask = cleanupExecutor.schedule({
103
+ cleanupStaleFrames()
104
+ }, CLEANUP_DELAY_MS, TimeUnit.MILLISECONDS)
105
+
106
+ Log.d(TAG, "Scheduled cleanup in ${CLEANUP_DELAY_MS}ms for ${activeBarcodeFrames.size} frames")
107
+ }
108
+
109
+ /**
110
+ * Clean up stale frames and reschedule if needed
111
+ */
112
+ private fun cleanupStaleFrames() {
113
+ val currentTime = System.currentTimeMillis()
114
+ val framesToRemove = mutableListOf<String>()
115
+
116
+ activeBarcodeFrames.forEach { (barcodeValue, frame) ->
117
+ if (currentTime - frame.lastSeenTime > FRAME_TIMEOUT_MS) {
118
+ framesToRemove.add(barcodeValue)
119
+ }
120
+ }
121
+
122
+ if (framesToRemove.isNotEmpty()) {
123
+ framesToRemove.forEach { barcodeValue ->
124
+ activeBarcodeFrames.remove(barcodeValue)
125
+ }
126
+
127
+ Log.d(TAG, "Removed ${framesToRemove.size} stale barcode frames")
128
+ onFramesChanged?.invoke()
129
+ }
130
+
131
+ // Reschedule cleanup if there are still frames to monitor
132
+ if (activeBarcodeFrames.isNotEmpty()) {
133
+ scheduleCleanup()
134
+ } else {
135
+ Log.d(TAG, "No more frames to monitor, stopping cleanup")
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Clear all frames immediately
141
+ */
142
+ fun clearAllFrames() {
143
+ val frameCount = activeBarcodeFrames.size
144
+ activeBarcodeFrames.clear()
145
+ currentCleanupTask?.cancel(false)
146
+
147
+ if (frameCount > 0) {
148
+ Log.d(TAG, "Cleared all $frameCount barcode frames")
149
+ onFramesChanged?.invoke()
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Shutdown the manager and cleanup resources
155
+ */
156
+ fun shutdown() {
157
+ currentCleanupTask?.cancel(false)
158
+ cleanupExecutor.shutdown()
159
+ try {
160
+ if (!cleanupExecutor.awaitTermination(1, TimeUnit.SECONDS)) {
161
+ cleanupExecutor.shutdownNow()
162
+ }
163
+ } catch (e: InterruptedException) {
164
+ cleanupExecutor.shutdownNow()
165
+ Thread.currentThread().interrupt()
166
+ }
167
+ Log.d(TAG, "BarcodeFrameManager shutdown complete")
168
+ }
169
+
170
+ }
@@ -0,0 +1,43 @@
1
+ package com.scanner.views
2
+
3
+ import android.content.Context
4
+ import android.graphics.Canvas
5
+ import android.graphics.Color
6
+ import android.graphics.Paint
7
+ import android.graphics.RectF
8
+ import android.util.AttributeSet
9
+ import android.view.View
10
+
11
+ class BarcodeFrameOverlayView : View {
12
+
13
+ private var barcodeBoxes: List<RectF> = emptyList()
14
+ private val paint = Paint().apply {
15
+ color = Color.YELLOW
16
+ style = Paint.Style.STROKE
17
+ strokeWidth = 5f
18
+ }
19
+
20
+ constructor(context: Context) : super(context) {
21
+ setWillNotDraw(false)
22
+ }
23
+
24
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
25
+ setWillNotDraw(false)
26
+ }
27
+
28
+ constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
29
+ setWillNotDraw(false)
30
+ }
31
+
32
+ fun setBarcodeBoxes(boxes: List<RectF>) {
33
+ this.barcodeBoxes = boxes
34
+ postInvalidate()
35
+ }
36
+
37
+ override fun onDraw(canvas: Canvas) {
38
+ super.onDraw(canvas)
39
+ for (box in barcodeBoxes) {
40
+ canvas.drawRect(box, paint)
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,124 @@
1
+ package com.scanner.views
2
+
3
+ import android.content.Context
4
+ import android.graphics.*
5
+ import android.util.AttributeSet
6
+ import android.view.View
7
+ import com.scanner.FrameSize
8
+
9
+ // Separate overlay view for frame drawing
10
+ class FocusAreaView : View {
11
+ private var enableFrame: Boolean = false
12
+ private var borderColor: Int = Color.TRANSPARENT
13
+ private var tintColor: Int = Color.BLACK
14
+ private var frameSize: FrameSize = FrameSize.Square(300)
15
+ private var positionX: Float = 50f // Default center (50%)
16
+ private var positionY: Float = 50f // Default center (50%)
17
+ var frameRect: RectF? = null
18
+ private set
19
+
20
+ constructor(context: Context) : super(context) {
21
+ setWillNotDraw(false)
22
+ }
23
+
24
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
25
+ setWillNotDraw(false)
26
+ }
27
+
28
+ constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
29
+ setWillNotDraw(false)
30
+ }
31
+
32
+ override fun onDraw(canvas: Canvas) {
33
+ super.onDraw(canvas)
34
+
35
+ if (enableFrame) {
36
+ drawFrame(canvas)
37
+ }
38
+ }
39
+
40
+ private fun drawFrame(canvas: Canvas) {
41
+ // Calculate position based on percentage (0-100)
42
+ val centerX = (width * positionX / 100f)
43
+ val centerY = (height * positionY / 100f)
44
+
45
+ // Calculate frame dimensions based on frameSize type
46
+ val currentFrameSize = frameSize // Local copy to avoid smart cast issues
47
+ val (frameWidth, frameHeight) = when (currentFrameSize) {
48
+ is FrameSize.Square -> {
49
+ val density = context.resources.displayMetrics.density
50
+ val size = currentFrameSize.size * density
51
+ size to size
52
+ }
53
+ is FrameSize.Rectangle -> {
54
+ val density = context.resources.displayMetrics.density
55
+ val width = currentFrameSize.width * density
56
+ val height = currentFrameSize.height * density
57
+ width to height
58
+ }
59
+ }
60
+
61
+ val frameHalfWidth = frameWidth / 2f
62
+ val frameHalfHeight = frameHeight / 2f
63
+
64
+ // Calculate frame rectangle with custom position
65
+ frameRect = RectF(
66
+ centerX - frameHalfWidth,
67
+ centerY - frameHalfHeight,
68
+ centerX + frameHalfWidth,
69
+ centerY + frameHalfHeight
70
+ )
71
+
72
+ // Draw semi-transparent overlay
73
+ val overlayPaint = Paint().apply {
74
+ color = tintColor
75
+ alpha = 128
76
+ }
77
+
78
+ // Draw overlay with transparent rectangle
79
+ canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), overlayPaint)
80
+
81
+ // Clear the frame area
82
+ val clearPaint = Paint().apply {
83
+ color = Color.TRANSPARENT
84
+ xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
85
+ }
86
+ canvas.drawRect(frameRect!!, clearPaint)
87
+
88
+ // Draw frame border only if color is not transparent
89
+ if (borderColor != Color.TRANSPARENT) {
90
+ val borderPaint = Paint().apply {
91
+ color = borderColor
92
+ style = Paint.Style.STROKE
93
+ strokeWidth = 4f
94
+ }
95
+ canvas.drawRect(frameRect!!, borderPaint)
96
+ }
97
+ }
98
+
99
+ fun setEnableFrame(enable: Boolean) {
100
+ enableFrame = enable
101
+ invalidate()
102
+ }
103
+
104
+ fun setBorderColor(color: Int) {
105
+ borderColor = color
106
+ invalidate()
107
+ }
108
+
109
+ fun setTintColor(color: Int) {
110
+ tintColor = color
111
+ invalidate()
112
+ }
113
+
114
+ fun setFrameSize(size: FrameSize) {
115
+ frameSize = size
116
+ invalidate()
117
+ }
118
+
119
+ fun setPosition(x: Float, y: Float) {
120
+ positionX = x.coerceIn(0f, 100f) // Clamp to 0-100 range
121
+ positionY = y.coerceIn(0f, 100f) // Clamp to 0-100 range
122
+ invalidate()
123
+ }
124
+ }