capacitor-camera-module 0.0.65 → 0.0.67
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.
|
@@ -5,26 +5,29 @@ import UIKit
|
|
|
5
5
|
import Photos
|
|
6
6
|
|
|
7
7
|
@objc(CameraModule)
|
|
8
|
-
public class CameraModulePlugin: CAPPlugin,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
8
|
+
public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,AVCaptureVideoDataOutputSampleBufferDelegate {
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
public let identifier = "CameraModule"
|
|
12
|
+
public let jsName = "CameraModule"
|
|
13
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
14
|
+
CAPPluginMethod(name: "checkPermission", returnType: CAPPluginReturnPromise),
|
|
15
|
+
CAPPluginMethod(name: "requestPermission", returnType: CAPPluginReturnPromise),
|
|
16
|
+
CAPPluginMethod(name: "checkGalleryPermission", returnType: CAPPluginReturnPromise),
|
|
17
|
+
CAPPluginMethod(name: "requestGalleryPermission", returnType: CAPPluginReturnPromise),
|
|
18
|
+
CAPPluginMethod(name: "checkAndRequestGalleryPermission", returnType: CAPPluginReturnPromise),
|
|
19
|
+
CAPPluginMethod(name: "pickImageBase64", returnType: CAPPluginReturnPromise),
|
|
20
|
+
CAPPluginMethod(name: "startPreview", returnType: CAPPluginReturnPromise),
|
|
21
|
+
CAPPluginMethod(name: "stopPreview", returnType: CAPPluginReturnPromise),
|
|
22
|
+
CAPPluginMethod(name: "toggleFlash", returnType: CAPPluginReturnPromise),
|
|
23
|
+
CAPPluginMethod(name: "hasFlash", returnType: CAPPluginReturnPromise),
|
|
24
|
+
CAPPluginMethod(name: "takePhotoBase64", returnType: CAPPluginReturnPromise),
|
|
25
|
+
CAPPluginMethod(name: "startBarcodeScan", returnType: CAPPluginReturnPromise),
|
|
26
|
+
CAPPluginMethod(name: "stopBarcodeScan", returnType: CAPPluginReturnPromise),
|
|
27
|
+
CAPPluginMethod(name: "getLastGalleryImage", returnType: CAPPluginReturnPromise)
|
|
28
|
+
|
|
29
|
+
]
|
|
30
|
+
|
|
28
31
|
|
|
29
32
|
private var previewView: UIView?
|
|
30
33
|
private var captureSession: AVCaptureSession?
|
|
@@ -33,6 +36,7 @@ public class CameraModulePlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureVideoData
|
|
|
33
36
|
private var photoDelegate: PhotoDelegate?
|
|
34
37
|
|
|
35
38
|
// MARK: - Barcode
|
|
39
|
+
|
|
36
40
|
private var isScanning = false
|
|
37
41
|
private var scanCall: CAPPluginCall?
|
|
38
42
|
private var barcodeRequest: VNDetectBarcodesRequest?
|
|
@@ -42,14 +46,20 @@ public class CameraModulePlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureVideoData
|
|
|
42
46
|
private let scanDebounceInterval: TimeInterval = 0.5
|
|
43
47
|
|
|
44
48
|
// MARK: - Gallery
|
|
49
|
+
|
|
45
50
|
private var galleryCall: CAPPluginCall?
|
|
46
51
|
|
|
47
52
|
// MARK: - Lifecycle
|
|
48
53
|
public override func load() {
|
|
54
|
+
// Este método ya no fuerza el color negro
|
|
55
|
+
// El webView será configurado dinámicamente en startPreview/stopPreview
|
|
49
56
|
print("📱 CameraModulePlugin cargado")
|
|
50
57
|
}
|
|
51
58
|
|
|
59
|
+
|
|
60
|
+
|
|
52
61
|
// MARK: - Camera Permission
|
|
62
|
+
|
|
53
63
|
@objc func checkPermission(_ call: CAPPluginCall) {
|
|
54
64
|
let status = AVCaptureDevice.authorizationStatus(for: .video)
|
|
55
65
|
call.resolve([
|
|
@@ -70,8 +80,10 @@ public class CameraModulePlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureVideoData
|
|
|
70
80
|
}
|
|
71
81
|
|
|
72
82
|
// MARK: - Gallery Permission
|
|
83
|
+
|
|
73
84
|
@objc func checkGalleryPermission(_ call: CAPPluginCall) {
|
|
74
85
|
let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
|
|
86
|
+
|
|
75
87
|
switch status {
|
|
76
88
|
case .authorized, .limited:
|
|
77
89
|
call.resolve(["granted": true, "status": "granted"])
|
|
@@ -104,6 +116,7 @@ public class CameraModulePlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureVideoData
|
|
|
104
116
|
}
|
|
105
117
|
|
|
106
118
|
// MARK: - Pick Image
|
|
119
|
+
|
|
107
120
|
@objc func pickImageBase64(_ call: CAPPluginCall) {
|
|
108
121
|
let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
|
|
109
122
|
guard status == .authorized || status == .limited else {
|
|
@@ -128,101 +141,108 @@ public class CameraModulePlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureVideoData
|
|
|
128
141
|
}
|
|
129
142
|
|
|
130
143
|
// MARK: - Camera Preview
|
|
131
|
-
@objc func startPreview(_ call: CAPPluginCall) {
|
|
132
|
-
DispatchQueue.main.async {
|
|
133
|
-
if self.previewView != nil {
|
|
134
|
-
call.resolve()
|
|
135
|
-
return
|
|
136
|
-
}
|
|
137
144
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
145
|
+
@objc func startPreview(_ call: CAPPluginCall) {
|
|
146
|
+
DispatchQueue.main.async {
|
|
147
|
+
if self.previewView != nil {
|
|
148
|
+
call.resolve()
|
|
149
|
+
return
|
|
150
|
+
}
|
|
142
151
|
|
|
143
|
-
|
|
152
|
+
guard AVCaptureDevice.authorizationStatus(for: .video) == .authorized else {
|
|
153
|
+
call.reject("Camera permission not granted")
|
|
154
|
+
return
|
|
155
|
+
}
|
|
144
156
|
|
|
145
|
-
|
|
146
|
-
let input = try? AVCaptureDeviceInput(device: device) else {
|
|
147
|
-
call.reject("Camera not available")
|
|
148
|
-
return
|
|
149
|
-
}
|
|
157
|
+
let session = AVCaptureSession()
|
|
150
158
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
159
|
+
guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
|
|
160
|
+
let input = try? AVCaptureDeviceInput(device: device) else {
|
|
161
|
+
call.reject("Camera not available")
|
|
162
|
+
return
|
|
163
|
+
}
|
|
154
164
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
165
|
+
if session.canAddInput(input) {
|
|
166
|
+
session.addInput(input)
|
|
167
|
+
}
|
|
158
168
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
169
|
+
if session.canAddOutput(self.photoOutput) {
|
|
170
|
+
session.addOutput(self.photoOutput)
|
|
171
|
+
}
|
|
163
172
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
173
|
+
guard let container = self.bridge?.viewController?.view else {
|
|
174
|
+
call.reject("No container view")
|
|
175
|
+
return
|
|
176
|
+
}
|
|
168
177
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
webView.scrollView.isOpaque = false
|
|
174
|
-
|
|
175
|
-
// Crear previewView
|
|
176
|
-
let previewView = UIView()
|
|
177
|
-
previewView.translatesAutoresizingMaskIntoConstraints = false
|
|
178
|
-
previewView.backgroundColor = .clear
|
|
179
|
-
previewView.isUserInteractionEnabled = false
|
|
180
|
-
|
|
181
|
-
// Insertar previewView DETRÁS de todo
|
|
182
|
-
container.insertSubview(previewView, at: 0)
|
|
183
|
-
|
|
184
|
-
// Configurar constraints para toda la pantalla
|
|
185
|
-
NSLayoutConstraint.activate([
|
|
186
|
-
previewView.topAnchor.constraint(equalTo: container.topAnchor),
|
|
187
|
-
previewView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
|
|
188
|
-
previewView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
|
|
189
|
-
previewView.trailingAnchor.constraint(equalTo: container.trailingAnchor)
|
|
190
|
-
])
|
|
178
|
+
guard let webView = self.bridge?.webView as? WKWebView else {
|
|
179
|
+
call.reject("No webView")
|
|
180
|
+
return
|
|
181
|
+
}
|
|
191
182
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
183
|
+
// 1. Hacer el webView transparente
|
|
184
|
+
webView.isOpaque = false
|
|
185
|
+
webView.backgroundColor = .clear
|
|
186
|
+
webView.scrollView.backgroundColor = .clear
|
|
187
|
+
webView.scrollView.isOpaque = false
|
|
188
|
+
|
|
189
|
+
// 2. Crear previewView
|
|
190
|
+
let previewView = UIView()
|
|
191
|
+
previewView.translatesAutoresizingMaskIntoConstraints = false
|
|
192
|
+
previewView.backgroundColor = .clear // Transparente desde el inicio
|
|
193
|
+
previewView.isUserInteractionEnabled = false // No intercepta toques
|
|
194
|
+
|
|
195
|
+
// 3. Insertar previewView DETRÁS de todo
|
|
196
|
+
container.insertSubview(previewView, at: 0)
|
|
197
|
+
|
|
198
|
+
// 4. Configurar constraints para toda la pantalla
|
|
199
|
+
NSLayoutConstraint.activate([
|
|
200
|
+
previewView.topAnchor.constraint(equalTo: container.topAnchor),
|
|
201
|
+
previewView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
|
|
202
|
+
previewView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
|
|
203
|
+
previewView.trailingAnchor.constraint(equalTo: container.trailingAnchor)
|
|
204
|
+
])
|
|
195
205
|
|
|
196
|
-
|
|
197
|
-
|
|
206
|
+
// 5. Asegurar orden de capas
|
|
207
|
+
previewView.layer.zPosition = -1 // Detrás de todo
|
|
208
|
+
webView.layer.zPosition = 0 // Normal
|
|
198
209
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
previewLayer.videoGravity = .resizeAspectFill
|
|
202
|
-
previewLayer.frame = previewView.bounds
|
|
203
|
-
previewLayer.masksToBounds = true
|
|
210
|
+
// 6. Forzar layout
|
|
211
|
+
container.layoutIfNeeded()
|
|
204
212
|
|
|
205
|
-
|
|
213
|
+
// 7. Crear previewLayer
|
|
214
|
+
let previewLayer = AVCaptureVideoPreviewLayer(session: session)
|
|
215
|
+
previewLayer.videoGravity = .resizeAspectFill
|
|
216
|
+
previewLayer.frame = previewView.bounds
|
|
217
|
+
previewLayer.masksToBounds = true
|
|
206
218
|
|
|
207
|
-
|
|
208
|
-
self.previewView = previewView
|
|
209
|
-
self.videoPreviewLayer = previewLayer
|
|
210
|
-
self.captureSession = session
|
|
219
|
+
previewView.layer.insertSublayer(previewLayer, at: 0)
|
|
211
220
|
|
|
212
|
-
|
|
213
|
-
|
|
221
|
+
// 8. Guardar referencias
|
|
222
|
+
self.previewView = previewView
|
|
223
|
+
self.videoPreviewLayer = previewLayer
|
|
224
|
+
self.captureSession = session
|
|
214
225
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
session.startRunning()
|
|
218
|
-
}
|
|
226
|
+
// 9. Configurar sesión
|
|
227
|
+
session.sessionPreset = .photo
|
|
219
228
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
229
|
+
// 10. Iniciar sesión en background
|
|
230
|
+
DispatchQueue.global(qos: .userInitiated).async {
|
|
231
|
+
session.startRunning()
|
|
232
|
+
|
|
233
|
+
DispatchQueue.main.async {
|
|
234
|
+
print("Cámara iniciada correctamente")
|
|
235
|
+
print("Preview visible: \(previewView.window != nil ? "SÍ" : "NO")")
|
|
236
|
+
}
|
|
224
237
|
}
|
|
238
|
+
|
|
239
|
+
call.resolve([
|
|
240
|
+
"success": true,
|
|
241
|
+
"message": "Preview de cámara iniciado"
|
|
242
|
+
])
|
|
225
243
|
}
|
|
244
|
+
}
|
|
245
|
+
|
|
226
246
|
|
|
227
247
|
@objc func stopPreview(_ call: CAPPluginCall) {
|
|
228
248
|
DispatchQueue.main.async {
|
|
@@ -237,19 +257,23 @@ public class CameraModulePlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureVideoData
|
|
|
237
257
|
self.videoPreviewLayer = nil
|
|
238
258
|
self.previewView = nil
|
|
239
259
|
|
|
240
|
-
// Restaurar webView a estado normal
|
|
260
|
+
// Restaurar webView a estado normal (opcional)
|
|
241
261
|
if let webView = self.bridge?.webView as? WKWebView {
|
|
242
262
|
webView.isOpaque = true
|
|
243
|
-
webView.backgroundColor = .white
|
|
263
|
+
webView.backgroundColor = .white // O el color de tu app
|
|
244
264
|
webView.scrollView.backgroundColor = .white
|
|
245
265
|
webView.scrollView.isOpaque = true
|
|
246
266
|
}
|
|
247
267
|
|
|
268
|
+
print("🛑 Preview detenido - webView restaurado")
|
|
248
269
|
call.resolve()
|
|
249
270
|
}
|
|
250
271
|
}
|
|
251
272
|
|
|
273
|
+
|
|
274
|
+
|
|
252
275
|
// MARK: - Flash
|
|
276
|
+
|
|
253
277
|
@objc func toggleFlash(_ call: CAPPluginCall) {
|
|
254
278
|
guard let enable = call.getBool("enable"),
|
|
255
279
|
let device = AVCaptureDevice.default(for: .video),
|
|
@@ -275,50 +299,53 @@ public class CameraModulePlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureVideoData
|
|
|
275
299
|
}
|
|
276
300
|
|
|
277
301
|
@objc func getLastGalleryImage(_ call: CAPPluginCall) {
|
|
278
|
-
let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
|
|
279
302
|
|
|
280
|
-
|
|
281
|
-
call.reject("Gallery permission not granted")
|
|
282
|
-
return
|
|
283
|
-
}
|
|
303
|
+
let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
|
|
284
304
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
fetchOptions.fetchLimit = 1
|
|
305
|
+
guard status == .authorized || status == .limited else {
|
|
306
|
+
call.reject("Gallery permission not granted")
|
|
307
|
+
return
|
|
308
|
+
}
|
|
290
309
|
|
|
291
|
-
|
|
310
|
+
let fetchOptions = PHFetchOptions()
|
|
311
|
+
fetchOptions.sortDescriptors = [
|
|
312
|
+
NSSortDescriptor(key: "creationDate", ascending: false)
|
|
313
|
+
]
|
|
314
|
+
fetchOptions.fetchLimit = 1
|
|
292
315
|
|
|
293
|
-
|
|
294
|
-
call.reject("No images found")
|
|
295
|
-
return
|
|
296
|
-
}
|
|
316
|
+
let assets = PHAsset.fetchAssets(with: .image, options: fetchOptions)
|
|
297
317
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
options.isSynchronous = true
|
|
301
|
-
options.deliveryMode = .highQualityFormat
|
|
302
|
-
options.resizeMode = .none
|
|
303
|
-
|
|
304
|
-
imageManager.requestImageDataAndOrientation(
|
|
305
|
-
for: asset,
|
|
306
|
-
options: options
|
|
307
|
-
) { data, _, _, _ in
|
|
308
|
-
guard let imageData = data else {
|
|
309
|
-
call.reject("Error fetching last image")
|
|
318
|
+
guard let asset = assets.firstObject else {
|
|
319
|
+
call.reject("No images found")
|
|
310
320
|
return
|
|
311
321
|
}
|
|
312
322
|
|
|
313
|
-
let
|
|
323
|
+
let imageManager = PHImageManager.default()
|
|
324
|
+
let options = PHImageRequestOptions()
|
|
325
|
+
options.isSynchronous = true
|
|
326
|
+
options.deliveryMode = .highQualityFormat
|
|
327
|
+
options.resizeMode = .none
|
|
328
|
+
|
|
329
|
+
imageManager.requestImageDataAndOrientation(
|
|
330
|
+
for: asset,
|
|
331
|
+
options: options
|
|
332
|
+
) { data, _, _, _ in
|
|
333
|
+
|
|
334
|
+
guard let imageData = data else {
|
|
335
|
+
call.reject("Error fetching last image")
|
|
336
|
+
return
|
|
337
|
+
}
|
|
314
338
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
339
|
+
let base64 = imageData.base64EncodedString()
|
|
340
|
+
|
|
341
|
+
var ret = JSObject()
|
|
342
|
+
ret["base64"] = base64
|
|
343
|
+
call.resolve(ret)
|
|
344
|
+
}
|
|
318
345
|
}
|
|
319
|
-
}
|
|
320
346
|
|
|
321
347
|
// MARK: - Photo Capture
|
|
348
|
+
|
|
322
349
|
@objc func takePhotoBase64(_ call: CAPPluginCall) {
|
|
323
350
|
guard captureSession?.isRunning == true else {
|
|
324
351
|
call.reject("Camera not started")
|
|
@@ -340,6 +367,7 @@ public class CameraModulePlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureVideoData
|
|
|
340
367
|
}
|
|
341
368
|
|
|
342
369
|
// MARK: - Barcode Scan
|
|
370
|
+
|
|
343
371
|
@objc func startBarcodeScan(_ call: CAPPluginCall) {
|
|
344
372
|
guard let session = captureSession else {
|
|
345
373
|
call.reject("Preview not started")
|
|
@@ -416,7 +444,8 @@ public class CameraModulePlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureVideoData
|
|
|
416
444
|
|
|
417
445
|
let handler = VNImageRequestHandler(
|
|
418
446
|
cvPixelBuffer: pixelBuffer,
|
|
419
|
-
orientation: CGImagePropertyOrientation.right
|
|
447
|
+
orientation: CGImagePropertyOrientation.right
|
|
448
|
+
,
|
|
420
449
|
options: [:]
|
|
421
450
|
)
|
|
422
451
|
|
|
@@ -425,10 +454,12 @@ public class CameraModulePlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureVideoData
|
|
|
425
454
|
}
|
|
426
455
|
|
|
427
456
|
// MARK: - UIImagePicker Delegate
|
|
457
|
+
|
|
428
458
|
extension CameraModulePlugin: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
|
|
429
459
|
|
|
430
460
|
public func imagePickerController(_ picker: UIImagePickerController,
|
|
431
461
|
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
|
|
462
|
+
|
|
432
463
|
picker.dismiss(animated: true)
|
|
433
464
|
|
|
434
465
|
guard let image = info[.originalImage] as? UIImage,
|
|
@@ -454,6 +485,7 @@ extension CameraModulePlugin: UIImagePickerControllerDelegate, UINavigationContr
|
|
|
454
485
|
}
|
|
455
486
|
|
|
456
487
|
// MARK: - UIImage Resize
|
|
488
|
+
|
|
457
489
|
extension UIImage {
|
|
458
490
|
func resized(maxSize: CGFloat) -> UIImage? {
|
|
459
491
|
let maxSide = max(size.width, size.height)
|
|
@@ -468,23 +500,6 @@ extension UIImage {
|
|
|
468
500
|
UIGraphicsEndImageContext()
|
|
469
501
|
return img
|
|
470
502
|
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// MARK: - Photo Delegate (si necesitas esta clase)
|
|
474
|
-
class PhotoDelegate: NSObject, AVCapturePhotoCaptureDelegate {
|
|
475
|
-
private let completion: (String) -> Void
|
|
476
503
|
|
|
477
|
-
init(completion: @escaping (String) -> Void) {
|
|
478
|
-
self.completion = completion
|
|
479
|
-
}
|
|
480
504
|
|
|
481
|
-
|
|
482
|
-
didFinishProcessingPhoto photo: AVCapturePhoto,
|
|
483
|
-
error: Error?) {
|
|
484
|
-
guard let data = photo.fileDataRepresentation(),
|
|
485
|
-
let base64 = data.base64EncodedString() else {
|
|
486
|
-
return
|
|
487
|
-
}
|
|
488
|
-
completion(base64)
|
|
489
|
-
}
|
|
490
|
-
}
|
|
505
|
+
}
|