capacitor-card-scanner 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapacitorCardScanner.podspec +17 -0
- package/Package.swift +28 -0
- package/README.md +268 -0
- package/android/build.gradle +64 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/pt/tcgclassifier/cardscanner/CardScanner.java +195 -0
- package/android/src/main/java/pt/tcgclassifier/cardscanner/CardScannerPlugin.java +131 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +296 -0
- package/dist/esm/definitions.d.ts +52 -0
- package/dist/esm/definitions.js +2 -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 +19 -0
- package/dist/esm/web.js +86 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +100 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +103 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/CardScannerPlugin/CardScanner.swift +151 -0
- package/ios/Sources/CardScannerPlugin/CardScannerPlugin.swift +119 -0
- package/ios/Tests/CardScannerPluginTests/CardScannerTests.swift +15 -0
- package/package.json +80 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@capacitor/core');
|
|
4
|
+
|
|
5
|
+
const CardScanner = core.registerPlugin('CardScanner', {
|
|
6
|
+
web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.CardScannerWeb()),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Browser implementation, useful for testing the layout on a desktop.
|
|
11
|
+
* The <video> element is placed fixed behind the page (z-index -1).
|
|
12
|
+
*/
|
|
13
|
+
class CardScannerWeb extends core.WebPlugin {
|
|
14
|
+
constructor() {
|
|
15
|
+
super(...arguments);
|
|
16
|
+
this.video = null;
|
|
17
|
+
this.stream = null;
|
|
18
|
+
this.position = 'rear';
|
|
19
|
+
}
|
|
20
|
+
async start(options) {
|
|
21
|
+
var _a;
|
|
22
|
+
await this.stop();
|
|
23
|
+
this.position = (_a = options === null || options === void 0 ? void 0 : options.position) !== null && _a !== void 0 ? _a : 'rear';
|
|
24
|
+
this.stream = await navigator.mediaDevices.getUserMedia({
|
|
25
|
+
video: { facingMode: this.position === 'front' ? 'user' : 'environment' },
|
|
26
|
+
audio: false,
|
|
27
|
+
});
|
|
28
|
+
const video = document.createElement('video');
|
|
29
|
+
video.setAttribute('playsinline', '');
|
|
30
|
+
video.muted = true;
|
|
31
|
+
video.autoplay = true;
|
|
32
|
+
Object.assign(video.style, {
|
|
33
|
+
position: 'fixed',
|
|
34
|
+
inset: '0',
|
|
35
|
+
width: '100%',
|
|
36
|
+
height: '100%',
|
|
37
|
+
objectFit: 'cover',
|
|
38
|
+
zIndex: '-1',
|
|
39
|
+
});
|
|
40
|
+
video.srcObject = this.stream;
|
|
41
|
+
document.body.appendChild(video);
|
|
42
|
+
await video.play();
|
|
43
|
+
this.video = video;
|
|
44
|
+
}
|
|
45
|
+
async stop() {
|
|
46
|
+
var _a, _b;
|
|
47
|
+
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((t) => t.stop());
|
|
48
|
+
this.stream = null;
|
|
49
|
+
(_b = this.video) === null || _b === void 0 ? void 0 : _b.remove();
|
|
50
|
+
this.video = null;
|
|
51
|
+
}
|
|
52
|
+
async capture(options) {
|
|
53
|
+
var _a, _b;
|
|
54
|
+
if (!this.video)
|
|
55
|
+
throw new Error('Camera is not running');
|
|
56
|
+
const canvas = document.createElement('canvas');
|
|
57
|
+
canvas.width = this.video.videoWidth;
|
|
58
|
+
canvas.height = this.video.videoHeight;
|
|
59
|
+
(_a = canvas.getContext('2d')) === null || _a === void 0 ? void 0 : _a.drawImage(this.video, 0, 0);
|
|
60
|
+
const quality = ((_b = options === null || options === void 0 ? void 0 : options.quality) !== null && _b !== void 0 ? _b : 85) / 100;
|
|
61
|
+
const base64 = canvas.toDataURL('image/jpeg', quality).split(',')[1];
|
|
62
|
+
return { base64, width: canvas.width, height: canvas.height };
|
|
63
|
+
}
|
|
64
|
+
async setTorch(_options) {
|
|
65
|
+
throw this.unavailable('Torch is not available on web');
|
|
66
|
+
}
|
|
67
|
+
async setZoom(_options) {
|
|
68
|
+
throw this.unavailable('Zoom is not available on web');
|
|
69
|
+
}
|
|
70
|
+
async flip() {
|
|
71
|
+
await this.start({ position: this.position === 'rear' ? 'front' : 'rear' });
|
|
72
|
+
}
|
|
73
|
+
async checkPermissions() {
|
|
74
|
+
try {
|
|
75
|
+
const status = await navigator.permissions.query({ name: 'camera' });
|
|
76
|
+
return { camera: status.state };
|
|
77
|
+
}
|
|
78
|
+
catch (_a) {
|
|
79
|
+
return { camera: 'prompt' };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async requestPermissions() {
|
|
83
|
+
try {
|
|
84
|
+
const s = await navigator.mediaDevices.getUserMedia({ video: true });
|
|
85
|
+
s.getTracks().forEach((t) => t.stop());
|
|
86
|
+
return { camera: 'granted' };
|
|
87
|
+
}
|
|
88
|
+
catch (_a) {
|
|
89
|
+
return { camera: 'denied' };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
var web = /*#__PURE__*/Object.freeze({
|
|
95
|
+
__proto__: null,
|
|
96
|
+
CardScannerWeb: CardScannerWeb
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
exports.CardScanner = CardScanner;
|
|
100
|
+
//# sourceMappingURL=plugin.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst CardScanner = registerPlugin('CardScanner', {\n web: () => import('./web').then((m) => new m.CardScannerWeb()),\n});\nexport * from './definitions';\nexport { CardScanner };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\n/**\n * Browser implementation, useful for testing the layout on a desktop.\n * The <video> element is placed fixed behind the page (z-index -1).\n */\nexport class CardScannerWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this.video = null;\n this.stream = null;\n this.position = 'rear';\n }\n async start(options) {\n var _a;\n await this.stop();\n this.position = (_a = options === null || options === void 0 ? void 0 : options.position) !== null && _a !== void 0 ? _a : 'rear';\n this.stream = await navigator.mediaDevices.getUserMedia({\n video: { facingMode: this.position === 'front' ? 'user' : 'environment' },\n audio: false,\n });\n const video = document.createElement('video');\n video.setAttribute('playsinline', '');\n video.muted = true;\n video.autoplay = true;\n Object.assign(video.style, {\n position: 'fixed',\n inset: '0',\n width: '100%',\n height: '100%',\n objectFit: 'cover',\n zIndex: '-1',\n });\n video.srcObject = this.stream;\n document.body.appendChild(video);\n await video.play();\n this.video = video;\n }\n async stop() {\n var _a, _b;\n (_a = this.stream) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((t) => t.stop());\n this.stream = null;\n (_b = this.video) === null || _b === void 0 ? void 0 : _b.remove();\n this.video = null;\n }\n async capture(options) {\n var _a, _b;\n if (!this.video)\n throw new Error('Camera is not running');\n const canvas = document.createElement('canvas');\n canvas.width = this.video.videoWidth;\n canvas.height = this.video.videoHeight;\n (_a = canvas.getContext('2d')) === null || _a === void 0 ? void 0 : _a.drawImage(this.video, 0, 0);\n const quality = ((_b = options === null || options === void 0 ? void 0 : options.quality) !== null && _b !== void 0 ? _b : 85) / 100;\n const base64 = canvas.toDataURL('image/jpeg', quality).split(',')[1];\n return { base64, width: canvas.width, height: canvas.height };\n }\n async setTorch(_options) {\n throw this.unavailable('Torch is not available on web');\n }\n async setZoom(_options) {\n throw this.unavailable('Zoom is not available on web');\n }\n async flip() {\n await this.start({ position: this.position === 'rear' ? 'front' : 'rear' });\n }\n async checkPermissions() {\n try {\n const status = await navigator.permissions.query({ name: 'camera' });\n return { camera: status.state };\n }\n catch (_a) {\n return { camera: 'prompt' };\n }\n }\n async requestPermissions() {\n try {\n const s = await navigator.mediaDevices.getUserMedia({ video: true });\n s.getTracks().forEach((t) => t.stop());\n return { camera: 'granted' };\n }\n catch (_a) {\n return { camera: 'denied' };\n }\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":"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"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
var capacitorCardScanner = (function (exports, core) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const CardScanner = core.registerPlugin('CardScanner', {
|
|
5
|
+
web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.CardScannerWeb()),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Browser implementation, useful for testing the layout on a desktop.
|
|
10
|
+
* The <video> element is placed fixed behind the page (z-index -1).
|
|
11
|
+
*/
|
|
12
|
+
class CardScannerWeb extends core.WebPlugin {
|
|
13
|
+
constructor() {
|
|
14
|
+
super(...arguments);
|
|
15
|
+
this.video = null;
|
|
16
|
+
this.stream = null;
|
|
17
|
+
this.position = 'rear';
|
|
18
|
+
}
|
|
19
|
+
async start(options) {
|
|
20
|
+
var _a;
|
|
21
|
+
await this.stop();
|
|
22
|
+
this.position = (_a = options === null || options === void 0 ? void 0 : options.position) !== null && _a !== void 0 ? _a : 'rear';
|
|
23
|
+
this.stream = await navigator.mediaDevices.getUserMedia({
|
|
24
|
+
video: { facingMode: this.position === 'front' ? 'user' : 'environment' },
|
|
25
|
+
audio: false,
|
|
26
|
+
});
|
|
27
|
+
const video = document.createElement('video');
|
|
28
|
+
video.setAttribute('playsinline', '');
|
|
29
|
+
video.muted = true;
|
|
30
|
+
video.autoplay = true;
|
|
31
|
+
Object.assign(video.style, {
|
|
32
|
+
position: 'fixed',
|
|
33
|
+
inset: '0',
|
|
34
|
+
width: '100%',
|
|
35
|
+
height: '100%',
|
|
36
|
+
objectFit: 'cover',
|
|
37
|
+
zIndex: '-1',
|
|
38
|
+
});
|
|
39
|
+
video.srcObject = this.stream;
|
|
40
|
+
document.body.appendChild(video);
|
|
41
|
+
await video.play();
|
|
42
|
+
this.video = video;
|
|
43
|
+
}
|
|
44
|
+
async stop() {
|
|
45
|
+
var _a, _b;
|
|
46
|
+
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((t) => t.stop());
|
|
47
|
+
this.stream = null;
|
|
48
|
+
(_b = this.video) === null || _b === void 0 ? void 0 : _b.remove();
|
|
49
|
+
this.video = null;
|
|
50
|
+
}
|
|
51
|
+
async capture(options) {
|
|
52
|
+
var _a, _b;
|
|
53
|
+
if (!this.video)
|
|
54
|
+
throw new Error('Camera is not running');
|
|
55
|
+
const canvas = document.createElement('canvas');
|
|
56
|
+
canvas.width = this.video.videoWidth;
|
|
57
|
+
canvas.height = this.video.videoHeight;
|
|
58
|
+
(_a = canvas.getContext('2d')) === null || _a === void 0 ? void 0 : _a.drawImage(this.video, 0, 0);
|
|
59
|
+
const quality = ((_b = options === null || options === void 0 ? void 0 : options.quality) !== null && _b !== void 0 ? _b : 85) / 100;
|
|
60
|
+
const base64 = canvas.toDataURL('image/jpeg', quality).split(',')[1];
|
|
61
|
+
return { base64, width: canvas.width, height: canvas.height };
|
|
62
|
+
}
|
|
63
|
+
async setTorch(_options) {
|
|
64
|
+
throw this.unavailable('Torch is not available on web');
|
|
65
|
+
}
|
|
66
|
+
async setZoom(_options) {
|
|
67
|
+
throw this.unavailable('Zoom is not available on web');
|
|
68
|
+
}
|
|
69
|
+
async flip() {
|
|
70
|
+
await this.start({ position: this.position === 'rear' ? 'front' : 'rear' });
|
|
71
|
+
}
|
|
72
|
+
async checkPermissions() {
|
|
73
|
+
try {
|
|
74
|
+
const status = await navigator.permissions.query({ name: 'camera' });
|
|
75
|
+
return { camera: status.state };
|
|
76
|
+
}
|
|
77
|
+
catch (_a) {
|
|
78
|
+
return { camera: 'prompt' };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async requestPermissions() {
|
|
82
|
+
try {
|
|
83
|
+
const s = await navigator.mediaDevices.getUserMedia({ video: true });
|
|
84
|
+
s.getTracks().forEach((t) => t.stop());
|
|
85
|
+
return { camera: 'granted' };
|
|
86
|
+
}
|
|
87
|
+
catch (_a) {
|
|
88
|
+
return { camera: 'denied' };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
var web = /*#__PURE__*/Object.freeze({
|
|
94
|
+
__proto__: null,
|
|
95
|
+
CardScannerWeb: CardScannerWeb
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
exports.CardScanner = CardScanner;
|
|
99
|
+
|
|
100
|
+
return exports;
|
|
101
|
+
|
|
102
|
+
})({}, capacitorExports);
|
|
103
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst CardScanner = registerPlugin('CardScanner', {\n web: () => import('./web').then((m) => new m.CardScannerWeb()),\n});\nexport * from './definitions';\nexport { CardScanner };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\n/**\n * Browser implementation, useful for testing the layout on a desktop.\n * The <video> element is placed fixed behind the page (z-index -1).\n */\nexport class CardScannerWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this.video = null;\n this.stream = null;\n this.position = 'rear';\n }\n async start(options) {\n var _a;\n await this.stop();\n this.position = (_a = options === null || options === void 0 ? void 0 : options.position) !== null && _a !== void 0 ? _a : 'rear';\n this.stream = await navigator.mediaDevices.getUserMedia({\n video: { facingMode: this.position === 'front' ? 'user' : 'environment' },\n audio: false,\n });\n const video = document.createElement('video');\n video.setAttribute('playsinline', '');\n video.muted = true;\n video.autoplay = true;\n Object.assign(video.style, {\n position: 'fixed',\n inset: '0',\n width: '100%',\n height: '100%',\n objectFit: 'cover',\n zIndex: '-1',\n });\n video.srcObject = this.stream;\n document.body.appendChild(video);\n await video.play();\n this.video = video;\n }\n async stop() {\n var _a, _b;\n (_a = this.stream) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((t) => t.stop());\n this.stream = null;\n (_b = this.video) === null || _b === void 0 ? void 0 : _b.remove();\n this.video = null;\n }\n async capture(options) {\n var _a, _b;\n if (!this.video)\n throw new Error('Camera is not running');\n const canvas = document.createElement('canvas');\n canvas.width = this.video.videoWidth;\n canvas.height = this.video.videoHeight;\n (_a = canvas.getContext('2d')) === null || _a === void 0 ? void 0 : _a.drawImage(this.video, 0, 0);\n const quality = ((_b = options === null || options === void 0 ? void 0 : options.quality) !== null && _b !== void 0 ? _b : 85) / 100;\n const base64 = canvas.toDataURL('image/jpeg', quality).split(',')[1];\n return { base64, width: canvas.width, height: canvas.height };\n }\n async setTorch(_options) {\n throw this.unavailable('Torch is not available on web');\n }\n async setZoom(_options) {\n throw this.unavailable('Zoom is not available on web');\n }\n async flip() {\n await this.start({ position: this.position === 'rear' ? 'front' : 'rear' });\n }\n async checkPermissions() {\n try {\n const status = await navigator.permissions.query({ name: 'camera' });\n return { camera: status.state };\n }\n catch (_a) {\n return { camera: 'prompt' };\n }\n }\n async requestPermissions() {\n try {\n const s = await navigator.mediaDevices.getUserMedia({ video: true });\n s.getTracks().forEach((t) => t.stop());\n return { camera: 'granted' };\n }\n catch (_a) {\n return { camera: 'denied' };\n }\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;AACK,UAAC,WAAW,GAAGA,mBAAc,CAAC,aAAa,EAAE;IAClD,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,cAAc,EAAE,CAAC;IAClE,CAAC;;ICFD;IACA;IACA;IACA;IACO,MAAM,cAAc,SAASC,cAAS,CAAC;IAC9C,IAAI,WAAW,GAAG;IAClB,QAAQ,KAAK,CAAC,GAAG,SAAS,CAAC;IAC3B,QAAQ,IAAI,CAAC,KAAK,GAAG,IAAI;IACzB,QAAQ,IAAI,CAAC,MAAM,GAAG,IAAI;IAC1B,QAAQ,IAAI,CAAC,QAAQ,GAAG,MAAM;IAC9B,IAAI;IACJ,IAAI,MAAM,KAAK,CAAC,OAAO,EAAE;IACzB,QAAQ,IAAI,EAAE;IACd,QAAQ,MAAM,IAAI,CAAC,IAAI,EAAE;IACzB,QAAQ,IAAI,CAAC,QAAQ,GAAG,CAAC,EAAE,GAAG,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,QAAQ,MAAM,IAAI,IAAI,EAAE,KAAK,MAAM,GAAG,EAAE,GAAG,MAAM;IACzI,QAAQ,IAAI,CAAC,MAAM,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,YAAY,CAAC;IAChE,YAAY,KAAK,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,QAAQ,KAAK,OAAO,GAAG,MAAM,GAAG,aAAa,EAAE;IACrF,YAAY,KAAK,EAAE,KAAK;IACxB,SAAS,CAAC;IACV,QAAQ,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC;IACrD,QAAQ,KAAK,CAAC,YAAY,CAAC,aAAa,EAAE,EAAE,CAAC;IAC7C,QAAQ,KAAK,CAAC,KAAK,GAAG,IAAI;IAC1B,QAAQ,KAAK,CAAC,QAAQ,GAAG,IAAI;IAC7B,QAAQ,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE;IACnC,YAAY,QAAQ,EAAE,OAAO;IAC7B,YAAY,KAAK,EAAE,GAAG;IACtB,YAAY,KAAK,EAAE,MAAM;IACzB,YAAY,MAAM,EAAE,MAAM;IAC1B,YAAY,SAAS,EAAE,OAAO;IAC9B,YAAY,MAAM,EAAE,IAAI;IACxB,SAAS,CAAC;IACV,QAAQ,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,MAAM;IACrC,QAAQ,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;IACxC,QAAQ,MAAM,KAAK,CAAC,IAAI,EAAE;IAC1B,QAAQ,IAAI,CAAC,KAAK,GAAG,KAAK;IAC1B,IAAI;IACJ,IAAI,MAAM,IAAI,GAAG;IACjB,QAAQ,IAAI,EAAE,EAAE,EAAE;IAClB,QAAQ,CAAC,EAAE,GAAG,IAAI,CAAC,MAAM,MAAM,IAAI,IAAI,EAAE,KAAK,MAAM,GAAG,MAAM,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IACvG,QAAQ,IAAI,CAAC,MAAM,GAAG,IAAI;IAC1B,QAAQ,CAAC,EAAE,GAAG,IAAI,CAAC,KAAK,MAAM,IAAI,IAAI,EAAE,KAAK,MAAM,GAAG,MAAM,GAAG,EAAE,CAAC,MAAM,EAAE;IAC1E,QAAQ,IAAI,CAAC,KAAK,GAAG,IAAI;IACzB,IAAI;IACJ,IAAI,MAAM,OAAO,CAAC,OAAO,EAAE;IAC3B,QAAQ,IAAI,EAAE,EAAE,EAAE;IAClB,QAAQ,IAAI,CAAC,IAAI,CAAC,KAAK;IACvB,YAAY,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC;IACpD,QAAQ,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC;IACvD,QAAQ,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU;IAC5C,QAAQ,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW;IAC9C,QAAQ,CAAC,EAAE,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,EAAE,KAAK,MAAM,GAAG,MAAM,GAAG,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IAC1G,QAAQ,MAAM,OAAO,GAAG,CAAC,CAAC,EAAE,GAAG,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,MAAM,IAAI,IAAI,EAAE,KAAK,MAAM,GAAG,EAAE,GAAG,EAAE,IAAI,GAAG;IAC5I,QAAQ,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC5E,QAAQ,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;IACrE,IAAI;IACJ,IAAI,MAAM,QAAQ,CAAC,QAAQ,EAAE;IAC7B,QAAQ,MAAM,IAAI,CAAC,WAAW,CAAC,+BAA+B,CAAC;IAC/D,IAAI;IACJ,IAAI,MAAM,OAAO,CAAC,QAAQ,EAAE;IAC5B,QAAQ,MAAM,IAAI,CAAC,WAAW,CAAC,8BAA8B,CAAC;IAC9D,IAAI;IACJ,IAAI,MAAM,IAAI,GAAG;IACjB,QAAQ,MAAM,IAAI,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,KAAK,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,CAAC;IACnF,IAAI;IACJ,IAAI,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,IAAI;IACZ,YAAY,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAChF,YAAY,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE;IAC3C,QAAQ;IACR,QAAQ,OAAO,EAAE,EAAE;IACnB,YAAY,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE;IACvC,QAAQ;IACR,IAAI;IACJ,IAAI,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,IAAI;IACZ,YAAY,MAAM,CAAC,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAChF,YAAY,CAAC,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IAClD,YAAY,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE;IACxC,QAAQ;IACR,QAAQ,OAAO,EAAE,EAAE;IACnB,YAAY,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE;IACvC,QAAQ;IACR,IAAI;IACJ;;;;;;;;;;;"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
import UIKit
|
|
3
|
+
import WebKit
|
|
4
|
+
|
|
5
|
+
enum CardScannerError: LocalizedError {
|
|
6
|
+
case noCamera
|
|
7
|
+
case notRunning
|
|
8
|
+
case captureFailed(String)
|
|
9
|
+
|
|
10
|
+
var errorDescription: String? {
|
|
11
|
+
switch self {
|
|
12
|
+
case .noCamera: return "No camera available"
|
|
13
|
+
case .notRunning: return "Camera is not running"
|
|
14
|
+
case .captureFailed(let msg): return "Capture failed: \(msg)"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Places an AVCaptureVideoPreviewLayer behind the Capacitor WebView and makes
|
|
21
|
+
* the WebView transparent, so the HTML UI is drawn on top of the camera feed.
|
|
22
|
+
*/
|
|
23
|
+
@objc public class CardScanner: NSObject, AVCapturePhotoCaptureDelegate {
|
|
24
|
+
private let session = AVCaptureSession()
|
|
25
|
+
private let photoOutput = AVCapturePhotoOutput()
|
|
26
|
+
private let sessionQueue = DispatchQueue(label: "pt.tcgclassifier.cardscanner.session")
|
|
27
|
+
private var input: AVCaptureDeviceInput?
|
|
28
|
+
private var previewView: PreviewView?
|
|
29
|
+
private var photoCompletion: ((Result<UIImage, Error>) -> Void)?
|
|
30
|
+
|
|
31
|
+
private(set) var position: AVCaptureDevice.Position = .back
|
|
32
|
+
|
|
33
|
+
var isRunning: Bool { input != nil }
|
|
34
|
+
|
|
35
|
+
/// Must be called on the main thread.
|
|
36
|
+
func start(position: AVCaptureDevice.Position, webView: WKWebView, completion: @escaping (Error?) -> Void) {
|
|
37
|
+
self.position = position
|
|
38
|
+
|
|
39
|
+
if previewView == nil, let container = webView.superview {
|
|
40
|
+
let view = PreviewView(frame: container.bounds)
|
|
41
|
+
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
42
|
+
view.previewLayer.session = session
|
|
43
|
+
view.previewLayer.videoGravity = .resizeAspectFill
|
|
44
|
+
container.insertSubview(view, belowSubview: webView)
|
|
45
|
+
previewView = view
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
webView.isOpaque = false
|
|
49
|
+
webView.backgroundColor = .clear
|
|
50
|
+
webView.scrollView.backgroundColor = .clear
|
|
51
|
+
|
|
52
|
+
sessionQueue.async {
|
|
53
|
+
do {
|
|
54
|
+
try self.configure(position: position)
|
|
55
|
+
if !self.session.isRunning { self.session.startRunning() }
|
|
56
|
+
DispatchQueue.main.async { completion(nil) }
|
|
57
|
+
} catch {
|
|
58
|
+
DispatchQueue.main.async { completion(error) }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private func configure(position: AVCaptureDevice.Position) throws {
|
|
64
|
+
guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: position) else {
|
|
65
|
+
throw CardScannerError.noCamera
|
|
66
|
+
}
|
|
67
|
+
let newInput = try AVCaptureDeviceInput(device: device)
|
|
68
|
+
|
|
69
|
+
session.beginConfiguration()
|
|
70
|
+
defer { session.commitConfiguration() }
|
|
71
|
+
|
|
72
|
+
session.sessionPreset = .photo
|
|
73
|
+
if let old = input { session.removeInput(old) }
|
|
74
|
+
if session.canAddInput(newInput) {
|
|
75
|
+
session.addInput(newInput)
|
|
76
|
+
input = newInput
|
|
77
|
+
}
|
|
78
|
+
if !session.outputs.contains(photoOutput), session.canAddOutput(photoOutput) {
|
|
79
|
+
session.addOutput(photoOutput)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// Must be called on the main thread.
|
|
84
|
+
func stop(webView: WKWebView?) {
|
|
85
|
+
previewView?.removeFromSuperview()
|
|
86
|
+
previewView = nil
|
|
87
|
+
webView?.isOpaque = true
|
|
88
|
+
webView?.backgroundColor = .white
|
|
89
|
+
input = nil
|
|
90
|
+
sessionQueue.async {
|
|
91
|
+
self.session.stopRunning()
|
|
92
|
+
self.session.beginConfiguration()
|
|
93
|
+
self.session.inputs.forEach { self.session.removeInput($0) }
|
|
94
|
+
self.session.commitConfiguration()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
func flip(completion: @escaping (Error?) -> Void) {
|
|
99
|
+
let next: AVCaptureDevice.Position = position == .back ? .front : .back
|
|
100
|
+
sessionQueue.async {
|
|
101
|
+
do {
|
|
102
|
+
try self.configure(position: next)
|
|
103
|
+
self.position = next
|
|
104
|
+
DispatchQueue.main.async { completion(nil) }
|
|
105
|
+
} catch {
|
|
106
|
+
DispatchQueue.main.async { completion(error) }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
func setTorch(_ enabled: Bool) throws {
|
|
112
|
+
guard let device = input?.device else { throw CardScannerError.notRunning }
|
|
113
|
+
guard device.hasTorch else { return }
|
|
114
|
+
try device.lockForConfiguration()
|
|
115
|
+
device.torchMode = enabled ? .on : .off
|
|
116
|
+
device.unlockForConfiguration()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
func setZoom(_ ratio: CGFloat) throws {
|
|
120
|
+
guard let device = input?.device else { throw CardScannerError.notRunning }
|
|
121
|
+
try device.lockForConfiguration()
|
|
122
|
+
device.videoZoomFactor = max(device.minAvailableVideoZoomFactor, min(ratio, device.maxAvailableVideoZoomFactor))
|
|
123
|
+
device.unlockForConfiguration()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
func capture(completion: @escaping (Result<UIImage, Error>) -> Void) {
|
|
127
|
+
guard isRunning else { return completion(.failure(CardScannerError.notRunning)) }
|
|
128
|
+
photoCompletion = completion
|
|
129
|
+
sessionQueue.async {
|
|
130
|
+
self.photoOutput.capturePhoto(with: AVCapturePhotoSettings(), delegate: self)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
|
|
135
|
+
let completion = photoCompletion
|
|
136
|
+
photoCompletion = nil
|
|
137
|
+
if let error = error {
|
|
138
|
+
completion?(.failure(error))
|
|
139
|
+
} else if let data = photo.fileDataRepresentation(), let image = UIImage(data: data) {
|
|
140
|
+
completion?(.success(image))
|
|
141
|
+
} else {
|
|
142
|
+
completion?(.failure(CardScannerError.captureFailed("no image data")))
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private class PreviewView: UIView {
|
|
148
|
+
override class var layerClass: AnyClass { AVCaptureVideoPreviewLayer.self }
|
|
149
|
+
// swiftlint:disable:next force_cast
|
|
150
|
+
var previewLayer: AVCaptureVideoPreviewLayer { layer as! AVCaptureVideoPreviewLayer }
|
|
151
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
import Foundation
|
|
3
|
+
import Capacitor
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Please read the Capacitor iOS Plugin Development Guide
|
|
7
|
+
* here: https://capacitorjs.com/docs/plugins/ios
|
|
8
|
+
*/
|
|
9
|
+
@objc(CardScannerPlugin)
|
|
10
|
+
public class CardScannerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
11
|
+
public let identifier = "CardScannerPlugin"
|
|
12
|
+
public let jsName = "CardScanner"
|
|
13
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
14
|
+
CAPPluginMethod(name: "start", returnType: CAPPluginReturnPromise),
|
|
15
|
+
CAPPluginMethod(name: "stop", returnType: CAPPluginReturnPromise),
|
|
16
|
+
CAPPluginMethod(name: "capture", returnType: CAPPluginReturnPromise),
|
|
17
|
+
CAPPluginMethod(name: "setTorch", returnType: CAPPluginReturnPromise),
|
|
18
|
+
CAPPluginMethod(name: "setZoom", returnType: CAPPluginReturnPromise),
|
|
19
|
+
CAPPluginMethod(name: "flip", returnType: CAPPluginReturnPromise),
|
|
20
|
+
CAPPluginMethod(name: "checkPermissions", returnType: CAPPluginReturnPromise),
|
|
21
|
+
CAPPluginMethod(name: "requestPermissions", returnType: CAPPluginReturnPromise)
|
|
22
|
+
]
|
|
23
|
+
private let implementation = CardScanner()
|
|
24
|
+
|
|
25
|
+
@objc func start(_ call: CAPPluginCall) {
|
|
26
|
+
let position: AVCaptureDevice.Position = call.getString("position") == "front" ? .front : .back
|
|
27
|
+
AVCaptureDevice.requestAccess(for: .video) { granted in
|
|
28
|
+
DispatchQueue.main.async {
|
|
29
|
+
guard granted else {
|
|
30
|
+
return call.reject("Camera permission denied", "PERMISSION_DENIED")
|
|
31
|
+
}
|
|
32
|
+
guard let webView = self.webView else {
|
|
33
|
+
return call.reject("WebView not available")
|
|
34
|
+
}
|
|
35
|
+
self.implementation.start(position: position, webView: webView) { error in
|
|
36
|
+
if let error = error {
|
|
37
|
+
call.reject(error.localizedDescription, nil, error)
|
|
38
|
+
} else {
|
|
39
|
+
call.resolve()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@objc func stop(_ call: CAPPluginCall) {
|
|
47
|
+
DispatchQueue.main.async {
|
|
48
|
+
self.implementation.stop(webView: self.webView)
|
|
49
|
+
call.resolve()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@objc func flip(_ call: CAPPluginCall) {
|
|
54
|
+
guard implementation.isRunning else { return call.reject("Camera is not running", "NOT_RUNNING") }
|
|
55
|
+
implementation.flip { error in
|
|
56
|
+
if let error = error {
|
|
57
|
+
call.reject(error.localizedDescription, nil, error)
|
|
58
|
+
} else {
|
|
59
|
+
call.resolve()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@objc func capture(_ call: CAPPluginCall) {
|
|
65
|
+
let quality = CGFloat(max(0, min(100, call.getInt("quality") ?? 85))) / 100
|
|
66
|
+
implementation.capture { result in
|
|
67
|
+
switch result {
|
|
68
|
+
case .success(let image):
|
|
69
|
+
guard let data = image.jpegData(compressionQuality: quality) else {
|
|
70
|
+
return call.reject("Failed to encode image")
|
|
71
|
+
}
|
|
72
|
+
call.resolve([
|
|
73
|
+
"base64": data.base64EncodedString(),
|
|
74
|
+
"width": Int(image.size.width * image.scale),
|
|
75
|
+
"height": Int(image.size.height * image.scale)
|
|
76
|
+
])
|
|
77
|
+
case .failure(let error):
|
|
78
|
+
call.reject(error.localizedDescription, nil, error)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@objc func setTorch(_ call: CAPPluginCall) {
|
|
84
|
+
do {
|
|
85
|
+
try implementation.setTorch(call.getBool("enabled") ?? false)
|
|
86
|
+
call.resolve()
|
|
87
|
+
} catch {
|
|
88
|
+
call.reject(error.localizedDescription, nil, error)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@objc func setZoom(_ call: CAPPluginCall) {
|
|
93
|
+
guard let ratio = call.getDouble("ratio") else { return call.reject("Missing 'ratio'") }
|
|
94
|
+
do {
|
|
95
|
+
try implementation.setZoom(CGFloat(ratio))
|
|
96
|
+
call.resolve()
|
|
97
|
+
} catch {
|
|
98
|
+
call.reject(error.localizedDescription, nil, error)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@objc override public func checkPermissions(_ call: CAPPluginCall) {
|
|
103
|
+
call.resolve(["camera": permissionState()])
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@objc override public func requestPermissions(_ call: CAPPluginCall) {
|
|
107
|
+
AVCaptureDevice.requestAccess(for: .video) { _ in
|
|
108
|
+
call.resolve(["camera": self.permissionState()])
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private func permissionState() -> String {
|
|
113
|
+
switch AVCaptureDevice.authorizationStatus(for: .video) {
|
|
114
|
+
case .authorized: return "granted"
|
|
115
|
+
case .denied, .restricted: return "denied"
|
|
116
|
+
default: return "prompt"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import CardScannerPlugin
|
|
3
|
+
|
|
4
|
+
class CardScannerTests: XCTestCase {
|
|
5
|
+
func testEcho() {
|
|
6
|
+
// This is an example of a functional test case for a plugin.
|
|
7
|
+
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
|
8
|
+
|
|
9
|
+
let implementation = CardScanner()
|
|
10
|
+
let value = "Hello, World!"
|
|
11
|
+
let result = implementation.echo(value)
|
|
12
|
+
|
|
13
|
+
XCTAssertEqual(value, result)
|
|
14
|
+
}
|
|
15
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "capacitor-card-scanner",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Recognize a pokemon card with camera open in background",
|
|
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
|
+
"CapacitorCardScanner.podspec"
|
|
17
|
+
],
|
|
18
|
+
"author": "Helder Roque",
|
|
19
|
+
"license": "Apache-2.0",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/Hroque1987/CardScanner.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/Hroque1987/CardScanner/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 CapacitorCardScanner -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 CardScannerPlugin --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": "^8.0.0",
|
|
50
|
+
"@capacitor/core": "^8.0.0",
|
|
51
|
+
"@capacitor/docgen": "^0.3.1",
|
|
52
|
+
"@capacitor/ios": "^8.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.1",
|
|
57
|
+
"prettier": "^3.6.2",
|
|
58
|
+
"prettier-plugin-java": "^2.7.7",
|
|
59
|
+
"rimraf": "^6.1.0",
|
|
60
|
+
"rollup": "^4.53.2",
|
|
61
|
+
"swiftlint": "^2.0.0",
|
|
62
|
+
"typescript": "^5.9.3"
|
|
63
|
+
},
|
|
64
|
+
"peerDependencies": {
|
|
65
|
+
"@capacitor/core": ">=8.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
|
+
}
|