react-native-rectangle-doc-scanner 3.144.0 → 3.145.0
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/android/src/main/kotlin/com/reactnativerectangledocscanner/CameraView.kt +277 -0
- package/android/src/main/kotlin/com/reactnativerectangledocscanner/CameraViewManager.kt +98 -0
- package/android/src/main/kotlin/com/reactnativerectangledocscanner/DocumentDetector.kt +2 -1
- package/android/src/main/kotlin/com/reactnativerectangledocscanner/DocumentScannerPackage.kt +4 -1
- package/android/src/main/kotlin/com/reactnativerectangledocscanner/ImageProxyExt.kt +51 -0
- package/dist/FullDocScanner.js +14 -4
- package/package.json +1 -1
- package/src/FullDocScanner.tsx +14 -4
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
package com.reactnativerectangledocscanner
|
|
2
|
+
|
|
3
|
+
import android.Manifest
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.pm.PackageManager
|
|
6
|
+
import android.graphics.Canvas
|
|
7
|
+
import android.graphics.Color
|
|
8
|
+
import android.graphics.Paint
|
|
9
|
+
import android.graphics.Path
|
|
10
|
+
import android.util.Log
|
|
11
|
+
import android.view.View
|
|
12
|
+
import android.widget.FrameLayout
|
|
13
|
+
import androidx.camera.core.Camera
|
|
14
|
+
import androidx.camera.core.CameraSelector
|
|
15
|
+
import androidx.camera.core.ImageAnalysis
|
|
16
|
+
import androidx.camera.core.ImageProxy
|
|
17
|
+
import androidx.camera.core.Preview
|
|
18
|
+
import androidx.camera.lifecycle.ProcessCameraProvider
|
|
19
|
+
import androidx.camera.view.PreviewView
|
|
20
|
+
import androidx.core.content.ContextCompat
|
|
21
|
+
import androidx.lifecycle.LifecycleOwner
|
|
22
|
+
import com.google.common.util.concurrent.ListenableFuture
|
|
23
|
+
import java.util.concurrent.ExecutorService
|
|
24
|
+
import java.util.concurrent.Executors
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* CameraView with real-time document detection, grid overlay, and rectangle overlay
|
|
28
|
+
* Matches iOS implementation behavior
|
|
29
|
+
*/
|
|
30
|
+
class CameraView(context: Context) : FrameLayout(context) {
|
|
31
|
+
private val TAG = "CameraView"
|
|
32
|
+
|
|
33
|
+
private val previewView: PreviewView
|
|
34
|
+
private val overlayView: OverlayView
|
|
35
|
+
|
|
36
|
+
private var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>? = null
|
|
37
|
+
private var cameraProvider: ProcessCameraProvider? = null
|
|
38
|
+
private var preview: Preview? = null
|
|
39
|
+
private var imageAnalysis: ImageAnalysis? = null
|
|
40
|
+
private var camera: Camera? = null
|
|
41
|
+
|
|
42
|
+
private val cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor()
|
|
43
|
+
// Callback for detected rectangles
|
|
44
|
+
var onRectangleDetected: ((Rectangle?) -> Unit)? = null
|
|
45
|
+
|
|
46
|
+
init {
|
|
47
|
+
// Create preview view
|
|
48
|
+
previewView = PreviewView(context).apply {
|
|
49
|
+
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
|
50
|
+
implementationMode = PreviewView.ImplementationMode.COMPATIBLE
|
|
51
|
+
}
|
|
52
|
+
addView(previewView)
|
|
53
|
+
|
|
54
|
+
// Create overlay view for grid and rectangle
|
|
55
|
+
overlayView = OverlayView(context)
|
|
56
|
+
addView(overlayView)
|
|
57
|
+
|
|
58
|
+
Log.d(TAG, "CameraView initialized")
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Start camera and document detection
|
|
63
|
+
*/
|
|
64
|
+
fun startCamera() {
|
|
65
|
+
if (!hasPermissions()) {
|
|
66
|
+
Log.e(TAG, "Camera permissions not granted")
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
cameraProviderFuture = ProcessCameraProvider.getInstance(context)
|
|
71
|
+
cameraProviderFuture?.addListener({
|
|
72
|
+
try {
|
|
73
|
+
cameraProvider = cameraProviderFuture?.get()
|
|
74
|
+
bindCamera()
|
|
75
|
+
} catch (e: Exception) {
|
|
76
|
+
Log.e(TAG, "Failed to get camera provider", e)
|
|
77
|
+
}
|
|
78
|
+
}, ContextCompat.getMainExecutor(context))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Stop camera and release resources
|
|
83
|
+
*/
|
|
84
|
+
fun stopCamera() {
|
|
85
|
+
cameraProvider?.unbindAll()
|
|
86
|
+
cameraExecutor.shutdown()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Bind camera use cases
|
|
91
|
+
*/
|
|
92
|
+
private fun bindCamera() {
|
|
93
|
+
val lifecycleOwner = context as? LifecycleOwner
|
|
94
|
+
if (lifecycleOwner == null) {
|
|
95
|
+
Log.e(TAG, "Context is not a LifecycleOwner")
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
val provider = cameraProvider ?: return
|
|
100
|
+
|
|
101
|
+
// Unbind all before rebinding
|
|
102
|
+
provider.unbindAll()
|
|
103
|
+
|
|
104
|
+
// Preview use case
|
|
105
|
+
preview = Preview.Builder()
|
|
106
|
+
.build()
|
|
107
|
+
.also {
|
|
108
|
+
it.setSurfaceProvider(previewView.surfaceProvider)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Image analysis use case for document detection
|
|
112
|
+
imageAnalysis = ImageAnalysis.Builder()
|
|
113
|
+
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
|
114
|
+
.build()
|
|
115
|
+
.also {
|
|
116
|
+
it.setAnalyzer(cameraExecutor, DocumentAnalyzer())
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Select back camera
|
|
120
|
+
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
// Bind use cases to camera
|
|
124
|
+
camera = provider.bindToLifecycle(
|
|
125
|
+
lifecycleOwner,
|
|
126
|
+
cameraSelector,
|
|
127
|
+
preview,
|
|
128
|
+
imageAnalysis
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
Log.d(TAG, "Camera bound successfully")
|
|
132
|
+
} catch (e: Exception) {
|
|
133
|
+
Log.e(TAG, "Failed to bind camera", e)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Check if camera permissions are granted
|
|
139
|
+
*/
|
|
140
|
+
private fun hasPermissions(): Boolean {
|
|
141
|
+
return ContextCompat.checkSelfPermission(
|
|
142
|
+
context,
|
|
143
|
+
Manifest.permission.CAMERA
|
|
144
|
+
) == PackageManager.PERMISSION_GRANTED
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Image analyzer for document detection
|
|
149
|
+
*/
|
|
150
|
+
private inner class DocumentAnalyzer : ImageAnalysis.Analyzer {
|
|
151
|
+
override fun analyze(imageProxy: ImageProxy) {
|
|
152
|
+
try {
|
|
153
|
+
val nv21 = imageProxy.toNv21()
|
|
154
|
+
val rotationDegrees = imageProxy.imageInfo.rotationDegrees
|
|
155
|
+
|
|
156
|
+
val frameWidth = if (rotationDegrees == 90 || rotationDegrees == 270) {
|
|
157
|
+
imageProxy.height
|
|
158
|
+
} else {
|
|
159
|
+
imageProxy.width
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
val frameHeight = if (rotationDegrees == 90 || rotationDegrees == 270) {
|
|
163
|
+
imageProxy.width
|
|
164
|
+
} else {
|
|
165
|
+
imageProxy.height
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
val rectangle = DocumentDetector.detectRectangleInYUV(
|
|
169
|
+
nv21,
|
|
170
|
+
imageProxy.width,
|
|
171
|
+
imageProxy.height,
|
|
172
|
+
rotationDegrees
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
val transformedRectangle = rectangle?.let {
|
|
176
|
+
val viewWidth = if (overlayView.width > 0) overlayView.width else width
|
|
177
|
+
val viewHeight = if (overlayView.height > 0) overlayView.height else height
|
|
178
|
+
|
|
179
|
+
DocumentDetector.transformRectangleToViewCoordinates(
|
|
180
|
+
it,
|
|
181
|
+
frameWidth,
|
|
182
|
+
frameHeight,
|
|
183
|
+
viewWidth,
|
|
184
|
+
viewHeight
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
post {
|
|
189
|
+
overlayView.setDetectedRectangle(transformedRectangle)
|
|
190
|
+
onRectangleDetected?.invoke(transformedRectangle)
|
|
191
|
+
}
|
|
192
|
+
} catch (e: Exception) {
|
|
193
|
+
Log.e(TAG, "Failed to analyze frame", e)
|
|
194
|
+
post {
|
|
195
|
+
overlayView.setDetectedRectangle(null)
|
|
196
|
+
onRectangleDetected?.invoke(null)
|
|
197
|
+
}
|
|
198
|
+
} finally {
|
|
199
|
+
imageProxy.close()
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Overlay view for grid and rectangle
|
|
206
|
+
*/
|
|
207
|
+
private class OverlayView(context: Context) : View(context) {
|
|
208
|
+
private var detectedRectangle: Rectangle? = null
|
|
209
|
+
|
|
210
|
+
private val gridPaint = Paint().apply {
|
|
211
|
+
color = Color.parseColor("#80FFFFFF") // 50% white
|
|
212
|
+
strokeWidth = 2f
|
|
213
|
+
style = Paint.Style.STROKE
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private val rectanglePaint = Paint().apply {
|
|
217
|
+
color = Color.parseColor("#00FF00") // Green
|
|
218
|
+
strokeWidth = 4f
|
|
219
|
+
style = Paint.Style.STROKE
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private val rectangleFillPaint = Paint().apply {
|
|
223
|
+
color = Color.parseColor("#2000FF00") // 12% green
|
|
224
|
+
style = Paint.Style.FILL
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
init {
|
|
228
|
+
setWillNotDraw(false)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
fun setDetectedRectangle(rectangle: Rectangle?) {
|
|
232
|
+
detectedRectangle = rectangle
|
|
233
|
+
invalidate()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
override fun onDraw(canvas: Canvas) {
|
|
237
|
+
super.onDraw(canvas)
|
|
238
|
+
|
|
239
|
+
// Draw 3x3 grid
|
|
240
|
+
drawGrid(canvas)
|
|
241
|
+
|
|
242
|
+
// Draw detected rectangle if available
|
|
243
|
+
detectedRectangle?.let { rect ->
|
|
244
|
+
drawRectangle(canvas, rect)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private fun drawGrid(canvas: Canvas) {
|
|
249
|
+
val width = width.toFloat()
|
|
250
|
+
val height = height.toFloat()
|
|
251
|
+
|
|
252
|
+
// Draw vertical lines (2 lines for 3x3 grid)
|
|
253
|
+
canvas.drawLine(width / 3f, 0f, width / 3f, height, gridPaint)
|
|
254
|
+
canvas.drawLine(2f * width / 3f, 0f, 2f * width / 3f, height, gridPaint)
|
|
255
|
+
|
|
256
|
+
// Draw horizontal lines (2 lines for 3x3 grid)
|
|
257
|
+
canvas.drawLine(0f, height / 3f, width, height / 3f, gridPaint)
|
|
258
|
+
canvas.drawLine(0f, 2f * height / 3f, width, 2f * height / 3f, gridPaint)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private fun drawRectangle(canvas: Canvas, rect: Rectangle) {
|
|
262
|
+
val path = Path().apply {
|
|
263
|
+
moveTo(rect.topLeft.x, rect.topLeft.y)
|
|
264
|
+
lineTo(rect.topRight.x, rect.topRight.y)
|
|
265
|
+
lineTo(rect.bottomRight.x, rect.bottomRight.y)
|
|
266
|
+
lineTo(rect.bottomLeft.x, rect.bottomLeft.y)
|
|
267
|
+
close()
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Draw filled rectangle
|
|
271
|
+
canvas.drawPath(path, rectangleFillPaint)
|
|
272
|
+
|
|
273
|
+
// Draw rectangle outline
|
|
274
|
+
canvas.drawPath(path, rectanglePaint)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
package com.reactnativerectangledocscanner
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.facebook.react.bridge.Arguments
|
|
5
|
+
import com.facebook.react.bridge.ReactContext
|
|
6
|
+
import com.facebook.react.bridge.WritableMap
|
|
7
|
+
import com.facebook.react.common.MapBuilder
|
|
8
|
+
import com.facebook.react.uimanager.SimpleViewManager
|
|
9
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
10
|
+
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* ViewManager for CameraView
|
|
14
|
+
* Bridges native CameraView to React Native
|
|
15
|
+
*/
|
|
16
|
+
class CameraViewManager : SimpleViewManager<CameraView>() {
|
|
17
|
+
private val TAG = "CameraViewManager"
|
|
18
|
+
|
|
19
|
+
companion object {
|
|
20
|
+
const val REACT_CLASS = "RNDocScannerCamera"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
override fun getName(): String = REACT_CLASS
|
|
24
|
+
|
|
25
|
+
override fun createViewInstance(reactContext: ThemedReactContext): CameraView {
|
|
26
|
+
Log.d(TAG, "Creating CameraView instance")
|
|
27
|
+
|
|
28
|
+
val cameraView = CameraView(reactContext)
|
|
29
|
+
|
|
30
|
+
// Set up rectangle detection callback
|
|
31
|
+
cameraView.onRectangleDetected = { rectangle ->
|
|
32
|
+
sendRectangleDetectedEvent(reactContext, cameraView, rectangle)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return cameraView
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
override fun onAfterUpdateTransaction(view: CameraView) {
|
|
39
|
+
super.onAfterUpdateTransaction(view)
|
|
40
|
+
// Start camera after view is ready
|
|
41
|
+
view.post {
|
|
42
|
+
view.startCamera()
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
override fun onDropViewInstance(view: CameraView) {
|
|
47
|
+
super.onDropViewInstance(view)
|
|
48
|
+
view.stopCamera()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {
|
|
52
|
+
return MapBuilder.of(
|
|
53
|
+
"onRectangleDetected",
|
|
54
|
+
MapBuilder.of("registrationName", "onRectangleDetected")
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Send rectangle detected event to React Native
|
|
60
|
+
*/
|
|
61
|
+
private fun sendRectangleDetectedEvent(
|
|
62
|
+
reactContext: ReactContext,
|
|
63
|
+
view: CameraView,
|
|
64
|
+
rectangle: Rectangle?
|
|
65
|
+
) {
|
|
66
|
+
val event: WritableMap = Arguments.createMap()
|
|
67
|
+
|
|
68
|
+
if (rectangle != null) {
|
|
69
|
+
val topLeft = Arguments.createMap().apply {
|
|
70
|
+
putDouble("x", rectangle.topLeft.x.toDouble())
|
|
71
|
+
putDouble("y", rectangle.topLeft.y.toDouble())
|
|
72
|
+
}
|
|
73
|
+
val topRight = Arguments.createMap().apply {
|
|
74
|
+
putDouble("x", rectangle.topRight.x.toDouble())
|
|
75
|
+
putDouble("y", rectangle.topRight.y.toDouble())
|
|
76
|
+
}
|
|
77
|
+
val bottomRight = Arguments.createMap().apply {
|
|
78
|
+
putDouble("x", rectangle.bottomRight.x.toDouble())
|
|
79
|
+
putDouble("y", rectangle.bottomRight.y.toDouble())
|
|
80
|
+
}
|
|
81
|
+
val bottomLeft = Arguments.createMap().apply {
|
|
82
|
+
putDouble("x", rectangle.bottomLeft.x.toDouble())
|
|
83
|
+
putDouble("y", rectangle.bottomLeft.y.toDouble())
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
event.putMap("topLeft", topLeft)
|
|
87
|
+
event.putMap("topRight", topRight)
|
|
88
|
+
event.putMap("bottomRight", bottomRight)
|
|
89
|
+
event.putMap("bottomLeft", bottomLeft)
|
|
90
|
+
} else {
|
|
91
|
+
event.putNull("rectangle")
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
reactContext
|
|
95
|
+
.getJSModule(RCTEventEmitter::class.java)
|
|
96
|
+
.receiveEvent(view.id, "onRectangleDetected", event)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -248,7 +248,8 @@ class DocumentDetector {
|
|
|
248
248
|
imageWidth: Int,
|
|
249
249
|
imageHeight: Int,
|
|
250
250
|
viewWidth: Int,
|
|
251
|
-
viewHeight: Int
|
|
251
|
+
viewHeight: Int,
|
|
252
|
+
rotationDegrees: Int = 0
|
|
252
253
|
): Rectangle {
|
|
253
254
|
if (imageWidth == 0 || imageHeight == 0 || viewWidth == 0 || viewHeight == 0) {
|
|
254
255
|
return rectangle
|
package/android/src/main/kotlin/com/reactnativerectangledocscanner/DocumentScannerPackage.kt
CHANGED
|
@@ -11,6 +11,9 @@ class DocumentScannerPackage : ReactPackage {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
14
|
-
return listOf(
|
|
14
|
+
return listOf(
|
|
15
|
+
DocumentScannerViewManager(),
|
|
16
|
+
CameraViewManager()
|
|
17
|
+
)
|
|
15
18
|
}
|
|
16
19
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
package com.reactnativerectangledocscanner
|
|
2
|
+
|
|
3
|
+
import androidx.camera.core.ImageProxy
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Convert [ImageProxy] in YUV_420_888 format to NV21 byte array.
|
|
7
|
+
* This mirrors the logic used by the existing DocumentScannerView pipeline so CameraView can reuse
|
|
8
|
+
* the same OpenCV detection utilities.
|
|
9
|
+
*/
|
|
10
|
+
fun ImageProxy.toNv21(): ByteArray {
|
|
11
|
+
val width = width
|
|
12
|
+
val height = height
|
|
13
|
+
|
|
14
|
+
val ySize = width * height
|
|
15
|
+
val uvSize = width * height / 2
|
|
16
|
+
val nv21 = ByteArray(ySize + uvSize)
|
|
17
|
+
|
|
18
|
+
val yBuffer = planes[0].buffer
|
|
19
|
+
val uBuffer = planes[1].buffer
|
|
20
|
+
val vBuffer = planes[2].buffer
|
|
21
|
+
|
|
22
|
+
val yRowStride = planes[0].rowStride
|
|
23
|
+
val yPixelStride = planes[0].pixelStride
|
|
24
|
+
var outputOffset = 0
|
|
25
|
+
for (row in 0 until height) {
|
|
26
|
+
var inputOffset = row * yRowStride
|
|
27
|
+
for (col in 0 until width) {
|
|
28
|
+
nv21[outputOffset++] = yBuffer.get(inputOffset)
|
|
29
|
+
inputOffset += yPixelStride
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
val uvRowStride = planes[1].rowStride
|
|
34
|
+
val uvPixelStride = planes[1].pixelStride
|
|
35
|
+
val vRowStride = planes[2].rowStride
|
|
36
|
+
val vPixelStride = planes[2].pixelStride
|
|
37
|
+
val uvHeight = height / 2
|
|
38
|
+
val uvWidth = width / 2
|
|
39
|
+
for (row in 0 until uvHeight) {
|
|
40
|
+
var uInputOffset = row * uvRowStride
|
|
41
|
+
var vInputOffset = row * vRowStride
|
|
42
|
+
for (col in 0 until uvWidth) {
|
|
43
|
+
nv21[outputOffset++] = vBuffer.get(vInputOffset)
|
|
44
|
+
nv21[outputOffset++] = uBuffer.get(uInputOffset)
|
|
45
|
+
uInputOffset += uvPixelStride
|
|
46
|
+
vInputOffset += vPixelStride
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return nv21
|
|
51
|
+
}
|
package/dist/FullDocScanner.js
CHANGED
|
@@ -228,11 +228,21 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
228
228
|
try {
|
|
229
229
|
console.log('[FullDocScanner] openCropper called with path:', imagePath);
|
|
230
230
|
setProcessing(true);
|
|
231
|
-
// Clean path
|
|
232
|
-
//
|
|
231
|
+
// Clean path handling differs by platform
|
|
232
|
+
// iOS: react-native-image-crop-picker handles file:// prefix internally
|
|
233
|
+
// Android: needs file:// prefix for proper URI handling
|
|
233
234
|
let cleanPath = imagePath;
|
|
234
|
-
if (
|
|
235
|
-
|
|
235
|
+
if (react_native_1.Platform.OS === 'ios') {
|
|
236
|
+
// iOS: remove file:// prefix as the library adds it
|
|
237
|
+
if (cleanPath.startsWith('file://')) {
|
|
238
|
+
cleanPath = cleanPath.replace('file://', '');
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
// Android: ensure file:// prefix exists
|
|
243
|
+
if (!cleanPath.startsWith('file://')) {
|
|
244
|
+
cleanPath = 'file://' + cleanPath;
|
|
245
|
+
}
|
|
236
246
|
}
|
|
237
247
|
console.log('[FullDocScanner] Clean path for cropper:', cleanPath);
|
|
238
248
|
const shouldWaitForPickerDismissal = options?.waitForPickerDismissal ?? true;
|
package/package.json
CHANGED
package/src/FullDocScanner.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
Image,
|
|
6
6
|
InteractionManager,
|
|
7
7
|
NativeModules,
|
|
8
|
+
Platform,
|
|
8
9
|
StyleSheet,
|
|
9
10
|
Text,
|
|
10
11
|
TouchableOpacity,
|
|
@@ -337,11 +338,20 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
337
338
|
console.log('[FullDocScanner] openCropper called with path:', imagePath);
|
|
338
339
|
setProcessing(true);
|
|
339
340
|
|
|
340
|
-
// Clean path
|
|
341
|
-
//
|
|
341
|
+
// Clean path handling differs by platform
|
|
342
|
+
// iOS: react-native-image-crop-picker handles file:// prefix internally
|
|
343
|
+
// Android: needs file:// prefix for proper URI handling
|
|
342
344
|
let cleanPath = imagePath;
|
|
343
|
-
if (
|
|
344
|
-
|
|
345
|
+
if (Platform.OS === 'ios') {
|
|
346
|
+
// iOS: remove file:// prefix as the library adds it
|
|
347
|
+
if (cleanPath.startsWith('file://')) {
|
|
348
|
+
cleanPath = cleanPath.replace('file://', '');
|
|
349
|
+
}
|
|
350
|
+
} else {
|
|
351
|
+
// Android: ensure file:// prefix exists
|
|
352
|
+
if (!cleanPath.startsWith('file://')) {
|
|
353
|
+
cleanPath = 'file://' + cleanPath;
|
|
354
|
+
}
|
|
345
355
|
}
|
|
346
356
|
console.log('[FullDocScanner] Clean path for cropper:', cleanPath);
|
|
347
357
|
|