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.
@@ -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":"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"}
@@ -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
+ }