react-native-rectangle-doc-scanner 3.44.0 → 3.44.2

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.
@@ -15,6 +15,8 @@ export interface FullDocScannerStrings {
15
15
  cancel?: string;
16
16
  processing?: string;
17
17
  galleryButton?: string;
18
+ retake?: string;
19
+ confirm?: string;
18
20
  }
19
21
  export interface FullDocScannerProps {
20
22
  onResult: (result: FullDocScannerResult) => void;
@@ -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
- onResult({
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, onResult]);
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,
@@ -108,8 +113,18 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
108
113
  manualCapturePending.current = false;
109
114
  }
110
115
  const normalizedDoc = normalizeCapturedDocument(document);
111
- // Open cropper with the captured image
112
- await openCropper(normalizedDoc.path);
116
+ // Auto-capture: Use already cropped image, skip cropper
117
+ if (document.origin === 'auto' && normalizedDoc.croppedPath) {
118
+ console.log('[FullDocScanner] Auto-capture: using pre-cropped image');
119
+ setCroppedImageData({
120
+ path: normalizedDoc.croppedPath,
121
+ });
122
+ }
123
+ else {
124
+ // Manual capture or gallery: Open cropper
125
+ console.log('[FullDocScanner] Manual capture: opening cropper');
126
+ await openCropper(normalizedDoc.path);
127
+ }
113
128
  }, [openCropper]);
114
129
  const triggerManualCapture = (0, react_1.useCallback)(() => {
115
130
  console.log('[FullDocScanner] triggerManualCapture called');
@@ -141,15 +156,17 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
141
156
  }, [processing]);
142
157
  const handleGalleryPick = (0, react_1.useCallback)(async () => {
143
158
  console.log('[FullDocScanner] handleGalleryPick called');
144
- if (processing) {
159
+ if (processing || isGalleryOpen) {
145
160
  return;
146
161
  }
147
162
  try {
163
+ setIsGalleryOpen(true);
148
164
  const result = await (0, react_native_image_picker_1.launchImageLibrary)({
149
165
  mediaType: 'photo',
150
166
  quality: 1,
151
167
  selectionLimit: 1,
152
168
  });
169
+ setIsGalleryOpen(false);
153
170
  if (result.didCancel || !result.assets?.[0]?.uri) {
154
171
  console.log('[FullDocScanner] User cancelled gallery picker');
155
172
  return;
@@ -160,15 +177,35 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
160
177
  await openCropper(imageUri);
161
178
  }
162
179
  catch (error) {
180
+ setIsGalleryOpen(false);
163
181
  emitError(error instanceof Error ? error : new Error(String(error)), 'Failed to pick image from gallery.');
164
182
  }
165
- }, [processing, openCropper, emitError]);
183
+ }, [processing, isGalleryOpen, openCropper, emitError]);
166
184
  const handleClose = (0, react_1.useCallback)(() => {
167
185
  onClose?.();
168
186
  }, [onClose]);
187
+ const handleConfirm = (0, react_1.useCallback)(() => {
188
+ if (croppedImageData) {
189
+ onResult({
190
+ path: croppedImageData.path,
191
+ base64: croppedImageData.base64,
192
+ });
193
+ }
194
+ }, [croppedImageData, onResult]);
195
+ const handleRetake = (0, react_1.useCallback)(() => {
196
+ setCroppedImageData(null);
197
+ }, []);
169
198
  return (react_1.default.createElement(react_native_1.View, { style: styles.container },
170
- react_1.default.createElement(react_native_1.View, { style: styles.flex },
171
- react_1.default.createElement(DocScanner_1.DocScanner, { ref: docScannerRef, autoCapture: !manualCapture, overlayColor: overlayColor, showGrid: showGrid, gridColor: resolvedGridColor, gridLineWidth: gridLineWidth, minStableFrames: minStableFrames ?? 6, detectionConfig: detectionConfig, onCapture: handleCapture, showManualCaptureButton: false },
199
+ croppedImageData ? (
200
+ // check_DP: Show confirmation screen
201
+ react_1.default.createElement(react_native_1.View, { style: styles.confirmationContainer },
202
+ react_1.default.createElement(react_native_1.Image, { source: { uri: croppedImageData.path }, style: styles.previewImage, resizeMode: "contain" }),
203
+ react_1.default.createElement(react_native_1.View, { style: styles.confirmationButtons },
204
+ react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.retakeButton], onPress: handleRetake, accessibilityLabel: mergedStrings.retake, accessibilityRole: "button" },
205
+ react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.retake)),
206
+ react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.confirmButtonPrimary], onPress: handleConfirm, accessibilityLabel: mergedStrings.confirm, accessibilityRole: "button" },
207
+ react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.confirm))))) : (react_1.default.createElement(react_native_1.View, { style: styles.flex },
208
+ 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
209
  react_1.default.createElement(react_native_1.View, { style: styles.overlayTop, pointerEvents: "box-none" },
173
210
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.closeButton, onPress: handleClose, accessibilityLabel: mergedStrings.cancel, accessibilityRole: "button" },
174
211
  react_1.default.createElement(react_native_1.Text, { style: styles.closeButtonLabel }, "\u00D7"))),
@@ -180,7 +217,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
180
217
  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
218
  react_1.default.createElement(react_native_1.Text, { style: styles.galleryButtonText }, "\uD83D\uDCC1"))),
182
219
  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 }))))),
220
+ react_1.default.createElement(react_native_1.View, { style: styles.shutterInner })))))),
184
221
  processing && (react_1.default.createElement(react_native_1.View, { style: styles.processingOverlay },
185
222
  react_1.default.createElement(react_native_1.ActivityIndicator, { size: "large", color: overlayColor }),
186
223
  mergedStrings.processing && (react_1.default.createElement(react_native_1.Text, { style: styles.processingText }, mergedStrings.processing))))));
@@ -288,4 +325,42 @@ const styles = react_native_1.StyleSheet.create({
288
325
  fontSize: 16,
289
326
  fontWeight: '600',
290
327
  },
328
+ confirmationContainer: {
329
+ flex: 1,
330
+ backgroundColor: '#000',
331
+ justifyContent: 'center',
332
+ alignItems: 'center',
333
+ },
334
+ previewImage: {
335
+ width: '100%',
336
+ height: '80%',
337
+ },
338
+ confirmationButtons: {
339
+ flexDirection: 'row',
340
+ justifyContent: 'center',
341
+ alignItems: 'center',
342
+ gap: 24,
343
+ paddingVertical: 32,
344
+ },
345
+ confirmButton: {
346
+ paddingHorizontal: 40,
347
+ paddingVertical: 16,
348
+ borderRadius: 12,
349
+ minWidth: 140,
350
+ alignItems: 'center',
351
+ justifyContent: 'center',
352
+ },
353
+ retakeButton: {
354
+ backgroundColor: 'rgba(255,255,255,0.2)',
355
+ borderWidth: 2,
356
+ borderColor: '#fff',
357
+ },
358
+ confirmButtonPrimary: {
359
+ backgroundColor: '#3170f3',
360
+ },
361
+ confirmButtonText: {
362
+ color: '#fff',
363
+ fontSize: 18,
364
+ fontWeight: '600',
365
+ },
291
366
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.44.0",
3
+ "version": "3.44.2",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -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
- onResult({
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, onResult],
146
+ [cropWidth, cropHeight, emitError],
139
147
  );
140
148
 
141
149
  const handleCapture = useCallback(
@@ -151,8 +159,17 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
151
159
 
152
160
  const normalizedDoc = normalizeCapturedDocument(document);
153
161
 
154
- // Open cropper with the captured image
155
- await openCropper(normalizedDoc.path);
162
+ // Auto-capture: Use already cropped image, skip cropper
163
+ if (document.origin === 'auto' && normalizedDoc.croppedPath) {
164
+ console.log('[FullDocScanner] Auto-capture: using pre-cropped image');
165
+ setCroppedImageData({
166
+ path: normalizedDoc.croppedPath,
167
+ });
168
+ } else {
169
+ // Manual capture or gallery: Open cropper
170
+ console.log('[FullDocScanner] Manual capture: opening cropper');
171
+ await openCropper(normalizedDoc.path);
172
+ }
156
173
  },
157
174
  [openCropper],
158
175
  );
@@ -189,17 +206,20 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
189
206
 
190
207
  const handleGalleryPick = useCallback(async () => {
191
208
  console.log('[FullDocScanner] handleGalleryPick called');
192
- if (processing) {
209
+ if (processing || isGalleryOpen) {
193
210
  return;
194
211
  }
195
212
 
196
213
  try {
214
+ setIsGalleryOpen(true);
197
215
  const result = await launchImageLibrary({
198
216
  mediaType: 'photo',
199
217
  quality: 1,
200
218
  selectionLimit: 1,
201
219
  });
202
220
 
221
+ setIsGalleryOpen(false);
222
+
203
223
  if (result.didCancel || !result.assets?.[0]?.uri) {
204
224
  console.log('[FullDocScanner] User cancelled gallery picker');
205
225
  return;
@@ -211,32 +231,74 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
211
231
  // Open cropper with the selected image
212
232
  await openCropper(imageUri);
213
233
  } catch (error) {
234
+ setIsGalleryOpen(false);
214
235
  emitError(
215
236
  error instanceof Error ? error : new Error(String(error)),
216
237
  'Failed to pick image from gallery.',
217
238
  );
218
239
  }
219
- }, [processing, openCropper, emitError]);
240
+ }, [processing, isGalleryOpen, openCropper, emitError]);
220
241
 
221
242
  const handleClose = useCallback(() => {
222
243
  onClose?.();
223
244
  }, [onClose]);
224
245
 
246
+ const handleConfirm = useCallback(() => {
247
+ if (croppedImageData) {
248
+ onResult({
249
+ path: croppedImageData.path,
250
+ base64: croppedImageData.base64,
251
+ });
252
+ }
253
+ }, [croppedImageData, onResult]);
254
+
255
+ const handleRetake = useCallback(() => {
256
+ setCroppedImageData(null);
257
+ }, []);
258
+
225
259
  return (
226
260
  <View style={styles.container}>
227
- <View style={styles.flex}>
228
- <DocScanner
229
- ref={docScannerRef}
230
- autoCapture={!manualCapture}
231
- overlayColor={overlayColor}
232
- showGrid={showGrid}
233
- gridColor={resolvedGridColor}
234
- gridLineWidth={gridLineWidth}
235
- minStableFrames={minStableFrames ?? 6}
236
- detectionConfig={detectionConfig}
237
- onCapture={handleCapture}
238
- showManualCaptureButton={false}
239
- >
261
+ {croppedImageData ? (
262
+ // check_DP: Show confirmation screen
263
+ <View style={styles.confirmationContainer}>
264
+ <Image
265
+ source={{ uri: croppedImageData.path }}
266
+ style={styles.previewImage}
267
+ resizeMode="contain"
268
+ />
269
+ <View style={styles.confirmationButtons}>
270
+ <TouchableOpacity
271
+ style={[styles.confirmButton, styles.retakeButton]}
272
+ onPress={handleRetake}
273
+ accessibilityLabel={mergedStrings.retake}
274
+ accessibilityRole="button"
275
+ >
276
+ <Text style={styles.confirmButtonText}>{mergedStrings.retake}</Text>
277
+ </TouchableOpacity>
278
+ <TouchableOpacity
279
+ style={[styles.confirmButton, styles.confirmButtonPrimary]}
280
+ onPress={handleConfirm}
281
+ accessibilityLabel={mergedStrings.confirm}
282
+ accessibilityRole="button"
283
+ >
284
+ <Text style={styles.confirmButtonText}>{mergedStrings.confirm}</Text>
285
+ </TouchableOpacity>
286
+ </View>
287
+ </View>
288
+ ) : (
289
+ <View style={styles.flex}>
290
+ <DocScanner
291
+ ref={docScannerRef}
292
+ autoCapture={!manualCapture && !isGalleryOpen}
293
+ overlayColor={overlayColor}
294
+ showGrid={showGrid}
295
+ gridColor={resolvedGridColor}
296
+ gridLineWidth={gridLineWidth}
297
+ minStableFrames={minStableFrames ?? 6}
298
+ detectionConfig={detectionConfig}
299
+ onCapture={handleCapture}
300
+ showManualCaptureButton={false}
301
+ >
240
302
  <View style={styles.overlayTop} pointerEvents="box-none">
241
303
  <TouchableOpacity
242
304
  style={styles.closeButton}
@@ -282,7 +344,8 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
282
344
  </TouchableOpacity>
283
345
  </View>
284
346
  </DocScanner>
285
- </View>
347
+ </View>
348
+ )}
286
349
 
287
350
  {processing && (
288
351
  <View style={styles.processingOverlay}>
@@ -398,4 +461,42 @@ const styles = StyleSheet.create({
398
461
  fontSize: 16,
399
462
  fontWeight: '600',
400
463
  },
464
+ confirmationContainer: {
465
+ flex: 1,
466
+ backgroundColor: '#000',
467
+ justifyContent: 'center',
468
+ alignItems: 'center',
469
+ },
470
+ previewImage: {
471
+ width: '100%',
472
+ height: '80%',
473
+ },
474
+ confirmationButtons: {
475
+ flexDirection: 'row',
476
+ justifyContent: 'center',
477
+ alignItems: 'center',
478
+ gap: 24,
479
+ paddingVertical: 32,
480
+ },
481
+ confirmButton: {
482
+ paddingHorizontal: 40,
483
+ paddingVertical: 16,
484
+ borderRadius: 12,
485
+ minWidth: 140,
486
+ alignItems: 'center',
487
+ justifyContent: 'center',
488
+ },
489
+ retakeButton: {
490
+ backgroundColor: 'rgba(255,255,255,0.2)',
491
+ borderWidth: 2,
492
+ borderColor: '#fff',
493
+ },
494
+ confirmButtonPrimary: {
495
+ backgroundColor: '#3170f3',
496
+ },
497
+ confirmButtonText: {
498
+ color: '#fff',
499
+ fontSize: 18,
500
+ fontWeight: '600',
501
+ },
401
502
  });