@capgo/capacitor-document-scanner 7.0.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/CapgoCapacitorDocumentScanner.podspec +17 -0
- package/Package.swift +28 -0
- package/README.md +86 -0
- package/android/build.gradle +59 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/app/capgo/plugin/document_scanner/DocumentScanner.java +11 -0
- package/android/src/main/java/app/capgo/plugin/document_scanner/DocumentScannerPlugin.java +22 -0
- package/android/src/main/java/app/capgo/plugin/documentscanner/DocumentScannerPlugin.java +292 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +156 -0
- package/dist/esm/definitions.d.ts +59 -0
- package/dist/esm/definitions.js +23 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +5 -0
- package/dist/esm/web.js +7 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +44 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +47 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/DocumentScannerPlugin/DocScanner.swift +126 -0
- package/ios/Sources/DocumentScannerPlugin/DocumentScannerPlugin.swift +51 -0
- package/ios/Sources/DocumentScannerPlugin/Errors.swift +6 -0
- package/ios/Sources/DocumentScannerPlugin/FileUtil.swift +42 -0
- package/ios/Sources/DocumentScannerPlugin/ResponseType.swift +7 -0
- package/ios/Tests/DocumentScannerPluginTests/DocumentScannerPluginTests.swift +9 -0
- package/package.json +80 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = 'CapgoCapacitorDocumentScanner'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.license = package['license']
|
|
10
|
+
s.homepage = package['repository']['url']
|
|
11
|
+
s.author = package['author']
|
|
12
|
+
s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
|
|
13
|
+
s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
|
|
14
|
+
s.ios.deployment_target = '14.0'
|
|
15
|
+
s.dependency 'Capacitor'
|
|
16
|
+
s.swift_version = '5.1'
|
|
17
|
+
end
|
package/Package.swift
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// swift-tools-version: 5.9
|
|
2
|
+
import PackageDescription
|
|
3
|
+
|
|
4
|
+
let package = Package(
|
|
5
|
+
name: "CapgoCapacitorDocumentScanner",
|
|
6
|
+
platforms: [.iOS(.v14)],
|
|
7
|
+
products: [
|
|
8
|
+
.library(
|
|
9
|
+
name: "CapgoCapacitorDocumentScanner",
|
|
10
|
+
targets: ["DocumentScannerPlugin"])
|
|
11
|
+
],
|
|
12
|
+
dependencies: [
|
|
13
|
+
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.0.0")
|
|
14
|
+
],
|
|
15
|
+
targets: [
|
|
16
|
+
.target(
|
|
17
|
+
name: "DocumentScannerPlugin",
|
|
18
|
+
dependencies: [
|
|
19
|
+
.product(name: "Capacitor", package: "capacitor-swift-pm"),
|
|
20
|
+
.product(name: "Cordova", package: "capacitor-swift-pm")
|
|
21
|
+
],
|
|
22
|
+
path: "ios/Sources/DocumentScannerPlugin"),
|
|
23
|
+
.testTarget(
|
|
24
|
+
name: "DocumentScannerPluginTests",
|
|
25
|
+
dependencies: ["DocumentScannerPlugin"],
|
|
26
|
+
path: "ios/Tests/DocumentScannerPluginTests")
|
|
27
|
+
]
|
|
28
|
+
)
|
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# @capgo/capacitor-document-scanner
|
|
2
|
+
<a href="https://capgo.app/"><img src='https://raw.githubusercontent.com/Cap-go/capgo/main/assets/capgo_banner.png' alt='Capgo - Instant updates for capacitor'/></a>
|
|
3
|
+
|
|
4
|
+
<div align="center">
|
|
5
|
+
<h2><a href="https://capgo.app/?ref=plugin"> ➡️ Get Instant updates for your App with Capgo</a></h2>
|
|
6
|
+
<h2><a href="https://capgo.app/consulting/?ref=plugin"> Missing a feature? We’ll build the plugin for you 💪</a></h2>
|
|
7
|
+
</div>
|
|
8
|
+
Capacitor plugin to scan document iOS and Android
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @capgo/capacitor-document-scanner
|
|
14
|
+
npx cap sync
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## API
|
|
18
|
+
|
|
19
|
+
<docgen-index>
|
|
20
|
+
|
|
21
|
+
* [`scanDocument(...)`](#scandocument)
|
|
22
|
+
* [Interfaces](#interfaces)
|
|
23
|
+
* [Enums](#enums)
|
|
24
|
+
|
|
25
|
+
</docgen-index>
|
|
26
|
+
|
|
27
|
+
<docgen-api>
|
|
28
|
+
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
|
|
29
|
+
|
|
30
|
+
### scanDocument(...)
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
scanDocument(options?: ScanDocumentOptions | undefined) => Promise<ScanDocumentResponse>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Opens the device camera and starts the document scanning experience.
|
|
37
|
+
|
|
38
|
+
| Param | Type |
|
|
39
|
+
| ------------- | ------------------------------------------------------------------- |
|
|
40
|
+
| **`options`** | <code><a href="#scandocumentoptions">ScanDocumentOptions</a></code> |
|
|
41
|
+
|
|
42
|
+
**Returns:** <code>Promise<<a href="#scandocumentresponse">ScanDocumentResponse</a>></code>
|
|
43
|
+
|
|
44
|
+
--------------------
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
### Interfaces
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
#### ScanDocumentResponse
|
|
51
|
+
|
|
52
|
+
| Prop | Type | Description |
|
|
53
|
+
| ------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------ |
|
|
54
|
+
| **`scannedImages`** | <code>string[]</code> | Scanned images in the requested response format. |
|
|
55
|
+
| **`status`** | <code><a href="#scandocumentresponsestatus">ScanDocumentResponseStatus</a></code> | Indicates whether the scan completed or was cancelled. |
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
#### ScanDocumentOptions
|
|
59
|
+
|
|
60
|
+
| Prop | Type | Description | Default |
|
|
61
|
+
| ------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
|
|
62
|
+
| **`croppedImageQuality`** | <code>number</code> | Android only: quality of the cropped image from 0 - 100 (100 is best). | <code>100</code> |
|
|
63
|
+
| **`letUserAdjustCrop`** | <code>boolean</code> | Android only: allow the user to adjust the detected crop before saving. Disabling this forces single-document capture. | <code>true</code> |
|
|
64
|
+
| **`maxNumDocuments`** | <code>number</code> | Android only: maximum number of documents the user can scan. | <code>24</code> |
|
|
65
|
+
| **`responseType`** | <code><a href="#responsetype">ResponseType</a></code> | Format to return scanned images in (file paths or base64 strings). | <code>ResponseType.ImageFilePath</code> |
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
### Enums
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
#### ScanDocumentResponseStatus
|
|
72
|
+
|
|
73
|
+
| Members | Value | Description |
|
|
74
|
+
| ------------- | ---------------------- | --------------------------------- |
|
|
75
|
+
| **`Success`** | <code>'success'</code> | The scan completed successfully. |
|
|
76
|
+
| **`Cancel`** | <code>'cancel'</code> | The user cancelled the scan flow. |
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
#### ResponseType
|
|
80
|
+
|
|
81
|
+
| Members | Value | Description |
|
|
82
|
+
| ------------------- | ---------------------------- | ------------------------------------------------ |
|
|
83
|
+
| **`Base64`** | <code>'base64'</code> | Return scanned images as base64-encoded strings. |
|
|
84
|
+
| **`ImageFilePath`** | <code>'imageFilePath'</code> | Return scanned images as file paths on disk. |
|
|
85
|
+
|
|
86
|
+
</docgen-api>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
ext {
|
|
2
|
+
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
|
|
3
|
+
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0'
|
|
4
|
+
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1'
|
|
5
|
+
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
buildscript {
|
|
9
|
+
repositories {
|
|
10
|
+
google()
|
|
11
|
+
mavenCentral()
|
|
12
|
+
}
|
|
13
|
+
dependencies {
|
|
14
|
+
classpath 'com.android.tools.build:gradle:8.7.2'
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
apply plugin: 'com.android.library'
|
|
19
|
+
|
|
20
|
+
android {
|
|
21
|
+
namespace "app.capgo.plugin.documentscanner"
|
|
22
|
+
compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35
|
|
23
|
+
defaultConfig {
|
|
24
|
+
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23
|
|
25
|
+
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35
|
|
26
|
+
versionCode 1
|
|
27
|
+
versionName "1.0"
|
|
28
|
+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
29
|
+
}
|
|
30
|
+
buildTypes {
|
|
31
|
+
release {
|
|
32
|
+
minifyEnabled false
|
|
33
|
+
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
lintOptions {
|
|
37
|
+
abortOnError false
|
|
38
|
+
}
|
|
39
|
+
compileOptions {
|
|
40
|
+
sourceCompatibility JavaVersion.VERSION_21
|
|
41
|
+
targetCompatibility JavaVersion.VERSION_21
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
repositories {
|
|
46
|
+
google()
|
|
47
|
+
mavenCentral()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
dependencies {
|
|
52
|
+
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
53
|
+
implementation project(':capacitor-android')
|
|
54
|
+
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
55
|
+
implementation "com.google.android.gms:play-services-mlkit-document-scanner:16.0.0"
|
|
56
|
+
testImplementation "junit:junit:$junitVersion"
|
|
57
|
+
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
58
|
+
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
59
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
package app.capgo.plugin.document_scanner;
|
|
2
|
+
|
|
3
|
+
import com.getcapacitor.JSObject;
|
|
4
|
+
import com.getcapacitor.Plugin;
|
|
5
|
+
import com.getcapacitor.PluginCall;
|
|
6
|
+
import com.getcapacitor.PluginMethod;
|
|
7
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
8
|
+
|
|
9
|
+
@CapacitorPlugin(name = "DocumentScanner")
|
|
10
|
+
public class DocumentScannerPlugin extends Plugin {
|
|
11
|
+
|
|
12
|
+
private DocumentScanner implementation = new DocumentScanner();
|
|
13
|
+
|
|
14
|
+
@PluginMethod
|
|
15
|
+
public void echo(PluginCall call) {
|
|
16
|
+
String value = call.getString("value");
|
|
17
|
+
|
|
18
|
+
JSObject ret = new JSObject();
|
|
19
|
+
ret.put("value", implementation.echo(value));
|
|
20
|
+
call.resolve(ret);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
package app.capgo.plugin.documentscanner;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
import android.content.Context;
|
|
5
|
+
import android.content.Intent;
|
|
6
|
+
import android.graphics.Bitmap;
|
|
7
|
+
import android.graphics.BitmapFactory;
|
|
8
|
+
import android.net.Uri;
|
|
9
|
+
import android.util.Base64;
|
|
10
|
+
import androidx.activity.result.ActivityResult;
|
|
11
|
+
import androidx.activity.result.ActivityResultLauncher;
|
|
12
|
+
import androidx.activity.result.IntentSenderRequest;
|
|
13
|
+
import androidx.activity.result.contract.ActivityResultContracts;
|
|
14
|
+
import com.getcapacitor.JSArray;
|
|
15
|
+
import com.getcapacitor.JSObject;
|
|
16
|
+
import com.getcapacitor.Logger;
|
|
17
|
+
import com.getcapacitor.Plugin;
|
|
18
|
+
import com.getcapacitor.PluginCall;
|
|
19
|
+
import com.getcapacitor.PluginMethod;
|
|
20
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
21
|
+
import com.google.mlkit.vision.documentscanner.GmsDocumentScanner;
|
|
22
|
+
import com.google.mlkit.vision.documentscanner.GmsDocumentScannerOptions;
|
|
23
|
+
import com.google.mlkit.vision.documentscanner.GmsDocumentScanning;
|
|
24
|
+
import com.google.mlkit.vision.documentscanner.GmsDocumentScanningResult;
|
|
25
|
+
import com.google.mlkit.vision.documentscanner.GmsDocumentScanningResult.Page;
|
|
26
|
+
import java.io.ByteArrayOutputStream;
|
|
27
|
+
import java.io.File;
|
|
28
|
+
import java.io.FileOutputStream;
|
|
29
|
+
import java.io.IOException;
|
|
30
|
+
import java.io.InputStream;
|
|
31
|
+
import java.util.ArrayList;
|
|
32
|
+
import java.util.List;
|
|
33
|
+
import java.util.Locale;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Bridges Capacitor calls to the ML Kit document scanner.
|
|
37
|
+
*/
|
|
38
|
+
@CapacitorPlugin(name = "DocumentScanner")
|
|
39
|
+
public class DocumentScannerPlugin extends Plugin {
|
|
40
|
+
|
|
41
|
+
private static final String RESPONSE_TYPE_BASE64 = "base64";
|
|
42
|
+
private static final String RESPONSE_TYPE_FILE_PATH = "imageFilePath";
|
|
43
|
+
|
|
44
|
+
private ActivityResultLauncher<IntentSenderRequest> scannerLauncher;
|
|
45
|
+
private PendingScan pendingScan;
|
|
46
|
+
|
|
47
|
+
private static class PendingScan {
|
|
48
|
+
|
|
49
|
+
private final String callId;
|
|
50
|
+
private final String responseType;
|
|
51
|
+
private final int quality;
|
|
52
|
+
|
|
53
|
+
PendingScan(String callId, String responseType, int quality) {
|
|
54
|
+
this.callId = callId;
|
|
55
|
+
this.responseType = responseType;
|
|
56
|
+
this.quality = quality;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@Override
|
|
61
|
+
public void load() {
|
|
62
|
+
super.load();
|
|
63
|
+
scannerLauncher = bridge.registerForActivityResult(
|
|
64
|
+
new ActivityResultContracts.StartIntentSenderForResult(),
|
|
65
|
+
this::handleScanResult
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@PluginMethod
|
|
70
|
+
public void scanDocument(PluginCall call) {
|
|
71
|
+
if (scannerLauncher == null) {
|
|
72
|
+
call.reject("Document scanner is not ready.");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (pendingScan != null) {
|
|
77
|
+
call.reject("Another scan is in progress.");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
Activity activity = getActivity();
|
|
82
|
+
if (activity == null) {
|
|
83
|
+
call.reject("Activity reference is unavailable.");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
int quality = clamp(call.getInt("croppedImageQuality", 100), 0, 100);
|
|
88
|
+
String responseType = normalizeResponseType(call.getString("responseType"));
|
|
89
|
+
int pageLimit = clamp(call.getInt("maxNumDocuments", 24), 1, 24);
|
|
90
|
+
boolean allowAdjustCrop = call.getBoolean("letUserAdjustCrop", true);
|
|
91
|
+
|
|
92
|
+
GmsDocumentScannerOptions.Builder optionsBuilder = new GmsDocumentScannerOptions.Builder()
|
|
93
|
+
.setGalleryImportAllowed(false)
|
|
94
|
+
.setResultFormats(GmsDocumentScannerOptions.RESULT_FORMAT_JPEG)
|
|
95
|
+
.setPageLimit(pageLimit);
|
|
96
|
+
|
|
97
|
+
optionsBuilder.setScannerMode(
|
|
98
|
+
allowAdjustCrop ? GmsDocumentScannerOptions.SCANNER_MODE_FULL : GmsDocumentScannerOptions.SCANNER_MODE_BASE
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
GmsDocumentScanner scanner = GmsDocumentScanning.getClient(optionsBuilder.build());
|
|
102
|
+
|
|
103
|
+
bridge.saveCall(call);
|
|
104
|
+
pendingScan = new PendingScan(call.getCallbackId(), responseType, quality);
|
|
105
|
+
|
|
106
|
+
scanner
|
|
107
|
+
.getStartScanIntent(activity)
|
|
108
|
+
.addOnSuccessListener((intentSender) -> {
|
|
109
|
+
IntentSenderRequest request = new IntentSenderRequest.Builder(intentSender).build();
|
|
110
|
+
scannerLauncher.launch(request);
|
|
111
|
+
})
|
|
112
|
+
.addOnFailureListener((e) -> {
|
|
113
|
+
Logger.error("DocumentScanner", "Failed to start scanner", e);
|
|
114
|
+
PluginCall savedCall = getPendingCall();
|
|
115
|
+
if (savedCall != null) {
|
|
116
|
+
savedCall.reject("Unable to start document scanner: " + e.getLocalizedMessage(), e);
|
|
117
|
+
releasePendingCall(savedCall);
|
|
118
|
+
} else {
|
|
119
|
+
bridge.releaseCall(call);
|
|
120
|
+
pendingScan = null;
|
|
121
|
+
call.reject("Unable to start document scanner: " + e.getLocalizedMessage(), e);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private void handleScanResult(ActivityResult result) {
|
|
127
|
+
PluginCall call = getPendingCall();
|
|
128
|
+
if (call == null) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (result.getResultCode() != Activity.RESULT_OK) {
|
|
133
|
+
JSObject response = new JSObject();
|
|
134
|
+
response.put("status", "cancel");
|
|
135
|
+
call.resolve(response);
|
|
136
|
+
releasePendingCall(call);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
Intent data = result.getData();
|
|
141
|
+
if (data == null) {
|
|
142
|
+
call.reject("Document scanner returned no data.");
|
|
143
|
+
releasePendingCall(call);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
GmsDocumentScanningResult scanningResult = GmsDocumentScanningResult.fromActivityResultIntent(data);
|
|
148
|
+
if (scanningResult == null) {
|
|
149
|
+
call.reject("Unable to parse document scan result.");
|
|
150
|
+
releasePendingCall(call);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
List<String> scannedImages = processScanResult(scanningResult);
|
|
156
|
+
JSObject response = new JSObject();
|
|
157
|
+
response.put("status", "success");
|
|
158
|
+
response.put("scannedImages", new JSArray(scannedImages));
|
|
159
|
+
call.resolve(response);
|
|
160
|
+
} catch (IOException ioException) {
|
|
161
|
+
call.reject("Failed to process scanned images: " + ioException.getLocalizedMessage(), ioException);
|
|
162
|
+
} finally {
|
|
163
|
+
releasePendingCall(call);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private List<String> processScanResult(GmsDocumentScanningResult scanningResult) throws IOException {
|
|
168
|
+
List<String> results = new ArrayList<>();
|
|
169
|
+
List<Page> pages = scanningResult.getPages();
|
|
170
|
+
if (pages == null || pages.isEmpty()) {
|
|
171
|
+
return results;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
for (int index = 0; index < pages.size(); index++) {
|
|
175
|
+
String processed = handlePage(pages.get(index), index);
|
|
176
|
+
if (processed != null) {
|
|
177
|
+
results.add(processed);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return results;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private String handlePage(Page page, int pageIndex) throws IOException {
|
|
184
|
+
PendingScan scan = pendingScan;
|
|
185
|
+
if (scan == null) {
|
|
186
|
+
throw new IOException("No active scan.");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
Uri imageUri = page.getImageUri();
|
|
190
|
+
if (imageUri == null) {
|
|
191
|
+
throw new IOException("Missing image URI for scanned page.");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
byte[] imageBytes = readBytesFromUri(imageUri);
|
|
195
|
+
if (scan.quality < 100) {
|
|
196
|
+
imageBytes = reencodeImage(imageBytes, scan.quality);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (RESPONSE_TYPE_BASE64.equals(scan.responseType)) {
|
|
200
|
+
return Base64.encodeToString(imageBytes, Base64.NO_WRAP);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return writeImageFile(imageBytes, pageIndex);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private byte[] readBytesFromUri(Uri uri) throws IOException {
|
|
207
|
+
Context context = getContext();
|
|
208
|
+
if (context == null) {
|
|
209
|
+
throw new IOException("Context unavailable for reading image.");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
try (InputStream inputStream = context.getContentResolver().openInputStream(uri)) {
|
|
213
|
+
if (inputStream == null) {
|
|
214
|
+
throw new IOException("Unable to open image stream.");
|
|
215
|
+
}
|
|
216
|
+
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
|
217
|
+
byte[] data = new byte[8192];
|
|
218
|
+
int nRead;
|
|
219
|
+
while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
|
|
220
|
+
buffer.write(data, 0, nRead);
|
|
221
|
+
}
|
|
222
|
+
return buffer.toByteArray();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private byte[] reencodeImage(byte[] source, int quality) throws IOException {
|
|
227
|
+
Bitmap bitmap = BitmapFactory.decodeByteArray(source, 0, source.length);
|
|
228
|
+
if (bitmap == null) {
|
|
229
|
+
throw new IOException("Unable to decode scanned image.");
|
|
230
|
+
}
|
|
231
|
+
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
|
232
|
+
if (!bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream)) {
|
|
233
|
+
throw new IOException("Unable to compress scanned image.");
|
|
234
|
+
}
|
|
235
|
+
return outputStream.toByteArray();
|
|
236
|
+
} finally {
|
|
237
|
+
bitmap.recycle();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private String writeImageFile(byte[] imageBytes, int pageIndex) throws IOException {
|
|
242
|
+
Context context = getContext();
|
|
243
|
+
if (context == null) {
|
|
244
|
+
throw new IOException("Context unavailable for writing image.");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
File directory = new File(context.getCacheDir(), "document_scanner");
|
|
248
|
+
if (!directory.exists() && !directory.mkdirs()) {
|
|
249
|
+
throw new IOException("Unable to create cache directory.");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
String fileName = String.format(Locale.US, "DOCUMENT_SCAN_%d_%d.jpg", pageIndex, System.currentTimeMillis());
|
|
253
|
+
File outputFile = new File(directory, fileName);
|
|
254
|
+
try (FileOutputStream outputStream = new FileOutputStream(outputFile)) {
|
|
255
|
+
outputStream.write(imageBytes);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return outputFile.getAbsolutePath();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private PluginCall getPendingCall() {
|
|
262
|
+
if (pendingScan == null) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
return bridge.getSavedCall(pendingScan.callId);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private void releasePendingCall(PluginCall call) {
|
|
269
|
+
if (pendingScan != null) {
|
|
270
|
+
bridge.releaseCall(call);
|
|
271
|
+
pendingScan = null;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private int clamp(Integer value, int min, int max) {
|
|
276
|
+
if (value == null) {
|
|
277
|
+
return max;
|
|
278
|
+
}
|
|
279
|
+
return Math.max(min, Math.min(max, value));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private String normalizeResponseType(String value) {
|
|
283
|
+
if (value == null) {
|
|
284
|
+
return RESPONSE_TYPE_FILE_PATH;
|
|
285
|
+
}
|
|
286
|
+
String normalized = value.toLowerCase(Locale.ROOT);
|
|
287
|
+
if (RESPONSE_TYPE_BASE64.equals(normalized) || RESPONSE_TYPE_FILE_PATH.equals(normalized)) {
|
|
288
|
+
return normalized;
|
|
289
|
+
}
|
|
290
|
+
return RESPONSE_TYPE_FILE_PATH;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
File without changes
|
package/dist/docs.json
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
{
|
|
2
|
+
"api": {
|
|
3
|
+
"name": "DocumentScannerPlugin",
|
|
4
|
+
"slug": "documentscannerplugin",
|
|
5
|
+
"docs": "",
|
|
6
|
+
"tags": [],
|
|
7
|
+
"methods": [
|
|
8
|
+
{
|
|
9
|
+
"name": "scanDocument",
|
|
10
|
+
"signature": "(options?: ScanDocumentOptions | undefined) => Promise<ScanDocumentResponse>",
|
|
11
|
+
"parameters": [
|
|
12
|
+
{
|
|
13
|
+
"name": "options",
|
|
14
|
+
"docs": "",
|
|
15
|
+
"type": "ScanDocumentOptions | undefined"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"returns": "Promise<ScanDocumentResponse>",
|
|
19
|
+
"tags": [],
|
|
20
|
+
"docs": "Opens the device camera and starts the document scanning experience.",
|
|
21
|
+
"complexTypes": [
|
|
22
|
+
"ScanDocumentResponse",
|
|
23
|
+
"ScanDocumentOptions"
|
|
24
|
+
],
|
|
25
|
+
"slug": "scandocument"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"properties": []
|
|
29
|
+
},
|
|
30
|
+
"interfaces": [
|
|
31
|
+
{
|
|
32
|
+
"name": "ScanDocumentResponse",
|
|
33
|
+
"slug": "scandocumentresponse",
|
|
34
|
+
"docs": "",
|
|
35
|
+
"tags": [],
|
|
36
|
+
"methods": [],
|
|
37
|
+
"properties": [
|
|
38
|
+
{
|
|
39
|
+
"name": "scannedImages",
|
|
40
|
+
"tags": [],
|
|
41
|
+
"docs": "Scanned images in the requested response format.",
|
|
42
|
+
"complexTypes": [],
|
|
43
|
+
"type": "string[] | undefined"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "status",
|
|
47
|
+
"tags": [],
|
|
48
|
+
"docs": "Indicates whether the scan completed or was cancelled.",
|
|
49
|
+
"complexTypes": [
|
|
50
|
+
"ScanDocumentResponseStatus"
|
|
51
|
+
],
|
|
52
|
+
"type": "ScanDocumentResponseStatus"
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "ScanDocumentOptions",
|
|
58
|
+
"slug": "scandocumentoptions",
|
|
59
|
+
"docs": "",
|
|
60
|
+
"tags": [],
|
|
61
|
+
"methods": [],
|
|
62
|
+
"properties": [
|
|
63
|
+
{
|
|
64
|
+
"name": "croppedImageQuality",
|
|
65
|
+
"tags": [
|
|
66
|
+
{
|
|
67
|
+
"text": "100",
|
|
68
|
+
"name": "default"
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"docs": "Android only: quality of the cropped image from 0 - 100 (100 is best).",
|
|
72
|
+
"complexTypes": [],
|
|
73
|
+
"type": "number | undefined"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"name": "letUserAdjustCrop",
|
|
77
|
+
"tags": [
|
|
78
|
+
{
|
|
79
|
+
"text": "true",
|
|
80
|
+
"name": "default"
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
"docs": "Android only: allow the user to adjust the detected crop before saving.\nDisabling this forces single-document capture.",
|
|
84
|
+
"complexTypes": [],
|
|
85
|
+
"type": "boolean | undefined"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"name": "maxNumDocuments",
|
|
89
|
+
"tags": [
|
|
90
|
+
{
|
|
91
|
+
"text": "24",
|
|
92
|
+
"name": "default"
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
"docs": "Android only: maximum number of documents the user can scan.",
|
|
96
|
+
"complexTypes": [],
|
|
97
|
+
"type": "number | undefined"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"name": "responseType",
|
|
101
|
+
"tags": [
|
|
102
|
+
{
|
|
103
|
+
"text": "ResponseType.ImageFilePath",
|
|
104
|
+
"name": "default"
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
"docs": "Format to return scanned images in (file paths or base64 strings).",
|
|
108
|
+
"complexTypes": [
|
|
109
|
+
"ResponseType"
|
|
110
|
+
],
|
|
111
|
+
"type": "ResponseType"
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
],
|
|
116
|
+
"enums": [
|
|
117
|
+
{
|
|
118
|
+
"name": "ScanDocumentResponseStatus",
|
|
119
|
+
"slug": "scandocumentresponsestatus",
|
|
120
|
+
"members": [
|
|
121
|
+
{
|
|
122
|
+
"name": "Success",
|
|
123
|
+
"value": "'success'",
|
|
124
|
+
"tags": [],
|
|
125
|
+
"docs": "The scan completed successfully."
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"name": "Cancel",
|
|
129
|
+
"value": "'cancel'",
|
|
130
|
+
"tags": [],
|
|
131
|
+
"docs": "The user cancelled the scan flow."
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"name": "ResponseType",
|
|
137
|
+
"slug": "responsetype",
|
|
138
|
+
"members": [
|
|
139
|
+
{
|
|
140
|
+
"name": "Base64",
|
|
141
|
+
"value": "'base64'",
|
|
142
|
+
"tags": [],
|
|
143
|
+
"docs": "Return scanned images as base64-encoded strings."
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"name": "ImageFilePath",
|
|
147
|
+
"value": "'imageFilePath'",
|
|
148
|
+
"tags": [],
|
|
149
|
+
"docs": "Return scanned images as file paths on disk."
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
],
|
|
154
|
+
"typeAliases": [],
|
|
155
|
+
"pluginConfigs": []
|
|
156
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export interface DocumentScannerPlugin {
|
|
2
|
+
/**
|
|
3
|
+
* Opens the device camera and starts the document scanning experience.
|
|
4
|
+
*/
|
|
5
|
+
scanDocument(options?: ScanDocumentOptions): Promise<ScanDocumentResponse>;
|
|
6
|
+
}
|
|
7
|
+
export interface ScanDocumentOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Android only: quality of the cropped image from 0 - 100 (100 is best).
|
|
10
|
+
* @default 100
|
|
11
|
+
*/
|
|
12
|
+
croppedImageQuality?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Android only: allow the user to adjust the detected crop before saving.
|
|
15
|
+
* Disabling this forces single-document capture.
|
|
16
|
+
* @default true
|
|
17
|
+
*/
|
|
18
|
+
letUserAdjustCrop?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Android only: maximum number of documents the user can scan.
|
|
21
|
+
* @default 24
|
|
22
|
+
*/
|
|
23
|
+
maxNumDocuments?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Format to return scanned images in (file paths or base64 strings).
|
|
26
|
+
* @default ResponseType.ImageFilePath
|
|
27
|
+
*/
|
|
28
|
+
responseType?: ResponseType;
|
|
29
|
+
}
|
|
30
|
+
export declare enum ResponseType {
|
|
31
|
+
/**
|
|
32
|
+
* Return scanned images as base64-encoded strings.
|
|
33
|
+
*/
|
|
34
|
+
Base64 = "base64",
|
|
35
|
+
/**
|
|
36
|
+
* Return scanned images as file paths on disk.
|
|
37
|
+
*/
|
|
38
|
+
ImageFilePath = "imageFilePath"
|
|
39
|
+
}
|
|
40
|
+
export interface ScanDocumentResponse {
|
|
41
|
+
/**
|
|
42
|
+
* Scanned images in the requested response format.
|
|
43
|
+
*/
|
|
44
|
+
scannedImages?: string[];
|
|
45
|
+
/**
|
|
46
|
+
* Indicates whether the scan completed or was cancelled.
|
|
47
|
+
*/
|
|
48
|
+
status?: ScanDocumentResponseStatus;
|
|
49
|
+
}
|
|
50
|
+
export declare enum ScanDocumentResponseStatus {
|
|
51
|
+
/**
|
|
52
|
+
* The scan completed successfully.
|
|
53
|
+
*/
|
|
54
|
+
Success = "success",
|
|
55
|
+
/**
|
|
56
|
+
* The user cancelled the scan flow.
|
|
57
|
+
*/
|
|
58
|
+
Cancel = "cancel"
|
|
59
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export var ResponseType;
|
|
2
|
+
(function (ResponseType) {
|
|
3
|
+
/**
|
|
4
|
+
* Return scanned images as base64-encoded strings.
|
|
5
|
+
*/
|
|
6
|
+
ResponseType["Base64"] = "base64";
|
|
7
|
+
/**
|
|
8
|
+
* Return scanned images as file paths on disk.
|
|
9
|
+
*/
|
|
10
|
+
ResponseType["ImageFilePath"] = "imageFilePath";
|
|
11
|
+
})(ResponseType || (ResponseType = {}));
|
|
12
|
+
export var ScanDocumentResponseStatus;
|
|
13
|
+
(function (ScanDocumentResponseStatus) {
|
|
14
|
+
/**
|
|
15
|
+
* The scan completed successfully.
|
|
16
|
+
*/
|
|
17
|
+
ScanDocumentResponseStatus["Success"] = "success";
|
|
18
|
+
/**
|
|
19
|
+
* The user cancelled the scan flow.
|
|
20
|
+
*/
|
|
21
|
+
ScanDocumentResponseStatus["Cancel"] = "cancel";
|
|
22
|
+
})(ScanDocumentResponseStatus || (ScanDocumentResponseStatus = {}));
|
|
23
|
+
//# sourceMappingURL=definitions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAkCA,MAAM,CAAN,IAAY,YAUX;AAVD,WAAY,YAAY;IACtB;;OAEG;IACH,iCAAiB,CAAA;IAEjB;;OAEG;IACH,+CAA+B,CAAA;AACjC,CAAC,EAVW,YAAY,KAAZ,YAAY,QAUvB;AAcD,MAAM,CAAN,IAAY,0BAUX;AAVD,WAAY,0BAA0B;IACpC;;OAEG;IACH,iDAAmB,CAAA;IAEnB;;OAEG;IACH,+CAAiB,CAAA;AACnB,CAAC,EAVW,0BAA0B,KAA1B,0BAA0B,QAUrC","sourcesContent":["export interface DocumentScannerPlugin {\n /**\n * Opens the device camera and starts the document scanning experience.\n */\n scanDocument(options?: ScanDocumentOptions): Promise<ScanDocumentResponse>;\n}\n\nexport interface ScanDocumentOptions {\n /**\n * Android only: quality of the cropped image from 0 - 100 (100 is best).\n * @default 100\n */\n croppedImageQuality?: number;\n\n /**\n * Android only: allow the user to adjust the detected crop before saving.\n * Disabling this forces single-document capture.\n * @default true\n */\n letUserAdjustCrop?: boolean;\n\n /**\n * Android only: maximum number of documents the user can scan.\n * @default 24\n */\n maxNumDocuments?: number;\n\n /**\n * Format to return scanned images in (file paths or base64 strings).\n * @default ResponseType.ImageFilePath\n */\n responseType?: ResponseType;\n}\n\nexport enum ResponseType {\n /**\n * Return scanned images as base64-encoded strings.\n */\n Base64 = 'base64',\n\n /**\n * Return scanned images as file paths on disk.\n */\n ImageFilePath = 'imageFilePath',\n}\n\nexport interface ScanDocumentResponse {\n /**\n * Scanned images in the requested response format.\n */\n scannedImages?: string[];\n\n /**\n * Indicates whether the scan completed or was cancelled.\n */\n status?: ScanDocumentResponseStatus;\n}\n\nexport enum ScanDocumentResponseStatus {\n /**\n * The scan completed successfully.\n */\n Success = 'success',\n\n /**\n * The user cancelled the scan flow.\n */\n Cancel = 'cancel',\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { registerPlugin } from '@capacitor/core';
|
|
2
|
+
const DocumentScanner = registerPlugin('DocumentScanner', {
|
|
3
|
+
web: () => import('./web').then((m) => new m.DocumentScannerWeb()),
|
|
4
|
+
});
|
|
5
|
+
export * from './definitions';
|
|
6
|
+
export { DocumentScanner };
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAIjD,MAAM,eAAe,GAAG,cAAc,CAAwB,iBAAiB,EAAE;IAC/E,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,kBAAkB,EAAE,CAAC;CACnE,CAAC,CAAC;AAEH,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,CAAC","sourcesContent":["import { registerPlugin } from '@capacitor/core';\n\nimport type { DocumentScannerPlugin } from './definitions';\n\nconst DocumentScanner = registerPlugin<DocumentScannerPlugin>('DocumentScanner', {\n web: () => import('./web').then((m) => new m.DocumentScannerWeb()),\n});\n\nexport * from './definitions';\nexport { DocumentScanner };\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { WebPlugin } from '@capacitor/core';
|
|
2
|
+
import type { DocumentScannerPlugin, ScanDocumentOptions, ScanDocumentResponse } from './definitions';
|
|
3
|
+
export declare class DocumentScannerWeb extends WebPlugin implements DocumentScannerPlugin {
|
|
4
|
+
scanDocument(_options?: ScanDocumentOptions): Promise<ScanDocumentResponse>;
|
|
5
|
+
}
|
package/dist/esm/web.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C,MAAM,OAAO,kBAAmB,SAAQ,SAAS;IAC/C,KAAK,CAAC,YAAY,CAAC,QAA8B;QAC/C,MAAM,IAAI,CAAC,aAAa,CAAC,gDAAgD,CAAC,CAAC;IAC7E,CAAC;CACF","sourcesContent":["import { WebPlugin } from '@capacitor/core';\n\nimport type { DocumentScannerPlugin, ScanDocumentOptions, ScanDocumentResponse } from './definitions';\n\nexport class DocumentScannerWeb extends WebPlugin implements DocumentScannerPlugin {\n async scanDocument(_options?: ScanDocumentOptions): Promise<ScanDocumentResponse> {\n throw this.unimplemented('Document scanning is not supported on the web.');\n }\n}\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@capacitor/core');
|
|
4
|
+
|
|
5
|
+
exports.ResponseType = void 0;
|
|
6
|
+
(function (ResponseType) {
|
|
7
|
+
/**
|
|
8
|
+
* Return scanned images as base64-encoded strings.
|
|
9
|
+
*/
|
|
10
|
+
ResponseType["Base64"] = "base64";
|
|
11
|
+
/**
|
|
12
|
+
* Return scanned images as file paths on disk.
|
|
13
|
+
*/
|
|
14
|
+
ResponseType["ImageFilePath"] = "imageFilePath";
|
|
15
|
+
})(exports.ResponseType || (exports.ResponseType = {}));
|
|
16
|
+
exports.ScanDocumentResponseStatus = void 0;
|
|
17
|
+
(function (ScanDocumentResponseStatus) {
|
|
18
|
+
/**
|
|
19
|
+
* The scan completed successfully.
|
|
20
|
+
*/
|
|
21
|
+
ScanDocumentResponseStatus["Success"] = "success";
|
|
22
|
+
/**
|
|
23
|
+
* The user cancelled the scan flow.
|
|
24
|
+
*/
|
|
25
|
+
ScanDocumentResponseStatus["Cancel"] = "cancel";
|
|
26
|
+
})(exports.ScanDocumentResponseStatus || (exports.ScanDocumentResponseStatus = {}));
|
|
27
|
+
|
|
28
|
+
const DocumentScanner = core.registerPlugin('DocumentScanner', {
|
|
29
|
+
web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.DocumentScannerWeb()),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
class DocumentScannerWeb extends core.WebPlugin {
|
|
33
|
+
async scanDocument(_options) {
|
|
34
|
+
throw this.unimplemented('Document scanning is not supported on the web.');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
var web = /*#__PURE__*/Object.freeze({
|
|
39
|
+
__proto__: null,
|
|
40
|
+
DocumentScannerWeb: DocumentScannerWeb
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
exports.DocumentScanner = DocumentScanner;
|
|
44
|
+
//# sourceMappingURL=plugin.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.cjs.js","sources":["esm/definitions.js","esm/index.js","esm/web.js"],"sourcesContent":["export var ResponseType;\n(function (ResponseType) {\n /**\n * Return scanned images as base64-encoded strings.\n */\n ResponseType[\"Base64\"] = \"base64\";\n /**\n * Return scanned images as file paths on disk.\n */\n ResponseType[\"ImageFilePath\"] = \"imageFilePath\";\n})(ResponseType || (ResponseType = {}));\nexport var ScanDocumentResponseStatus;\n(function (ScanDocumentResponseStatus) {\n /**\n * The scan completed successfully.\n */\n ScanDocumentResponseStatus[\"Success\"] = \"success\";\n /**\n * The user cancelled the scan flow.\n */\n ScanDocumentResponseStatus[\"Cancel\"] = \"cancel\";\n})(ScanDocumentResponseStatus || (ScanDocumentResponseStatus = {}));\n//# sourceMappingURL=definitions.js.map","import { registerPlugin } from '@capacitor/core';\nconst DocumentScanner = registerPlugin('DocumentScanner', {\n web: () => import('./web').then((m) => new m.DocumentScannerWeb()),\n});\nexport * from './definitions';\nexport { DocumentScanner };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class DocumentScannerWeb extends WebPlugin {\n async scanDocument(_options) {\n throw this.unimplemented('Document scanning is not supported on the web.');\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["ResponseType","ScanDocumentResponseStatus","registerPlugin","WebPlugin"],"mappings":";;;;AAAWA;AACX,CAAC,UAAU,YAAY,EAAE;AACzB;AACA;AACA;AACA,IAAI,YAAY,CAAC,QAAQ,CAAC,GAAG,QAAQ;AACrC;AACA;AACA;AACA,IAAI,YAAY,CAAC,eAAe,CAAC,GAAG,eAAe;AACnD,CAAC,EAAEA,oBAAY,KAAKA,oBAAY,GAAG,EAAE,CAAC,CAAC;AAC5BC;AACX,CAAC,UAAU,0BAA0B,EAAE;AACvC;AACA;AACA;AACA,IAAI,0BAA0B,CAAC,SAAS,CAAC,GAAG,SAAS;AACrD;AACA;AACA;AACA,IAAI,0BAA0B,CAAC,QAAQ,CAAC,GAAG,QAAQ;AACnD,CAAC,EAAEA,kCAA0B,KAAKA,kCAA0B,GAAG,EAAE,CAAC,CAAC;;ACpB9D,MAAC,eAAe,GAAGC,mBAAc,CAAC,iBAAiB,EAAE;AAC1D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,kBAAkB,EAAE,CAAC;AACtE,CAAC;;ACFM,MAAM,kBAAkB,SAASC,cAAS,CAAC;AAClD,IAAI,MAAM,YAAY,CAAC,QAAQ,EAAE;AACjC,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,gDAAgD,CAAC;AAClF,IAAI;AACJ;;;;;;;;;"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
var capacitorDocumentScanner = (function (exports, core) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
exports.ResponseType = void 0;
|
|
5
|
+
(function (ResponseType) {
|
|
6
|
+
/**
|
|
7
|
+
* Return scanned images as base64-encoded strings.
|
|
8
|
+
*/
|
|
9
|
+
ResponseType["Base64"] = "base64";
|
|
10
|
+
/**
|
|
11
|
+
* Return scanned images as file paths on disk.
|
|
12
|
+
*/
|
|
13
|
+
ResponseType["ImageFilePath"] = "imageFilePath";
|
|
14
|
+
})(exports.ResponseType || (exports.ResponseType = {}));
|
|
15
|
+
exports.ScanDocumentResponseStatus = void 0;
|
|
16
|
+
(function (ScanDocumentResponseStatus) {
|
|
17
|
+
/**
|
|
18
|
+
* The scan completed successfully.
|
|
19
|
+
*/
|
|
20
|
+
ScanDocumentResponseStatus["Success"] = "success";
|
|
21
|
+
/**
|
|
22
|
+
* The user cancelled the scan flow.
|
|
23
|
+
*/
|
|
24
|
+
ScanDocumentResponseStatus["Cancel"] = "cancel";
|
|
25
|
+
})(exports.ScanDocumentResponseStatus || (exports.ScanDocumentResponseStatus = {}));
|
|
26
|
+
|
|
27
|
+
const DocumentScanner = core.registerPlugin('DocumentScanner', {
|
|
28
|
+
web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.DocumentScannerWeb()),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
class DocumentScannerWeb extends core.WebPlugin {
|
|
32
|
+
async scanDocument(_options) {
|
|
33
|
+
throw this.unimplemented('Document scanning is not supported on the web.');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
var web = /*#__PURE__*/Object.freeze({
|
|
38
|
+
__proto__: null,
|
|
39
|
+
DocumentScannerWeb: DocumentScannerWeb
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
exports.DocumentScanner = DocumentScanner;
|
|
43
|
+
|
|
44
|
+
return exports;
|
|
45
|
+
|
|
46
|
+
})({}, capacitorExports);
|
|
47
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["esm/definitions.js","esm/index.js","esm/web.js"],"sourcesContent":["export var ResponseType;\n(function (ResponseType) {\n /**\n * Return scanned images as base64-encoded strings.\n */\n ResponseType[\"Base64\"] = \"base64\";\n /**\n * Return scanned images as file paths on disk.\n */\n ResponseType[\"ImageFilePath\"] = \"imageFilePath\";\n})(ResponseType || (ResponseType = {}));\nexport var ScanDocumentResponseStatus;\n(function (ScanDocumentResponseStatus) {\n /**\n * The scan completed successfully.\n */\n ScanDocumentResponseStatus[\"Success\"] = \"success\";\n /**\n * The user cancelled the scan flow.\n */\n ScanDocumentResponseStatus[\"Cancel\"] = \"cancel\";\n})(ScanDocumentResponseStatus || (ScanDocumentResponseStatus = {}));\n//# sourceMappingURL=definitions.js.map","import { registerPlugin } from '@capacitor/core';\nconst DocumentScanner = registerPlugin('DocumentScanner', {\n web: () => import('./web').then((m) => new m.DocumentScannerWeb()),\n});\nexport * from './definitions';\nexport { DocumentScanner };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class DocumentScannerWeb extends WebPlugin {\n async scanDocument(_options) {\n throw this.unimplemented('Document scanning is not supported on the web.');\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["ResponseType","ScanDocumentResponseStatus","registerPlugin","WebPlugin"],"mappings":";;;AAAWA;IACX,CAAC,UAAU,YAAY,EAAE;IACzB;IACA;IACA;IACA,IAAI,YAAY,CAAC,QAAQ,CAAC,GAAG,QAAQ;IACrC;IACA;IACA;IACA,IAAI,YAAY,CAAC,eAAe,CAAC,GAAG,eAAe;IACnD,CAAC,EAAEA,oBAAY,KAAKA,oBAAY,GAAG,EAAE,CAAC,CAAC;AAC5BC;IACX,CAAC,UAAU,0BAA0B,EAAE;IACvC;IACA;IACA;IACA,IAAI,0BAA0B,CAAC,SAAS,CAAC,GAAG,SAAS;IACrD;IACA;IACA;IACA,IAAI,0BAA0B,CAAC,QAAQ,CAAC,GAAG,QAAQ;IACnD,CAAC,EAAEA,kCAA0B,KAAKA,kCAA0B,GAAG,EAAE,CAAC,CAAC;;ACpB9D,UAAC,eAAe,GAAGC,mBAAc,CAAC,iBAAiB,EAAE;IAC1D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,kBAAkB,EAAE,CAAC;IACtE,CAAC;;ICFM,MAAM,kBAAkB,SAASC,cAAS,CAAC;IAClD,IAAI,MAAM,YAAY,CAAC,QAAQ,EAAE;IACjC,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,gDAAgD,CAAC;IAClF,IAAI;IACJ;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
import VisionKit
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
Handles presenting the VisionKit document scanner and returning results.
|
|
6
|
+
*/
|
|
7
|
+
@available(iOS 13.0, *)
|
|
8
|
+
class DocScanner: NSObject, VNDocumentCameraViewControllerDelegate {
|
|
9
|
+
private weak var viewController: UIViewController?
|
|
10
|
+
private var successHandler: ([String]) -> Void
|
|
11
|
+
private var errorHandler: (String) -> Void
|
|
12
|
+
private var cancelHandler: () -> Void
|
|
13
|
+
private var responseType: String
|
|
14
|
+
private var croppedImageQuality: Int
|
|
15
|
+
|
|
16
|
+
init(
|
|
17
|
+
_ viewController: UIViewController? = nil,
|
|
18
|
+
successHandler: @escaping ([String]) -> Void = { _ in },
|
|
19
|
+
errorHandler: @escaping (String) -> Void = { _ in },
|
|
20
|
+
cancelHandler: @escaping () -> Void = {},
|
|
21
|
+
responseType: String = ResponseType.imageFilePath,
|
|
22
|
+
croppedImageQuality: Int = 100
|
|
23
|
+
) {
|
|
24
|
+
self.viewController = viewController
|
|
25
|
+
self.successHandler = successHandler
|
|
26
|
+
self.errorHandler = errorHandler
|
|
27
|
+
self.cancelHandler = cancelHandler
|
|
28
|
+
self.responseType = responseType
|
|
29
|
+
self.croppedImageQuality = croppedImageQuality
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
override convenience init() {
|
|
33
|
+
self.init(nil)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
func startScan() {
|
|
37
|
+
guard VNDocumentCameraViewController.isSupported else {
|
|
38
|
+
errorHandler("Document scanning is not supported on this device.")
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
DispatchQueue.main.async {
|
|
43
|
+
let documentCameraViewController = VNDocumentCameraViewController()
|
|
44
|
+
documentCameraViewController.delegate = self
|
|
45
|
+
self.viewController?.present(documentCameraViewController, animated: true)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func startScan(
|
|
50
|
+
_ viewController: UIViewController? = nil,
|
|
51
|
+
successHandler: @escaping ([String]) -> Void = { _ in },
|
|
52
|
+
errorHandler: @escaping (String) -> Void = { _ in },
|
|
53
|
+
cancelHandler: @escaping () -> Void = {},
|
|
54
|
+
responseType: String? = ResponseType.imageFilePath,
|
|
55
|
+
croppedImageQuality: Int? = 100
|
|
56
|
+
) {
|
|
57
|
+
self.viewController = viewController
|
|
58
|
+
self.successHandler = successHandler
|
|
59
|
+
self.errorHandler = errorHandler
|
|
60
|
+
self.cancelHandler = cancelHandler
|
|
61
|
+
self.responseType = responseType ?? ResponseType.imageFilePath
|
|
62
|
+
self.croppedImageQuality = croppedImageQuality ?? 100
|
|
63
|
+
|
|
64
|
+
startScan()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
func documentCameraViewController(
|
|
68
|
+
_ controller: VNDocumentCameraViewController,
|
|
69
|
+
didFinishWith scan: VNDocumentCameraScan
|
|
70
|
+
) {
|
|
71
|
+
var results: [String] = []
|
|
72
|
+
|
|
73
|
+
for pageNumber in 0 ..< scan.pageCount {
|
|
74
|
+
guard
|
|
75
|
+
let scannedImageData = scan.imageOfPage(at: pageNumber)
|
|
76
|
+
.jpegData(compressionQuality: CGFloat(croppedImageQuality) / CGFloat(100))
|
|
77
|
+
else {
|
|
78
|
+
goBackToPreviousView(controller)
|
|
79
|
+
errorHandler("Unable to get scanned document in jpeg format.")
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
switch responseType {
|
|
84
|
+
case ResponseType.base64:
|
|
85
|
+
results.append(scannedImageData.base64EncodedString())
|
|
86
|
+
case ResponseType.imageFilePath:
|
|
87
|
+
do {
|
|
88
|
+
let imagePath = FileUtil().createImageFile(pageNumber)
|
|
89
|
+
try scannedImageData.write(to: imagePath)
|
|
90
|
+
results.append(imagePath.absoluteString)
|
|
91
|
+
} catch {
|
|
92
|
+
goBackToPreviousView(controller)
|
|
93
|
+
errorHandler("Unable to save scanned image: \(error.localizedDescription)")
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
default:
|
|
97
|
+
errorHandler(
|
|
98
|
+
"responseType must be \(ResponseType.base64) or \(ResponseType.imageFilePath)"
|
|
99
|
+
)
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
goBackToPreviousView(controller)
|
|
105
|
+
successHandler(results)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
func documentCameraViewControllerDidCancel(_ controller: VNDocumentCameraViewController) {
|
|
109
|
+
goBackToPreviousView(controller)
|
|
110
|
+
cancelHandler()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
func documentCameraViewController(
|
|
114
|
+
_ controller: VNDocumentCameraViewController,
|
|
115
|
+
didFailWithError error: Error
|
|
116
|
+
) {
|
|
117
|
+
goBackToPreviousView(controller)
|
|
118
|
+
errorHandler(error.localizedDescription)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private func goBackToPreviousView(_ controller: VNDocumentCameraViewController) {
|
|
122
|
+
DispatchQueue.main.async {
|
|
123
|
+
controller.dismiss(animated: true)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import Capacitor
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
@available(iOS 13.0, *)
|
|
5
|
+
@objc(DocumentScannerPlugin)
|
|
6
|
+
public class DocumentScannerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
7
|
+
public let identifier = "DocumentScannerPlugin"
|
|
8
|
+
public let jsName = "DocumentScanner"
|
|
9
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
10
|
+
CAPPluginMethod(name: "scanDocument", returnType: CAPPluginReturnPromise)
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
private var documentScanner: DocScanner?
|
|
14
|
+
|
|
15
|
+
@objc func scanDocument(_ call: CAPPluginCall) {
|
|
16
|
+
guard let bridgeViewController = bridge?.viewController else {
|
|
17
|
+
call.reject("Bridge view controller unavailable.")
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
documentScanner = DocScanner(
|
|
22
|
+
bridgeViewController,
|
|
23
|
+
successHandler: { [weak self] scannedImages in
|
|
24
|
+
call.resolve([
|
|
25
|
+
"status": "success",
|
|
26
|
+
"scannedImages": scannedImages
|
|
27
|
+
])
|
|
28
|
+
self?.documentScanner = nil
|
|
29
|
+
},
|
|
30
|
+
errorHandler: { [weak self] errorMessage in
|
|
31
|
+
call.reject(errorMessage)
|
|
32
|
+
self?.documentScanner = nil
|
|
33
|
+
},
|
|
34
|
+
cancelHandler: { [weak self] in
|
|
35
|
+
call.resolve([
|
|
36
|
+
"status": "cancel"
|
|
37
|
+
])
|
|
38
|
+
self?.documentScanner = nil
|
|
39
|
+
},
|
|
40
|
+
responseType: call.getString("responseType") ?? ResponseType.imageFilePath,
|
|
41
|
+
croppedImageQuality: clampQuality(call.getInt("croppedImageQuality"))
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
documentScanner?.startScan()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private func clampQuality(_ value: Int?) -> Int {
|
|
48
|
+
let quality = value ?? 100
|
|
49
|
+
return max(0, min(100, quality))
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
Utilities for creating and managing scanned image files.
|
|
5
|
+
*/
|
|
6
|
+
class FileUtil {
|
|
7
|
+
func createImageFile(_ pageNumber: Int) -> URL {
|
|
8
|
+
let documentsDirectory = FileManager.default.urls(
|
|
9
|
+
for: .documentDirectory,
|
|
10
|
+
in: .userDomainMask
|
|
11
|
+
)[0]
|
|
12
|
+
|
|
13
|
+
return documentsDirectory.appendingPathComponent(
|
|
14
|
+
"DOCUMENT_SCAN_\(pageNumber)_\(currentTimestamp()).jpg"
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func getBase64Image(imageFilePath: String) throws -> String {
|
|
19
|
+
let imageUrl = try imageURL(imageFilePath)
|
|
20
|
+
guard let imageData = try? Data(contentsOf: imageUrl) else {
|
|
21
|
+
throw RuntimeError.message("Unable to get image from file")
|
|
22
|
+
}
|
|
23
|
+
return imageData.base64EncodedString()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func deleteImage(imageFilePath: String) throws {
|
|
27
|
+
try FileManager.default.removeItem(at: imageURL(imageFilePath))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private func imageURL(_ imageFilePath: String) throws -> URL {
|
|
31
|
+
guard let imageUrl = URL(string: imageFilePath) else {
|
|
32
|
+
throw RuntimeError.message("Unable to get image from file")
|
|
33
|
+
}
|
|
34
|
+
return imageUrl
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private func currentTimestamp() -> String {
|
|
38
|
+
let dateFormatter = DateFormatter()
|
|
39
|
+
dateFormatter.dateFormat = "yyyyMMdd_HHmmss"
|
|
40
|
+
return dateFormatter.string(from: Date())
|
|
41
|
+
}
|
|
42
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@capgo/capacitor-document-scanner",
|
|
3
|
+
"version": "7.0.0",
|
|
4
|
+
"description": "Capacitor plugin to scan document iOS and Android",
|
|
5
|
+
"main": "dist/plugin.cjs.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/esm/index.d.ts",
|
|
8
|
+
"unpkg": "dist/plugin.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"android/src/main/",
|
|
11
|
+
"android/build.gradle",
|
|
12
|
+
"dist/",
|
|
13
|
+
"ios/Sources",
|
|
14
|
+
"ios/Tests",
|
|
15
|
+
"Package.swift",
|
|
16
|
+
"CapgoCapacitorDocumentScanner.podspec"
|
|
17
|
+
],
|
|
18
|
+
"author": "Martin Donadieu <martin@capgo.app>",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/Cap-go/capacitor-document-scanner.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/Cap-go/capacitor-document-scanner/issues"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"capacitor",
|
|
29
|
+
"plugin",
|
|
30
|
+
"native"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
|
|
34
|
+
"verify:ios": "xcodebuild -scheme CapgoCapacitorDocumentScanner -destination generic/platform=iOS",
|
|
35
|
+
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
36
|
+
"verify:web": "npm run build",
|
|
37
|
+
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
|
|
38
|
+
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
|
|
39
|
+
"eslint": "eslint . --ext ts",
|
|
40
|
+
"prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
|
|
41
|
+
"swiftlint": "node-swiftlint",
|
|
42
|
+
"docgen": "docgen --api DocumentScannerPlugin --output-readme README.md --output-json dist/docs.json",
|
|
43
|
+
"build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
|
|
44
|
+
"clean": "rimraf ./dist",
|
|
45
|
+
"watch": "tsc --watch",
|
|
46
|
+
"prepublishOnly": "npm run build"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@capacitor/android": "^7.0.0",
|
|
50
|
+
"@capacitor/core": "^7.0.0",
|
|
51
|
+
"@capacitor/docgen": "^0.3.0",
|
|
52
|
+
"@capacitor/ios": "^7.0.0",
|
|
53
|
+
"@ionic/eslint-config": "^0.4.0",
|
|
54
|
+
"@ionic/prettier-config": "^4.0.0",
|
|
55
|
+
"@ionic/swiftlint-config": "^2.0.0",
|
|
56
|
+
"eslint": "^8.57.0",
|
|
57
|
+
"prettier": "^3.4.2",
|
|
58
|
+
"prettier-plugin-java": "^2.6.6",
|
|
59
|
+
"rimraf": "^6.0.1",
|
|
60
|
+
"rollup": "^4.30.1",
|
|
61
|
+
"swiftlint": "^2.0.0",
|
|
62
|
+
"typescript": "~4.1.5"
|
|
63
|
+
},
|
|
64
|
+
"peerDependencies": {
|
|
65
|
+
"@capacitor/core": ">=7.0.0"
|
|
66
|
+
},
|
|
67
|
+
"prettier": "@ionic/prettier-config",
|
|
68
|
+
"swiftlint": "@ionic/swiftlint-config",
|
|
69
|
+
"eslintConfig": {
|
|
70
|
+
"extends": "@ionic/eslint-config/recommended"
|
|
71
|
+
},
|
|
72
|
+
"capacitor": {
|
|
73
|
+
"ios": {
|
|
74
|
+
"src": "ios"
|
|
75
|
+
},
|
|
76
|
+
"android": {
|
|
77
|
+
"src": "android"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|