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.
@@ -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,
@@ -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
- 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 },
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.44.0",
3
+ "version": "3.44.1",
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(
@@ -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
- <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
- >
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
- </View>
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
  });