react-native-rectangle-doc-scanner 3.68.0 → 3.70.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/dist/FullDocScanner.js
CHANGED
|
@@ -58,11 +58,13 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
58
58
|
const [croppedImageData, setCroppedImageData] = (0, react_1.useState)(null);
|
|
59
59
|
const [isGalleryOpen, setIsGalleryOpen] = (0, react_1.useState)(false);
|
|
60
60
|
const [rectangleDetected, setRectangleDetected] = (0, react_1.useState)(false);
|
|
61
|
+
const [rectangleHint, setRectangleHint] = (0, react_1.useState)(false);
|
|
61
62
|
const resolvedGridColor = gridColor ?? overlayColor;
|
|
62
63
|
const docScannerRef = (0, react_1.useRef)(null);
|
|
63
64
|
const captureModeRef = (0, react_1.useRef)(null);
|
|
64
65
|
const captureInProgressRef = (0, react_1.useRef)(false);
|
|
65
|
-
const
|
|
66
|
+
const rectangleCaptureTimeoutRef = (0, react_1.useRef)(null);
|
|
67
|
+
const rectangleHintTimeoutRef = (0, react_1.useRef)(null);
|
|
66
68
|
const mergedStrings = (0, react_1.useMemo)(() => ({
|
|
67
69
|
captureHint: strings?.captureHint,
|
|
68
70
|
manualHint: strings?.manualHint,
|
|
@@ -166,6 +168,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
166
168
|
processing,
|
|
167
169
|
hasRef: hasScanner,
|
|
168
170
|
rectangleDetected,
|
|
171
|
+
rectangleHint,
|
|
169
172
|
currentCaptureMode: captureModeRef.current,
|
|
170
173
|
captureInProgress: captureInProgressRef.current,
|
|
171
174
|
});
|
|
@@ -214,7 +217,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
214
217
|
emitError(error, 'Failed to capture image. Please try again.');
|
|
215
218
|
}
|
|
216
219
|
});
|
|
217
|
-
}, [processing, rectangleDetected, emitError]);
|
|
220
|
+
}, [processing, rectangleDetected, rectangleHint, emitError]);
|
|
218
221
|
const handleGalleryPick = (0, react_1.useCallback)(async () => {
|
|
219
222
|
console.log('[FullDocScanner] handleGalleryPick called');
|
|
220
223
|
if (processing || isGalleryOpen) {
|
|
@@ -266,11 +269,16 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
266
269
|
setCroppedImageData(null);
|
|
267
270
|
setProcessing(false);
|
|
268
271
|
setRectangleDetected(false);
|
|
272
|
+
setRectangleHint(false);
|
|
269
273
|
captureModeRef.current = null;
|
|
270
274
|
captureInProgressRef.current = false;
|
|
271
|
-
if (
|
|
272
|
-
clearTimeout(
|
|
273
|
-
|
|
275
|
+
if (rectangleCaptureTimeoutRef.current) {
|
|
276
|
+
clearTimeout(rectangleCaptureTimeoutRef.current);
|
|
277
|
+
rectangleCaptureTimeoutRef.current = null;
|
|
278
|
+
}
|
|
279
|
+
if (rectangleHintTimeoutRef.current) {
|
|
280
|
+
clearTimeout(rectangleHintTimeoutRef.current);
|
|
281
|
+
rectangleHintTimeoutRef.current = null;
|
|
274
282
|
}
|
|
275
283
|
// Reset DocScanner state
|
|
276
284
|
if (docScannerRef.current?.reset) {
|
|
@@ -279,40 +287,62 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
279
287
|
}, []);
|
|
280
288
|
const handleRectangleDetect = (0, react_1.useCallback)((event) => {
|
|
281
289
|
const stableCounter = event.stableCounter ?? 0;
|
|
282
|
-
const
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
if (
|
|
287
|
-
clearTimeout(
|
|
288
|
-
rectangleTimeoutRef.current = null;
|
|
290
|
+
const rectangleCoordinates = event.rectangleOnScreen ?? event.rectangleCoordinates;
|
|
291
|
+
const hasRectangle = Boolean(rectangleCoordinates);
|
|
292
|
+
const captureReady = hasRectangle && event.lastDetectionType === 0 && stableCounter >= 1;
|
|
293
|
+
const scheduleClear = (ref, clearFn) => {
|
|
294
|
+
if (ref.current) {
|
|
295
|
+
clearTimeout(ref.current);
|
|
289
296
|
}
|
|
290
|
-
|
|
297
|
+
ref.current = setTimeout(() => {
|
|
298
|
+
ref.current = null;
|
|
299
|
+
clearFn();
|
|
300
|
+
}, 350);
|
|
301
|
+
};
|
|
302
|
+
if (hasRectangle) {
|
|
303
|
+
scheduleClear(rectangleHintTimeoutRef, () => setRectangleHint(false));
|
|
304
|
+
setRectangleHint(true);
|
|
291
305
|
}
|
|
292
306
|
else {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
307
|
+
if (rectangleHintTimeoutRef.current) {
|
|
308
|
+
clearTimeout(rectangleHintTimeoutRef.current);
|
|
309
|
+
rectangleHintTimeoutRef.current = null;
|
|
296
310
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
311
|
+
setRectangleHint(false);
|
|
312
|
+
}
|
|
313
|
+
if (captureReady) {
|
|
314
|
+
scheduleClear(rectangleCaptureTimeoutRef, () => {
|
|
315
|
+
console.log('[FullDocScanner] Rectangle timeout - clearing detection');
|
|
301
316
|
setRectangleDetected(false);
|
|
317
|
+
});
|
|
318
|
+
setRectangleDetected(true);
|
|
319
|
+
}
|
|
320
|
+
else if (!hasRectangle) {
|
|
321
|
+
if (rectangleCaptureTimeoutRef.current) {
|
|
322
|
+
clearTimeout(rectangleCaptureTimeoutRef.current);
|
|
323
|
+
rectangleCaptureTimeoutRef.current = null;
|
|
324
|
+
}
|
|
325
|
+
setRectangleDetected(false);
|
|
326
|
+
}
|
|
327
|
+
else if (rectangleDetected) {
|
|
328
|
+
scheduleClear(rectangleCaptureTimeoutRef, () => {
|
|
302
329
|
console.log('[FullDocScanner] Rectangle timeout - clearing detection');
|
|
303
|
-
|
|
330
|
+
setRectangleDetected(false);
|
|
331
|
+
});
|
|
304
332
|
}
|
|
305
333
|
console.log('[FullDocScanner] Rectangle detection update', {
|
|
306
334
|
lastDetectionType: event.lastDetectionType,
|
|
307
335
|
stableCounter,
|
|
308
336
|
hasRectangle,
|
|
309
|
-
|
|
310
|
-
rectangleDetected: isGoodRectangle,
|
|
337
|
+
captureReady,
|
|
311
338
|
});
|
|
312
|
-
}, []);
|
|
339
|
+
}, [rectangleDetected]);
|
|
313
340
|
(0, react_1.useEffect)(() => () => {
|
|
314
|
-
if (
|
|
315
|
-
clearTimeout(
|
|
341
|
+
if (rectangleCaptureTimeoutRef.current) {
|
|
342
|
+
clearTimeout(rectangleCaptureTimeoutRef.current);
|
|
343
|
+
}
|
|
344
|
+
if (rectangleHintTimeoutRef.current) {
|
|
345
|
+
clearTimeout(rectangleHintTimeoutRef.current);
|
|
316
346
|
}
|
|
317
347
|
}, []);
|
|
318
348
|
return (react_1.default.createElement(react_native_1.View, { style: styles.container },
|
|
@@ -339,7 +369,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
339
369
|
react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.shutterButton, processing && styles.buttonDisabled], onPress: triggerManualCapture, disabled: processing, accessibilityLabel: mergedStrings.manualHint, accessibilityRole: "button" },
|
|
340
370
|
react_1.default.createElement(react_native_1.View, { style: [
|
|
341
371
|
styles.shutterInner,
|
|
342
|
-
|
|
372
|
+
rectangleHint && { backgroundColor: overlayColor }
|
|
343
373
|
] })))))),
|
|
344
374
|
processing && (react_1.default.createElement(react_native_1.View, { style: styles.processingOverlay },
|
|
345
375
|
react_1.default.createElement(react_native_1.ActivityIndicator, { size: "large", color: overlayColor }),
|
package/package.json
CHANGED
package/src/FullDocScanner.tsx
CHANGED
|
@@ -86,11 +86,13 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
86
86
|
const [croppedImageData, setCroppedImageData] = useState<{path: string; base64?: string} | null>(null);
|
|
87
87
|
const [isGalleryOpen, setIsGalleryOpen] = useState(false);
|
|
88
88
|
const [rectangleDetected, setRectangleDetected] = useState(false);
|
|
89
|
+
const [rectangleHint, setRectangleHint] = useState(false);
|
|
89
90
|
const resolvedGridColor = gridColor ?? overlayColor;
|
|
90
91
|
const docScannerRef = useRef<DocScannerHandle | null>(null);
|
|
91
92
|
const captureModeRef = useRef<'grid' | 'no-grid' | null>(null);
|
|
92
93
|
const captureInProgressRef = useRef(false);
|
|
93
|
-
const
|
|
94
|
+
const rectangleCaptureTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
95
|
+
const rectangleHintTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
94
96
|
|
|
95
97
|
const mergedStrings = useMemo(
|
|
96
98
|
() => ({
|
|
@@ -226,6 +228,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
226
228
|
processing,
|
|
227
229
|
hasRef: hasScanner,
|
|
228
230
|
rectangleDetected,
|
|
231
|
+
rectangleHint,
|
|
229
232
|
currentCaptureMode: captureModeRef.current,
|
|
230
233
|
captureInProgress: captureInProgressRef.current,
|
|
231
234
|
});
|
|
@@ -288,7 +291,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
288
291
|
);
|
|
289
292
|
}
|
|
290
293
|
});
|
|
291
|
-
}, [processing, rectangleDetected, emitError]);
|
|
294
|
+
}, [processing, rectangleDetected, rectangleHint, emitError]);
|
|
292
295
|
|
|
293
296
|
const handleGalleryPick = useCallback(async () => {
|
|
294
297
|
console.log('[FullDocScanner] handleGalleryPick called');
|
|
@@ -353,11 +356,16 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
353
356
|
setCroppedImageData(null);
|
|
354
357
|
setProcessing(false);
|
|
355
358
|
setRectangleDetected(false);
|
|
359
|
+
setRectangleHint(false);
|
|
356
360
|
captureModeRef.current = null;
|
|
357
361
|
captureInProgressRef.current = false;
|
|
358
|
-
if (
|
|
359
|
-
clearTimeout(
|
|
360
|
-
|
|
362
|
+
if (rectangleCaptureTimeoutRef.current) {
|
|
363
|
+
clearTimeout(rectangleCaptureTimeoutRef.current);
|
|
364
|
+
rectangleCaptureTimeoutRef.current = null;
|
|
365
|
+
}
|
|
366
|
+
if (rectangleHintTimeoutRef.current) {
|
|
367
|
+
clearTimeout(rectangleHintTimeoutRef.current);
|
|
368
|
+
rectangleHintTimeoutRef.current = null;
|
|
361
369
|
}
|
|
362
370
|
// Reset DocScanner state
|
|
363
371
|
if (docScannerRef.current?.reset) {
|
|
@@ -367,45 +375,72 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
367
375
|
|
|
368
376
|
const handleRectangleDetect = useCallback((event: RectangleDetectEvent) => {
|
|
369
377
|
const stableCounter = event.stableCounter ?? 0;
|
|
370
|
-
const
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
+
const rectangleCoordinates = event.rectangleOnScreen ?? event.rectangleCoordinates;
|
|
379
|
+
const hasRectangle = Boolean(rectangleCoordinates);
|
|
380
|
+
const captureReady = hasRectangle && event.lastDetectionType === 0 && stableCounter >= 1;
|
|
381
|
+
|
|
382
|
+
const scheduleClear = (
|
|
383
|
+
ref: React.MutableRefObject<ReturnType<typeof setTimeout> | null>,
|
|
384
|
+
clearFn: () => void,
|
|
385
|
+
) => {
|
|
386
|
+
if (ref.current) {
|
|
387
|
+
clearTimeout(ref.current);
|
|
378
388
|
}
|
|
379
|
-
|
|
389
|
+
ref.current = setTimeout(() => {
|
|
390
|
+
ref.current = null;
|
|
391
|
+
clearFn();
|
|
392
|
+
}, 350);
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
if (hasRectangle) {
|
|
396
|
+
scheduleClear(rectangleHintTimeoutRef, () => setRectangleHint(false));
|
|
397
|
+
setRectangleHint(true);
|
|
380
398
|
} else {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
399
|
+
if (rectangleHintTimeoutRef.current) {
|
|
400
|
+
clearTimeout(rectangleHintTimeoutRef.current);
|
|
401
|
+
rectangleHintTimeoutRef.current = null;
|
|
384
402
|
}
|
|
385
|
-
|
|
403
|
+
setRectangleHint(false);
|
|
404
|
+
}
|
|
386
405
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
406
|
+
if (captureReady) {
|
|
407
|
+
scheduleClear(rectangleCaptureTimeoutRef, () => {
|
|
408
|
+
console.log('[FullDocScanner] Rectangle timeout - clearing detection');
|
|
390
409
|
setRectangleDetected(false);
|
|
410
|
+
});
|
|
411
|
+
setRectangleDetected(true);
|
|
412
|
+
} else if (!hasRectangle) {
|
|
413
|
+
if (rectangleCaptureTimeoutRef.current) {
|
|
414
|
+
clearTimeout(rectangleCaptureTimeoutRef.current);
|
|
415
|
+
rectangleCaptureTimeoutRef.current = null;
|
|
416
|
+
}
|
|
417
|
+
setRectangleDetected(false);
|
|
418
|
+
} else if (rectangleDetected) {
|
|
419
|
+
scheduleClear(rectangleCaptureTimeoutRef, () => {
|
|
391
420
|
console.log('[FullDocScanner] Rectangle timeout - clearing detection');
|
|
392
|
-
|
|
421
|
+
setRectangleDetected(false);
|
|
422
|
+
});
|
|
393
423
|
}
|
|
394
424
|
|
|
395
425
|
console.log('[FullDocScanner] Rectangle detection update', {
|
|
396
426
|
lastDetectionType: event.lastDetectionType,
|
|
397
427
|
stableCounter,
|
|
398
428
|
hasRectangle,
|
|
399
|
-
|
|
400
|
-
rectangleDetected: isGoodRectangle,
|
|
429
|
+
captureReady,
|
|
401
430
|
});
|
|
402
|
-
}, []);
|
|
431
|
+
}, [rectangleDetected]);
|
|
403
432
|
|
|
404
|
-
useEffect(
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
433
|
+
useEffect(
|
|
434
|
+
() => () => {
|
|
435
|
+
if (rectangleCaptureTimeoutRef.current) {
|
|
436
|
+
clearTimeout(rectangleCaptureTimeoutRef.current);
|
|
437
|
+
}
|
|
438
|
+
if (rectangleHintTimeoutRef.current) {
|
|
439
|
+
clearTimeout(rectangleHintTimeoutRef.current);
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
[],
|
|
443
|
+
);
|
|
409
444
|
|
|
410
445
|
return (
|
|
411
446
|
<View style={styles.container}>
|
|
@@ -494,7 +529,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
494
529
|
>
|
|
495
530
|
<View style={[
|
|
496
531
|
styles.shutterInner,
|
|
497
|
-
|
|
532
|
+
rectangleHint && { backgroundColor: overlayColor }
|
|
498
533
|
]} />
|
|
499
534
|
</TouchableOpacity>
|
|
500
535
|
</View>
|
|
@@ -35,22 +35,27 @@ RCT_EXPORT_VIEW_PROPERTY(brightness, float)
|
|
|
35
35
|
RCT_EXPORT_VIEW_PROPERTY(contrast, float)
|
|
36
36
|
|
|
37
37
|
// Main capture method - returns a Promise
|
|
38
|
-
RCT_EXPORT_METHOD(capture:(nullable
|
|
38
|
+
RCT_EXPORT_METHOD(capture:(nullable id)reactTag
|
|
39
39
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
40
40
|
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
41
41
|
NSLog(@"[RNPdfScannerManager] capture called with reactTag: %@", reactTag);
|
|
42
42
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
43
43
|
DocumentScannerView *targetView = nil;
|
|
44
|
+
NSNumber *resolvedTag = ([reactTag isKindOfClass:[NSNumber class]]) ? (NSNumber *)reactTag : nil;
|
|
44
45
|
|
|
45
|
-
if (reactTag) {
|
|
46
|
-
|
|
46
|
+
if (!resolvedTag && reactTag) {
|
|
47
|
+
NSLog(@"[RNPdfScannerManager] Unexpected reactTag type %@ - ignoring reactTag", NSStringFromClass([reactTag class]));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (resolvedTag) {
|
|
51
|
+
UIView *view = [self.bridge.uiManager viewForReactTag:resolvedTag];
|
|
47
52
|
if ([view isKindOfClass:[DocumentScannerView class]]) {
|
|
48
53
|
targetView = (DocumentScannerView *)view;
|
|
49
54
|
self->_scannerView = targetView;
|
|
50
55
|
} else if (view) {
|
|
51
|
-
NSLog(@"[RNPdfScannerManager] View for tag %@ is not DocumentScannerView: %@",
|
|
56
|
+
NSLog(@"[RNPdfScannerManager] View for tag %@ is not DocumentScannerView: %@", resolvedTag, NSStringFromClass(view.class));
|
|
52
57
|
} else {
|
|
53
|
-
NSLog(@"[RNPdfScannerManager] No view found for tag %@",
|
|
58
|
+
NSLog(@"[RNPdfScannerManager] No view found for tag %@", resolvedTag);
|
|
54
59
|
}
|
|
55
60
|
}
|
|
56
61
|
|
|
@@ -61,7 +66,9 @@ RCT_EXPORT_METHOD(capture:(nullable NSNumber *)reactTag
|
|
|
61
66
|
|
|
62
67
|
if (!targetView) {
|
|
63
68
|
NSLog(@"[RNPdfScannerManager] ERROR: No scanner view available for capture");
|
|
64
|
-
reject
|
|
69
|
+
if (reject) {
|
|
70
|
+
reject(@"NO_VIEW", @"Document scanner view is not ready", nil);
|
|
71
|
+
}
|
|
65
72
|
return;
|
|
66
73
|
}
|
|
67
74
|
|
|
@@ -31,12 +31,12 @@ class PdfScanner extends React.Component {
|
|
|
31
31
|
const handle = findNodeHandle(this.scannerRef.current);
|
|
32
32
|
console.log('[PdfScanner/ios.js] node handle (reactTag):', handle);
|
|
33
33
|
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
if (typeof handle !== 'number') {
|
|
35
|
+
const error = new Error('DocumentScanner native view is not ready');
|
|
36
|
+
console.error('[PdfScanner/ios.js] ERROR:', error.message);
|
|
37
|
+
return Promise.reject(error);
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
// Call native method with reactTag - now returns a Promise
|
|
40
40
|
console.log('[PdfScanner/ios.js] Calling native capture with handle:', handle);
|
|
41
41
|
return NativeModules.RNPdfScannerManager.capture(handle);
|
|
42
42
|
}
|