@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.
- package/LICENSE +20 -0
- package/README.md +609 -0
- package/Scanner.podspec +20 -0
- package/android/build.gradle +90 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +8 -0
- package/android/src/main/java/com/scanner/CameraInfoModule.kt +253 -0
- package/android/src/main/java/com/scanner/ScannerPackage.kt +21 -0
- package/android/src/main/java/com/scanner/ScannerView.kt +783 -0
- package/android/src/main/java/com/scanner/ScannerViewManager.kt +181 -0
- package/android/src/main/java/com/scanner/utils/BarcodeFrameManager.kt +170 -0
- package/android/src/main/java/com/scanner/views/BarcodeFrameOverlayView.kt +43 -0
- package/android/src/main/java/com/scanner/views/FocusAreaView.kt +124 -0
- package/ios/BarcodeDetectionManager.swift +229 -0
- package/ios/BarcodeFrameManager.swift +175 -0
- package/ios/BarcodeFrameOverlayView.swift +102 -0
- package/ios/CameraManager.swift +396 -0
- package/ios/CoordinateTransformer.swift +140 -0
- package/ios/FocusAreaOverlayView.swift +161 -0
- package/ios/Models.swift +341 -0
- package/ios/Protocols.swift +194 -0
- package/ios/ScannerView.h +14 -0
- package/ios/ScannerView.mm +358 -0
- package/ios/ScannerViewImpl.swift +580 -0
- package/ios/react-native-scanner-Bridging-Header.h +26 -0
- package/lib/module/CameraInfoModule.js +8 -0
- package/lib/module/CameraInfoModule.js.map +1 -0
- package/lib/module/ScannerViewNativeComponent.ts +121 -0
- package/lib/module/hooks/useCameraInfo.js +106 -0
- package/lib/module/hooks/useCameraInfo.js.map +1 -0
- package/lib/module/index.js +13 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +47 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/CameraInfoModule.d.ts +8 -0
- package/lib/typescript/src/CameraInfoModule.d.ts.map +1 -0
- package/lib/typescript/src/ScannerViewNativeComponent.d.ts +91 -0
- package/lib/typescript/src/ScannerViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useCameraInfo.d.ts +25 -0
- package/lib/typescript/src/hooks/useCameraInfo.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +8 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +145 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +178 -0
- package/src/CameraInfoModule.ts +11 -0
- package/src/ScannerViewNativeComponent.ts +121 -0
- package/src/hooks/useCameraInfo.ts +190 -0
- package/src/index.tsx +30 -0
- 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
|
+
}
|