Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,30 @@ Opens the camera, and starts the document scan
| ----------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
| **`croppedImageQuality`** | <code>number</code> | The quality of the cropped image from 0 - 100. 100 is the best quality. | <code>: 100</code> |
| **`maxNumDocuments`** | <code>number</code> | Android only: The maximum number of photos an user can take (not counting photo retakes) | <code>: undefined</code> |
| **`scannerMode`** | <code><a href="#scannermode">ScannerMode</a></code> | Android only: The scanner feature set to enable. `ScannerMode.BASE` provides basic scanning and editing features, `ScannerMode.BASE_WITH_FILTER` adds image filters, and `ScannerMode.FULL` adds ML-enabled image cleaning. | <code>: ScannerMode.FULL</code> |
| **`responseType`** | <code><a href="#responsetype">ResponseType</a></code> | The response comes back in this format on success. It can be the document scan image file paths or base64 images. | <code>: ResponseType.ImageFilePath</code> |


### Enums

#### ScannerMode

Android-only scanner modes. `ScannerMode.FULL` remains the default for backwards compatibility.

| Member | Value | Description |
| --- | --- | --- |
| **`BASE`** | <code>'base'</code> | Basic scanning features such as capture, auto rotation, crop, and page reordering. |
| **`BASE_WITH_FILTER`** | <code>'base_with_filter'</code> | Basic scanning features plus image filters. |
| **`FULL`** | <code>'full'</code> | The full feature set, including ML-enabled image cleaning. |

```tsx
import DocumentScanner, { ScannerMode } from 'react-native-document-scanner-plugin'

const { scannedImages } = await DocumentScanner.scanDocument({
scannerMode: ScannerMode.BASE,
})
```


#### ScanDocumentResponseStatus

Expand Down Expand Up @@ -291,4 +310,4 @@ export default () => {
)
}
```
<!-- {% endraw %} -->
<!-- {% endraw %} -->
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class DocumentScannerModule(reactContext: ReactApplicationContext) :

val documentScannerOptionsBuilder = GmsDocumentScannerOptions.Builder()
.setResultFormats(GmsDocumentScannerOptions.RESULT_FORMAT_JPEG)
.setScannerMode(GmsDocumentScannerOptions.SCANNER_MODE_FULL)
.setScannerMode(getScannerMode(options))

if (options.hasKey("maxNumDocuments")) {
documentScannerOptionsBuilder.setPageLimit(
Expand Down Expand Up @@ -132,6 +132,23 @@ class DocumentScannerModule(reactContext: ReactApplicationContext) :
})
}

private fun getScannerMode(options: ReadableMap): Int {
return try {
if (!options.hasKey("scannerMode") || options.isNull("scannerMode")) {
return GmsDocumentScannerOptions.SCANNER_MODE_FULL
}

when (options.getString("scannerMode")) {
"base" -> GmsDocumentScannerOptions.SCANNER_MODE_BASE
"base_with_filter" -> GmsDocumentScannerOptions.SCANNER_MODE_BASE_WITH_FILTER
"full" -> GmsDocumentScannerOptions.SCANNER_MODE_FULL
else -> GmsDocumentScannerOptions.SCANNER_MODE_FULL
}
} catch (_: RuntimeException) {
GmsDocumentScannerOptions.SCANNER_MODE_FULL
}
}

companion object {
const val NAME = "DocumentScanner"
}
Expand Down
140 changes: 117 additions & 23 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,127 @@
import { useState, useEffect } from 'react';
import { Image } from 'react-native';
import DocumentScanner from 'react-native-document-scanner-plugin';
import { useState } from 'react';
import {
Image,
Platform,
Pressable,
StyleSheet,
Text,
View,
} from 'react-native';
import DocumentScanner, {
ScannerMode,
} from 'react-native-document-scanner-plugin';

const scannerModes = [
{ mode: ScannerMode.BASE, label: 'Base' },
{ mode: ScannerMode.BASE_WITH_FILTER, label: 'Base with filter' },
{ mode: ScannerMode.FULL, label: 'Full' },
];

export default () => {
const [scannedImage, setScannedImage] = useState<any>();
const [scannedImage, setScannedImage] = useState<string>();
const [scannerMode, setScannerMode] = useState(ScannerMode.FULL);

const scanDocument = async () => {
// start the document scanner
const { scannedImages } = await DocumentScanner.scanDocument();

// check if undefined
if (scannedImages) {
// get back an array with scanned image file paths
if (scannedImages.length > 0) {
// set the img src, so we can view the first scanned image
setScannedImage(scannedImages[0]);
}
const options = Platform.OS === 'android' ? { scannerMode } : undefined;
const { scannedImages } = await DocumentScanner.scanDocument(options);

if (scannedImages && scannedImages.length > 0) {
setScannedImage(scannedImages[0]);
}
};

useEffect(() => {
// call scanDocument on load
scanDocument();
}, []);

return (
<Image
style={{ width: '100%', height: '100%' }}
source={{ uri: scannedImage }}
/>
<View style={styles.container}>
{Platform.OS === 'android' && (
<View style={styles.controls}>
<Text style={styles.title}>Scanner mode</Text>
<View style={styles.modeSelector}>
{scannerModes.map(({ mode, label }) => (
<Pressable
key={mode}
onPress={() => setScannerMode(mode)}
style={[
styles.modeButton,
scannerMode === mode && styles.selectedModeButton,
]}
>
<Text
style={[
styles.modeButtonText,
scannerMode === mode && styles.selectedModeButtonText,
]}
>
{label}
</Text>
</Pressable>
))}
</View>
</View>
)}

<Pressable onPress={scanDocument} style={styles.scanButton}>
<Text style={styles.scanButtonText}>Scan document</Text>
</Pressable>

{scannedImage && (
<Image
resizeMode="contain"
style={styles.image}
source={{ uri: scannedImage }}
/>
)}
</View>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
padding: 24,
backgroundColor: '#fff',
},
controls: {
marginTop: 24,
},
title: {
marginBottom: 8,
fontSize: 18,
fontWeight: '600',
},
modeSelector: {
gap: 8,
},
modeButton: {
padding: 12,
borderWidth: 1,
borderColor: '#777',
borderRadius: 6,
},
selectedModeButton: {
backgroundColor: '#222',
borderColor: '#222',
},
modeButtonText: {
textAlign: 'center',
color: '#222',
},
selectedModeButtonText: {
color: '#fff',
},
scanButton: {
marginTop: 16,
padding: 14,
borderRadius: 6,
backgroundColor: '#1976d2',
},
scanButtonText: {
textAlign: 'center',
color: '#fff',
fontWeight: '600',
},
image: {
flex: 1,
width: '100%',
marginTop: 24,
},
});
25 changes: 25 additions & 0 deletions src/NativeDocumentScanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@ export interface Spec extends TurboModule {
scanDocument(options: ScanDocumentOptions): Promise<ScanDocumentResponse>;
}

export enum ScannerMode {
/**
* Basic scanning features such as capture, auto rotation, crop, and page
* reordering. Android only.
*/
BASE = 'base',

/**
* Basic scanning features plus image filters. Android only.
*/
BASE_WITH_FILTER = 'base_with_filter',

/**
* The full scanner feature set, including ML-enabled image cleaning.
* Android only and the default mode.
*/
FULL = 'full',
}

export interface ScanDocumentOptions {
/**
* The quality of the cropped image from 0 - 100. 100 is the best quality.
Expand All @@ -17,6 +36,12 @@ export interface ScanDocumentOptions {
*/
maxNumDocuments?: number;

/**
* Android only: The scanner feature set to enable.
* @default: ScannerMode.FULL
*/
scannerMode?: ScannerMode;

/**
* The response comes back in this format on success. It can be the document
* scan image file paths or base64 images.
Expand Down
25 changes: 24 additions & 1 deletion src/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,24 @@
it.todo('write a test');
jest.mock('react-native', () => ({
TurboModuleRegistry: {
getEnforcing: jest.fn(),
},
}));

import {
ResponseType,
ScannerMode,
ScanDocumentResponseStatus,
} from '../index';

describe('public API constants', () => {
it('exposes the supported scanner modes', () => {
expect(ScannerMode.BASE).toBe('base');
expect(ScannerMode.BASE_WITH_FILTER).toBe('base_with_filter');
expect(ScannerMode.FULL).toBe('full');
});

it('preserves existing exported constants', () => {
expect(ResponseType.ImageFilePath).toBe('imageFilePath');
expect(ScanDocumentResponseStatus.Success).toBe('success');
});
});
1 change: 1 addition & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type {

export {
ResponseType,
ScannerMode,
ScanDocumentResponseStatus,
} from './NativeDocumentScanner';

Expand Down