react-native-rectangle-doc-scanner 3.44.0 → 3.44.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/dist/FullDocScanner.d.ts +2 -0
- package/dist/FullDocScanner.js +72 -7
- package/package.json +1 -1
- package/src/FullDocScanner.tsx +110 -18
package/dist/FullDocScanner.d.ts
CHANGED
package/dist/FullDocScanner.js
CHANGED
|
@@ -55,6 +55,8 @@ const normalizeCapturedDocument = (document) => {
|
|
|
55
55
|
};
|
|
56
56
|
const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3170f3', gridColor, gridLineWidth, showGrid, strings, manualCapture = false, minStableFrames, onError, enableGallery = true, cropWidth = 1200, cropHeight = 1600, }) => {
|
|
57
57
|
const [processing, setProcessing] = (0, react_1.useState)(false);
|
|
58
|
+
const [croppedImageData, setCroppedImageData] = (0, react_1.useState)(null);
|
|
59
|
+
const [isGalleryOpen, setIsGalleryOpen] = (0, react_1.useState)(false);
|
|
58
60
|
const resolvedGridColor = gridColor ?? overlayColor;
|
|
59
61
|
const docScannerRef = (0, react_1.useRef)(null);
|
|
60
62
|
const manualCapturePending = (0, react_1.useRef)(false);
|
|
@@ -64,6 +66,8 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
64
66
|
cancel: strings?.cancel,
|
|
65
67
|
processing: strings?.processing,
|
|
66
68
|
galleryButton: strings?.galleryButton,
|
|
69
|
+
retake: strings?.retake ?? 'Retake',
|
|
70
|
+
confirm: strings?.confirm ?? 'Confirm',
|
|
67
71
|
}), [strings]);
|
|
68
72
|
const emitError = (0, react_1.useCallback)((error, fallbackMessage) => {
|
|
69
73
|
console.error('[FullDocScanner] error', error);
|
|
@@ -87,7 +91,8 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
87
91
|
compressImageQuality: 0.9,
|
|
88
92
|
});
|
|
89
93
|
setProcessing(false);
|
|
90
|
-
|
|
94
|
+
// Show check_DP confirmation screen
|
|
95
|
+
setCroppedImageData({
|
|
91
96
|
path: croppedImage.path,
|
|
92
97
|
base64: croppedImage.data ?? undefined,
|
|
93
98
|
});
|
|
@@ -98,7 +103,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
98
103
|
emitError(error instanceof Error ? error : new Error(String(error)), 'Failed to crop image.');
|
|
99
104
|
}
|
|
100
105
|
}
|
|
101
|
-
}, [cropWidth, cropHeight, emitError
|
|
106
|
+
}, [cropWidth, cropHeight, emitError]);
|
|
102
107
|
const handleCapture = (0, react_1.useCallback)(async (document) => {
|
|
103
108
|
console.log('[FullDocScanner] handleCapture called:', {
|
|
104
109
|
origin: document.origin,
|
|
@@ -141,15 +146,17 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
141
146
|
}, [processing]);
|
|
142
147
|
const handleGalleryPick = (0, react_1.useCallback)(async () => {
|
|
143
148
|
console.log('[FullDocScanner] handleGalleryPick called');
|
|
144
|
-
if (processing) {
|
|
149
|
+
if (processing || isGalleryOpen) {
|
|
145
150
|
return;
|
|
146
151
|
}
|
|
147
152
|
try {
|
|
153
|
+
setIsGalleryOpen(true);
|
|
148
154
|
const result = await (0, react_native_image_picker_1.launchImageLibrary)({
|
|
149
155
|
mediaType: 'photo',
|
|
150
156
|
quality: 1,
|
|
151
157
|
selectionLimit: 1,
|
|
152
158
|
});
|
|
159
|
+
setIsGalleryOpen(false);
|
|
153
160
|
if (result.didCancel || !result.assets?.[0]?.uri) {
|
|
154
161
|
console.log('[FullDocScanner] User cancelled gallery picker');
|
|
155
162
|
return;
|
|
@@ -160,15 +167,35 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
160
167
|
await openCropper(imageUri);
|
|
161
168
|
}
|
|
162
169
|
catch (error) {
|
|
170
|
+
setIsGalleryOpen(false);
|
|
163
171
|
emitError(error instanceof Error ? error : new Error(String(error)), 'Failed to pick image from gallery.');
|
|
164
172
|
}
|
|
165
|
-
}, [processing, openCropper, emitError]);
|
|
173
|
+
}, [processing, isGalleryOpen, openCropper, emitError]);
|
|
166
174
|
const handleClose = (0, react_1.useCallback)(() => {
|
|
167
175
|
onClose?.();
|
|
168
176
|
}, [onClose]);
|
|
177
|
+
const handleConfirm = (0, react_1.useCallback)(() => {
|
|
178
|
+
if (croppedImageData) {
|
|
179
|
+
onResult({
|
|
180
|
+
path: croppedImageData.path,
|
|
181
|
+
base64: croppedImageData.base64,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}, [croppedImageData, onResult]);
|
|
185
|
+
const handleRetake = (0, react_1.useCallback)(() => {
|
|
186
|
+
setCroppedImageData(null);
|
|
187
|
+
}, []);
|
|
169
188
|
return (react_1.default.createElement(react_native_1.View, { style: styles.container },
|
|
170
|
-
|
|
171
|
-
|
|
189
|
+
croppedImageData ? (
|
|
190
|
+
// check_DP: Show confirmation screen
|
|
191
|
+
react_1.default.createElement(react_native_1.View, { style: styles.confirmationContainer },
|
|
192
|
+
react_1.default.createElement(react_native_1.Image, { source: { uri: croppedImageData.path }, style: styles.previewImage, resizeMode: "contain" }),
|
|
193
|
+
react_1.default.createElement(react_native_1.View, { style: styles.confirmationButtons },
|
|
194
|
+
react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.retakeButton], onPress: handleRetake, accessibilityLabel: mergedStrings.retake, accessibilityRole: "button" },
|
|
195
|
+
react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.retake)),
|
|
196
|
+
react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.confirmButtonPrimary], onPress: handleConfirm, accessibilityLabel: mergedStrings.confirm, accessibilityRole: "button" },
|
|
197
|
+
react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.confirm))))) : (react_1.default.createElement(react_native_1.View, { style: styles.flex },
|
|
198
|
+
react_1.default.createElement(DocScanner_1.DocScanner, { ref: docScannerRef, autoCapture: !manualCapture && !isGalleryOpen, overlayColor: overlayColor, showGrid: showGrid, gridColor: resolvedGridColor, gridLineWidth: gridLineWidth, minStableFrames: minStableFrames ?? 6, detectionConfig: detectionConfig, onCapture: handleCapture, showManualCaptureButton: false },
|
|
172
199
|
react_1.default.createElement(react_native_1.View, { style: styles.overlayTop, pointerEvents: "box-none" },
|
|
173
200
|
react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.closeButton, onPress: handleClose, accessibilityLabel: mergedStrings.cancel, accessibilityRole: "button" },
|
|
174
201
|
react_1.default.createElement(react_native_1.Text, { style: styles.closeButtonLabel }, "\u00D7"))),
|
|
@@ -180,7 +207,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
180
207
|
enableGallery && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.galleryButton, processing && styles.buttonDisabled], onPress: handleGalleryPick, disabled: processing, accessibilityLabel: mergedStrings.galleryButton, accessibilityRole: "button" },
|
|
181
208
|
react_1.default.createElement(react_native_1.Text, { style: styles.galleryButtonText }, "\uD83D\uDCC1"))),
|
|
182
209
|
react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.shutterButton, processing && styles.buttonDisabled], onPress: triggerManualCapture, disabled: processing, accessibilityLabel: mergedStrings.manualHint, accessibilityRole: "button" },
|
|
183
|
-
react_1.default.createElement(react_native_1.View, { style: styles.shutterInner }))))),
|
|
210
|
+
react_1.default.createElement(react_native_1.View, { style: styles.shutterInner })))))),
|
|
184
211
|
processing && (react_1.default.createElement(react_native_1.View, { style: styles.processingOverlay },
|
|
185
212
|
react_1.default.createElement(react_native_1.ActivityIndicator, { size: "large", color: overlayColor }),
|
|
186
213
|
mergedStrings.processing && (react_1.default.createElement(react_native_1.Text, { style: styles.processingText }, mergedStrings.processing))))));
|
|
@@ -288,4 +315,42 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
288
315
|
fontSize: 16,
|
|
289
316
|
fontWeight: '600',
|
|
290
317
|
},
|
|
318
|
+
confirmationContainer: {
|
|
319
|
+
flex: 1,
|
|
320
|
+
backgroundColor: '#000',
|
|
321
|
+
justifyContent: 'center',
|
|
322
|
+
alignItems: 'center',
|
|
323
|
+
},
|
|
324
|
+
previewImage: {
|
|
325
|
+
width: '100%',
|
|
326
|
+
height: '80%',
|
|
327
|
+
},
|
|
328
|
+
confirmationButtons: {
|
|
329
|
+
flexDirection: 'row',
|
|
330
|
+
justifyContent: 'center',
|
|
331
|
+
alignItems: 'center',
|
|
332
|
+
gap: 24,
|
|
333
|
+
paddingVertical: 32,
|
|
334
|
+
},
|
|
335
|
+
confirmButton: {
|
|
336
|
+
paddingHorizontal: 40,
|
|
337
|
+
paddingVertical: 16,
|
|
338
|
+
borderRadius: 12,
|
|
339
|
+
minWidth: 140,
|
|
340
|
+
alignItems: 'center',
|
|
341
|
+
justifyContent: 'center',
|
|
342
|
+
},
|
|
343
|
+
retakeButton: {
|
|
344
|
+
backgroundColor: 'rgba(255,255,255,0.2)',
|
|
345
|
+
borderWidth: 2,
|
|
346
|
+
borderColor: '#fff',
|
|
347
|
+
},
|
|
348
|
+
confirmButtonPrimary: {
|
|
349
|
+
backgroundColor: '#3170f3',
|
|
350
|
+
},
|
|
351
|
+
confirmButtonText: {
|
|
352
|
+
color: '#fff',
|
|
353
|
+
fontSize: 18,
|
|
354
|
+
fontWeight: '600',
|
|
355
|
+
},
|
|
291
356
|
});
|
package/package.json
CHANGED
package/src/FullDocScanner.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
|
2
2
|
import {
|
|
3
3
|
ActivityIndicator,
|
|
4
4
|
Alert,
|
|
5
|
+
Image,
|
|
5
6
|
StyleSheet,
|
|
6
7
|
Text,
|
|
7
8
|
TouchableOpacity,
|
|
@@ -41,6 +42,8 @@ export interface FullDocScannerStrings {
|
|
|
41
42
|
cancel?: string;
|
|
42
43
|
processing?: string;
|
|
43
44
|
galleryButton?: string;
|
|
45
|
+
retake?: string;
|
|
46
|
+
confirm?: string;
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
export interface FullDocScannerProps {
|
|
@@ -77,6 +80,8 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
77
80
|
cropHeight = 1600,
|
|
78
81
|
}) => {
|
|
79
82
|
const [processing, setProcessing] = useState(false);
|
|
83
|
+
const [croppedImageData, setCroppedImageData] = useState<{path: string; base64?: string} | null>(null);
|
|
84
|
+
const [isGalleryOpen, setIsGalleryOpen] = useState(false);
|
|
80
85
|
const resolvedGridColor = gridColor ?? overlayColor;
|
|
81
86
|
const docScannerRef = useRef<DocScannerHandle | null>(null);
|
|
82
87
|
const manualCapturePending = useRef(false);
|
|
@@ -88,6 +93,8 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
88
93
|
cancel: strings?.cancel,
|
|
89
94
|
processing: strings?.processing,
|
|
90
95
|
galleryButton: strings?.galleryButton,
|
|
96
|
+
retake: strings?.retake ?? 'Retake',
|
|
97
|
+
confirm: strings?.confirm ?? 'Confirm',
|
|
91
98
|
}),
|
|
92
99
|
[strings],
|
|
93
100
|
);
|
|
@@ -121,7 +128,8 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
121
128
|
|
|
122
129
|
setProcessing(false);
|
|
123
130
|
|
|
124
|
-
|
|
131
|
+
// Show check_DP confirmation screen
|
|
132
|
+
setCroppedImageData({
|
|
125
133
|
path: croppedImage.path,
|
|
126
134
|
base64: croppedImage.data ?? undefined,
|
|
127
135
|
});
|
|
@@ -135,7 +143,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
135
143
|
}
|
|
136
144
|
}
|
|
137
145
|
},
|
|
138
|
-
[cropWidth, cropHeight, emitError
|
|
146
|
+
[cropWidth, cropHeight, emitError],
|
|
139
147
|
);
|
|
140
148
|
|
|
141
149
|
const handleCapture = useCallback(
|
|
@@ -189,17 +197,20 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
189
197
|
|
|
190
198
|
const handleGalleryPick = useCallback(async () => {
|
|
191
199
|
console.log('[FullDocScanner] handleGalleryPick called');
|
|
192
|
-
if (processing) {
|
|
200
|
+
if (processing || isGalleryOpen) {
|
|
193
201
|
return;
|
|
194
202
|
}
|
|
195
203
|
|
|
196
204
|
try {
|
|
205
|
+
setIsGalleryOpen(true);
|
|
197
206
|
const result = await launchImageLibrary({
|
|
198
207
|
mediaType: 'photo',
|
|
199
208
|
quality: 1,
|
|
200
209
|
selectionLimit: 1,
|
|
201
210
|
});
|
|
202
211
|
|
|
212
|
+
setIsGalleryOpen(false);
|
|
213
|
+
|
|
203
214
|
if (result.didCancel || !result.assets?.[0]?.uri) {
|
|
204
215
|
console.log('[FullDocScanner] User cancelled gallery picker');
|
|
205
216
|
return;
|
|
@@ -211,32 +222,74 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
211
222
|
// Open cropper with the selected image
|
|
212
223
|
await openCropper(imageUri);
|
|
213
224
|
} catch (error) {
|
|
225
|
+
setIsGalleryOpen(false);
|
|
214
226
|
emitError(
|
|
215
227
|
error instanceof Error ? error : new Error(String(error)),
|
|
216
228
|
'Failed to pick image from gallery.',
|
|
217
229
|
);
|
|
218
230
|
}
|
|
219
|
-
}, [processing, openCropper, emitError]);
|
|
231
|
+
}, [processing, isGalleryOpen, openCropper, emitError]);
|
|
220
232
|
|
|
221
233
|
const handleClose = useCallback(() => {
|
|
222
234
|
onClose?.();
|
|
223
235
|
}, [onClose]);
|
|
224
236
|
|
|
237
|
+
const handleConfirm = useCallback(() => {
|
|
238
|
+
if (croppedImageData) {
|
|
239
|
+
onResult({
|
|
240
|
+
path: croppedImageData.path,
|
|
241
|
+
base64: croppedImageData.base64,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}, [croppedImageData, onResult]);
|
|
245
|
+
|
|
246
|
+
const handleRetake = useCallback(() => {
|
|
247
|
+
setCroppedImageData(null);
|
|
248
|
+
}, []);
|
|
249
|
+
|
|
225
250
|
return (
|
|
226
251
|
<View style={styles.container}>
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
252
|
+
{croppedImageData ? (
|
|
253
|
+
// check_DP: Show confirmation screen
|
|
254
|
+
<View style={styles.confirmationContainer}>
|
|
255
|
+
<Image
|
|
256
|
+
source={{ uri: croppedImageData.path }}
|
|
257
|
+
style={styles.previewImage}
|
|
258
|
+
resizeMode="contain"
|
|
259
|
+
/>
|
|
260
|
+
<View style={styles.confirmationButtons}>
|
|
261
|
+
<TouchableOpacity
|
|
262
|
+
style={[styles.confirmButton, styles.retakeButton]}
|
|
263
|
+
onPress={handleRetake}
|
|
264
|
+
accessibilityLabel={mergedStrings.retake}
|
|
265
|
+
accessibilityRole="button"
|
|
266
|
+
>
|
|
267
|
+
<Text style={styles.confirmButtonText}>{mergedStrings.retake}</Text>
|
|
268
|
+
</TouchableOpacity>
|
|
269
|
+
<TouchableOpacity
|
|
270
|
+
style={[styles.confirmButton, styles.confirmButtonPrimary]}
|
|
271
|
+
onPress={handleConfirm}
|
|
272
|
+
accessibilityLabel={mergedStrings.confirm}
|
|
273
|
+
accessibilityRole="button"
|
|
274
|
+
>
|
|
275
|
+
<Text style={styles.confirmButtonText}>{mergedStrings.confirm}</Text>
|
|
276
|
+
</TouchableOpacity>
|
|
277
|
+
</View>
|
|
278
|
+
</View>
|
|
279
|
+
) : (
|
|
280
|
+
<View style={styles.flex}>
|
|
281
|
+
<DocScanner
|
|
282
|
+
ref={docScannerRef}
|
|
283
|
+
autoCapture={!manualCapture && !isGalleryOpen}
|
|
284
|
+
overlayColor={overlayColor}
|
|
285
|
+
showGrid={showGrid}
|
|
286
|
+
gridColor={resolvedGridColor}
|
|
287
|
+
gridLineWidth={gridLineWidth}
|
|
288
|
+
minStableFrames={minStableFrames ?? 6}
|
|
289
|
+
detectionConfig={detectionConfig}
|
|
290
|
+
onCapture={handleCapture}
|
|
291
|
+
showManualCaptureButton={false}
|
|
292
|
+
>
|
|
240
293
|
<View style={styles.overlayTop} pointerEvents="box-none">
|
|
241
294
|
<TouchableOpacity
|
|
242
295
|
style={styles.closeButton}
|
|
@@ -282,7 +335,8 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
282
335
|
</TouchableOpacity>
|
|
283
336
|
</View>
|
|
284
337
|
</DocScanner>
|
|
285
|
-
|
|
338
|
+
</View>
|
|
339
|
+
)}
|
|
286
340
|
|
|
287
341
|
{processing && (
|
|
288
342
|
<View style={styles.processingOverlay}>
|
|
@@ -398,4 +452,42 @@ const styles = StyleSheet.create({
|
|
|
398
452
|
fontSize: 16,
|
|
399
453
|
fontWeight: '600',
|
|
400
454
|
},
|
|
455
|
+
confirmationContainer: {
|
|
456
|
+
flex: 1,
|
|
457
|
+
backgroundColor: '#000',
|
|
458
|
+
justifyContent: 'center',
|
|
459
|
+
alignItems: 'center',
|
|
460
|
+
},
|
|
461
|
+
previewImage: {
|
|
462
|
+
width: '100%',
|
|
463
|
+
height: '80%',
|
|
464
|
+
},
|
|
465
|
+
confirmationButtons: {
|
|
466
|
+
flexDirection: 'row',
|
|
467
|
+
justifyContent: 'center',
|
|
468
|
+
alignItems: 'center',
|
|
469
|
+
gap: 24,
|
|
470
|
+
paddingVertical: 32,
|
|
471
|
+
},
|
|
472
|
+
confirmButton: {
|
|
473
|
+
paddingHorizontal: 40,
|
|
474
|
+
paddingVertical: 16,
|
|
475
|
+
borderRadius: 12,
|
|
476
|
+
minWidth: 140,
|
|
477
|
+
alignItems: 'center',
|
|
478
|
+
justifyContent: 'center',
|
|
479
|
+
},
|
|
480
|
+
retakeButton: {
|
|
481
|
+
backgroundColor: 'rgba(255,255,255,0.2)',
|
|
482
|
+
borderWidth: 2,
|
|
483
|
+
borderColor: '#fff',
|
|
484
|
+
},
|
|
485
|
+
confirmButtonPrimary: {
|
|
486
|
+
backgroundColor: '#3170f3',
|
|
487
|
+
},
|
|
488
|
+
confirmButtonText: {
|
|
489
|
+
color: '#fff',
|
|
490
|
+
fontSize: 18,
|
|
491
|
+
fontWeight: '600',
|
|
492
|
+
},
|
|
401
493
|
});
|