react-native-rectangle-doc-scanner 3.149.0 → 3.151.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.
@@ -8,6 +8,7 @@ import androidx.camera.core.*
8
8
  import androidx.camera.lifecycle.ProcessCameraProvider
9
9
  import androidx.camera.view.PreviewView
10
10
  import androidx.core.content.ContextCompat
11
+ import androidx.lifecycle.Lifecycle
11
12
  import androidx.lifecycle.LifecycleOwner
12
13
  import java.io.File
13
14
  import java.util.concurrent.ExecutorService
@@ -68,6 +69,13 @@ class CameraController(
68
69
  private fun bindCameraUseCases(enableDetection: Boolean) {
69
70
  val cameraProvider = cameraProvider ?: return
70
71
 
72
+ // Check lifecycle state
73
+ val lifecycle = lifecycleOwner.lifecycle
74
+ if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
75
+ Log.e(TAG, "Cannot bind camera - lifecycle is destroyed")
76
+ return
77
+ }
78
+
71
79
  // Select camera
72
80
  val cameraSelector = if (useFrontCamera) {
73
81
  CameraSelector.DEFAULT_FRONT_CAMERA
@@ -79,14 +87,12 @@ class CameraController(
79
87
  val preview = Preview.Builder()
80
88
  .setTargetResolution(Size(1080, 1920))
81
89
  .build()
82
- .also {
83
- it.setSurfaceProvider(previewView.surfaceProvider)
84
- }
85
90
 
86
91
  // Image capture use case (high resolution for document scanning)
87
92
  imageCapture = ImageCapture.Builder()
88
93
  .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
89
94
  .setTargetResolution(Size(1920, 2560))
95
+ .setFlashMode(ImageCapture.FLASH_MODE_AUTO)
90
96
  .build()
91
97
 
92
98
  // Image analysis use case for rectangle detection
@@ -121,12 +127,15 @@ class CameraController(
121
127
  *useCases.toTypedArray()
122
128
  )
123
129
 
130
+ // Set surface provider AFTER binding to lifecycle
131
+ preview.setSurfaceProvider(previewView.surfaceProvider)
132
+
124
133
  // Restore torch state if it was enabled
125
134
  if (torchEnabled) {
126
135
  setTorchEnabled(true)
127
136
  }
128
137
 
129
- Log.d(TAG, "Camera started successfully")
138
+ Log.d(TAG, "Camera started successfully, hasFlashUnit: ${camera?.cameraInfo?.hasFlashUnit()}")
130
139
  } catch (e: Exception) {
131
140
  Log.e(TAG, "Failed to bind camera use cases", e)
132
141
  }
@@ -19,6 +19,7 @@ import com.facebook.react.bridge.WritableMap
19
19
  import com.facebook.react.uimanager.ThemedReactContext
20
20
  import com.facebook.react.uimanager.events.RCTEventEmitter
21
21
  import kotlinx.coroutines.*
22
+ import kotlinx.coroutines.delay
22
23
  import java.io.File
23
24
  import kotlin.math.min
24
25
 
@@ -48,6 +49,7 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
48
49
  private var stableCounter = 0
49
50
  private var lastDetectionTimestamp: Long = 0L
50
51
  private var isCapturing = false
52
+ private var isDetaching = false
51
53
 
52
54
  // Coroutine scope for async operations
53
55
  private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
@@ -80,14 +82,22 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
80
82
 
81
83
  private fun setupCamera() {
82
84
  try {
85
+ // Move to STARTED state first
86
+ if (lifecycleRegistry.currentState == Lifecycle.State.CREATED) {
87
+ lifecycleRegistry.currentState = Lifecycle.State.STARTED
88
+ }
89
+
83
90
  cameraController = CameraController(context, this, previewView)
84
91
  cameraController?.onFrameAnalyzed = { rectangle, imageWidth, imageHeight ->
85
92
  handleDetectionResult(rectangle, imageWidth, imageHeight)
86
93
  }
87
94
  lastDetectionTimestamp = 0L
95
+
96
+ // Move to RESUMED state before starting camera
88
97
  if (lifecycleRegistry.currentState != Lifecycle.State.DESTROYED) {
89
98
  lifecycleRegistry.currentState = Lifecycle.State.RESUMED
90
99
  }
100
+
91
101
  cameraController?.startCamera(isUsingFrontCamera, true)
92
102
  if (isTorchEnabled) {
93
103
  cameraController?.setTorchEnabled(true)
@@ -181,6 +191,12 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
181
191
  return
182
192
  }
183
193
 
194
+ if (isDetaching) {
195
+ Log.d(TAG, "View is detaching, cannot capture")
196
+ promise?.reject("VIEW_DETACHING", "View is being removed")
197
+ return
198
+ }
199
+
184
200
  isCapturing = true
185
201
  Log.d(TAG, "Capture initiated with promise: ${promise != null}")
186
202
 
@@ -193,8 +209,14 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
193
209
  cameraController?.capturePhoto(
194
210
  outputDirectory = outputDir,
195
211
  onImageCaptured = { file ->
196
- scope.launch {
197
- processAndEmitImage(file, promise)
212
+ if (!isDetaching) {
213
+ scope.launch {
214
+ processAndEmitImage(file, promise)
215
+ }
216
+ } else {
217
+ Log.d(TAG, "View detaching, skipping image processing")
218
+ isCapturing = false
219
+ promise?.reject("VIEW_DETACHING", "View was removed during capture")
198
220
  }
199
221
  },
200
222
  onError = { exception ->
@@ -343,25 +365,61 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
343
365
  cameraController?.onFrameAnalyzed = { rectangle, imageWidth, imageHeight ->
344
366
  handleDetectionResult(rectangle, imageWidth, imageHeight)
345
367
  }
368
+
369
+ // Ensure proper lifecycle state before starting camera
370
+ if (lifecycleRegistry.currentState == Lifecycle.State.CREATED) {
371
+ lifecycleRegistry.currentState = Lifecycle.State.STARTED
372
+ }
373
+ if (lifecycleRegistry.currentState != Lifecycle.State.DESTROYED) {
374
+ lifecycleRegistry.currentState = Lifecycle.State.RESUMED
375
+ }
376
+
346
377
  cameraController?.startCamera(isUsingFrontCamera, true)
347
378
  if (isTorchEnabled) {
348
379
  cameraController?.setTorchEnabled(true)
349
380
  }
350
- lifecycleRegistry.currentState = Lifecycle.State.RESUMED
351
381
  }
352
382
 
353
383
  fun stopCamera() {
354
- cameraController?.stopCamera()
355
- overlayView.setRectangle(null, overlayColor)
356
- stableCounter = 0
357
- if (lifecycleRegistry.currentState != Lifecycle.State.DESTROYED) {
358
- lifecycleRegistry.currentState = Lifecycle.State.CREATED
384
+ if (!isCapturing) {
385
+ cameraController?.stopCamera()
386
+ overlayView.setRectangle(null, overlayColor)
387
+ stableCounter = 0
388
+ if (lifecycleRegistry.currentState != Lifecycle.State.DESTROYED) {
389
+ lifecycleRegistry.currentState = Lifecycle.State.CREATED
390
+ }
391
+ } else {
392
+ Log.d(TAG, "Cannot stop camera while capturing")
359
393
  }
360
394
  }
361
395
 
362
396
  override fun onDetachedFromWindow() {
363
397
  super.onDetachedFromWindow()
364
- stopCamera()
398
+ Log.d(TAG, "onDetachedFromWindow called, isCapturing: $isCapturing")
399
+
400
+ // Mark as detaching to prevent new captures
401
+ isDetaching = true
402
+
403
+ // Wait for any ongoing capture to complete before cleaning up
404
+ if (isCapturing) {
405
+ Log.d(TAG, "Waiting for capture to complete before cleanup...")
406
+ // Use a coroutine to wait briefly for capture to complete
407
+ scope.launch {
408
+ var waitCount = 0
409
+ while (isCapturing && waitCount < 20) { // Wait up to 2 seconds
410
+ delay(100)
411
+ waitCount++
412
+ }
413
+ performCleanup()
414
+ }
415
+ } else {
416
+ performCleanup()
417
+ }
418
+ }
419
+
420
+ private fun performCleanup() {
421
+ Log.d(TAG, "Performing cleanup")
422
+ cameraController?.stopCamera()
365
423
  lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
366
424
  cameraController?.shutdown()
367
425
  scope.cancel()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.149.0",
3
+ "version": "3.151.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",