capacitor-camera-module 0.0.45 → 0.0.46

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.
@@ -2,49 +2,56 @@ import Capacitor
2
2
  import AVFoundation
3
3
  import Vision
4
4
  import UIKit
5
-
5
+ import Photos
6
6
 
7
7
  @objc(CameraModule)
8
- public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,
9
- AVCaptureVideoDataOutputSampleBufferDelegate {
8
+ public class CameraModulePlugin: CAPPlugin,
9
+ AVCaptureVideoDataOutputSampleBufferDelegate {
10
+
11
+ // MARK: - Camera
10
12
 
11
13
  private var previewView: UIView?
12
14
  private var captureSession: AVCaptureSession?
13
15
  private var videoPreviewLayer: AVCaptureVideoPreviewLayer?
16
+ private let photoOutput = AVCapturePhotoOutput()
17
+ private var photoDelegate: PhotoDelegate?
18
+
19
+ // MARK: - Barcode
14
20
 
15
- private var photoOutput = AVCapturePhotoOutput()
16
21
  private var isScanning = false
17
22
  private var scanCall: CAPPluginCall?
18
-
19
- private var photoDelegate: PhotoDelegate?
20
-
21
23
  private var barcodeRequest: VNDetectBarcodesRequest?
22
-
23
24
  private var videoDataOutput: AVCaptureVideoDataOutput?
24
-
25
25
  private var lastScannedValue: String?
26
26
  private var lastScanTime: Date?
27
- private let scanDebounceInterval: TimeInterval = 0.5 // medio segundo
27
+ private let scanDebounceInterval: TimeInterval = 0.5
28
28
 
29
- public let identifier = "CameraModule"
30
- public let jsName = "CameraModule"
29
+ // MARK: - Gallery
31
30
 
31
+ private var galleryCall: CAPPluginCall?
32
+
33
+ // MARK: - Capacitor Bridge
32
34
 
33
35
  public let pluginMethods: [CAPPluginMethod] = [
34
- CAPPluginMethod(name: "checkPermission", returnType: CAPPluginReturnPromise),
35
- CAPPluginMethod(name: "requestPermission", returnType: CAPPluginReturnPromise),
36
+ CAPPluginMethod(name: "checkPermission", returnType: CAPPluginReturnPromise),
37
+ CAPPluginMethod(name: "requestPermission", returnType: CAPPluginReturnPromise),
38
+
39
+ CAPPluginMethod(name: "checkGalleryPermission", returnType: CAPPluginReturnPromise),
40
+ CAPPluginMethod(name: "requestGalleryPermission", returnType: CAPPluginReturnPromise),
41
+ CAPPluginMethod(name: "checkAndRequestGalleryPermission", returnType: CAPPluginReturnPromise),
42
+ CAPPluginMethod(name: "pickImageBase64", returnType: CAPPluginReturnPromise),
36
43
 
37
- CAPPluginMethod(name: "startPreview", returnType: CAPPluginReturnPromise),
38
- CAPPluginMethod(name: "stopPreview", returnType: CAPPluginReturnPromise),
44
+ CAPPluginMethod(name: "startPreview", returnType: CAPPluginReturnPromise),
45
+ CAPPluginMethod(name: "stopPreview", returnType: CAPPluginReturnPromise),
39
46
 
40
- CAPPluginMethod(name: "toggleFlash", returnType: CAPPluginReturnPromise),
41
- CAPPluginMethod(name: "hasFlash", returnType: CAPPluginReturnPromise),
47
+ CAPPluginMethod(name: "toggleFlash", returnType: CAPPluginReturnPromise),
48
+ CAPPluginMethod(name: "hasFlash", returnType: CAPPluginReturnPromise),
42
49
 
43
- CAPPluginMethod(name: "takePhotoBase64", returnType: CAPPluginReturnPromise),
50
+ CAPPluginMethod(name: "takePhotoBase64", returnType: CAPPluginReturnPromise),
44
51
 
45
- CAPPluginMethod(name: "startBarcodeScan", returnType: CAPPluginReturnPromise),
46
- CAPPluginMethod(name: "stopBarcodeScan", returnType: CAPPluginReturnPromise)
47
- ]
52
+ CAPPluginMethod(name: "startBarcodeScan", returnType: CAPPluginReturnPromise),
53
+ CAPPluginMethod(name: "stopBarcodeScan", returnType: CAPPluginReturnPromise)
54
+ ]
48
55
 
49
56
  // MARK: - Lifecycle
50
57
 
@@ -53,12 +60,10 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,
53
60
  bridge?.webView?.backgroundColor = .clear
54
61
  }
55
62
 
56
-
57
- // MARK: - Permissions
63
+ // MARK: - Camera Permission
58
64
 
59
65
  @objc func checkPermission(_ call: CAPPluginCall) {
60
66
  let status = AVCaptureDevice.authorizationStatus(for: .video)
61
-
62
67
  call.resolve([
63
68
  "granted": status == .authorized,
64
69
  "status": status == .authorized ? "granted" : "prompt"
@@ -76,11 +81,71 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,
76
81
  }
77
82
  }
78
83
 
84
+ // MARK: - Gallery Permission
85
+
86
+ @objc func checkGalleryPermission(_ call: CAPPluginCall) {
87
+ let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
88
+
89
+ switch status {
90
+ case .authorized, .limited:
91
+ call.resolve(["granted": true, "status": "granted"])
92
+ case .notDetermined:
93
+ call.resolve(["granted": false, "status": "prompt"])
94
+ default:
95
+ call.resolve(["granted": false, "status": "denied"])
96
+ }
97
+ }
98
+
99
+ @objc func requestGalleryPermission(_ call: CAPPluginCall) {
100
+ PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in
101
+ DispatchQueue.main.async {
102
+ let granted = (status == .authorized || status == .limited)
103
+ call.resolve([
104
+ "granted": granted,
105
+ "status": granted ? "granted" : "denied"
106
+ ])
107
+ }
108
+ }
109
+ }
110
+
111
+ @objc func checkAndRequestGalleryPermission(_ call: CAPPluginCall) {
112
+ let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
113
+ if status == .authorized || status == .limited {
114
+ call.resolve(["granted": true, "status": "granted"])
115
+ } else {
116
+ requestGalleryPermission(call)
117
+ }
118
+ }
119
+
120
+ // MARK: - Pick Image
121
+
122
+ @objc func pickImageBase64(_ call: CAPPluginCall) {
123
+ let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
124
+ guard status == .authorized || status == .limited else {
125
+ call.reject("Gallery permission not granted")
126
+ return
127
+ }
128
+
129
+ guard galleryCall == nil else {
130
+ call.reject("Gallery already open")
131
+ return
132
+ }
133
+
134
+ galleryCall = call
135
+
136
+ DispatchQueue.main.async {
137
+ let picker = UIImagePickerController()
138
+ picker.sourceType = .photoLibrary
139
+ picker.delegate = self
140
+ picker.modalPresentationStyle = .fullScreen
141
+ self.bridge?.viewController?.present(picker, animated: true)
142
+ }
143
+ }
144
+
79
145
  // MARK: - Camera Preview
80
146
 
81
147
  @objc func startPreview(_ call: CAPPluginCall) {
82
148
  DispatchQueue.main.async {
83
-
84
149
  if self.previewView != nil {
85
150
  call.resolve()
86
151
  return
@@ -89,11 +154,8 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,
89
154
  let session = AVCaptureSession()
90
155
  session.sessionPreset = .photo
91
156
 
92
- guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera,
93
- for: .video,
94
- position: .back),
95
- let input = try? AVCaptureDeviceInput(device: camera)
96
- else {
157
+ guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
158
+ let input = try? AVCaptureDeviceInput(device: device) else {
97
159
  call.reject("Camera not available")
98
160
  return
99
161
  }
@@ -102,16 +164,15 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,
102
164
  session.addOutput(self.photoOutput)
103
165
 
104
166
  let view = UIView(frame: UIScreen.main.bounds)
105
- let previewLayer = AVCaptureVideoPreviewLayer(session: session)
106
- previewLayer.videoGravity = .resizeAspectFill
107
- previewLayer.frame = view.bounds
108
-
109
- view.layer.addSublayer(previewLayer)
167
+ let layer = AVCaptureVideoPreviewLayer(session: session)
168
+ layer.videoGravity = .resizeAspectFill
169
+ layer.frame = view.bounds
110
170
 
171
+ view.layer.addSublayer(layer)
111
172
  self.bridge?.viewController?.view.insertSubview(view, at: 0)
112
173
 
113
174
  self.previewView = view
114
- self.videoPreviewLayer = previewLayer
175
+ self.videoPreviewLayer = layer
115
176
  self.captureSession = session
116
177
 
117
178
  session.startRunning()
@@ -121,16 +182,12 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,
121
182
 
122
183
  @objc func stopPreview(_ call: CAPPluginCall) {
123
184
  DispatchQueue.main.async {
124
-
125
185
  self.captureSession?.stopRunning()
126
186
  self.captureSession = nil
127
-
128
187
  self.videoPreviewLayer?.removeFromSuperlayer()
129
- self.videoPreviewLayer = nil
130
-
131
188
  self.previewView?.removeFromSuperview()
189
+ self.videoPreviewLayer = nil
132
190
  self.previewView = nil
133
-
134
191
  call.resolve()
135
192
  }
136
193
  }
@@ -138,14 +195,9 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,
138
195
  // MARK: - Flash
139
196
 
140
197
  @objc func toggleFlash(_ call: CAPPluginCall) {
141
- guard let enable = call.getBool("enable") else {
142
- call.reject("enable parameter is required")
143
- return
144
- }
145
-
146
- guard let device = AVCaptureDevice.default(for: .video),
147
- device.hasTorch
148
- else {
198
+ guard let enable = call.getBool("enable"),
199
+ let device = AVCaptureDevice.default(for: .video),
200
+ device.hasTorch else {
149
201
  call.reject("Flash not available")
150
202
  return
151
203
  }
@@ -161,14 +213,14 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,
161
213
  }
162
214
 
163
215
  @objc func hasFlash(_ call: CAPPluginCall) {
164
- let hasFlash = AVCaptureDevice.default(for: .video)?.hasTorch ?? false
165
- call.resolve(["hasFlash": hasFlash])
216
+ call.resolve([
217
+ "hasFlash": AVCaptureDevice.default(for: .video)?.hasTorch ?? false
218
+ ])
166
219
  }
167
220
 
168
221
  // MARK: - Photo Capture
169
222
 
170
223
  @objc func takePhotoBase64(_ call: CAPPluginCall) {
171
-
172
224
  guard captureSession?.isRunning == true else {
173
225
  call.reject("Camera not started")
174
226
  return
@@ -177,26 +229,20 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,
177
229
  let settings = AVCapturePhotoSettings()
178
230
  settings.flashMode = .off
179
231
 
180
- self.photoDelegate = PhotoDelegate { [weak self] base64 in
232
+ photoDelegate = PhotoDelegate { [weak self] base64 in
181
233
  call.resolve([
182
234
  "base64": base64,
183
235
  "mimeType": "image/jpeg"
184
236
  ])
185
-
186
237
  self?.photoDelegate = nil
187
238
  }
188
239
 
189
- self.photoOutput.capturePhoto(
190
- with: settings,
191
- delegate: self.photoDelegate!
192
- )
240
+ photoOutput.capturePhoto(with: settings, delegate: photoDelegate!)
193
241
  }
194
242
 
195
-
196
243
  // MARK: - Barcode Scan
197
244
 
198
245
  @objc func startBarcodeScan(_ call: CAPPluginCall) {
199
-
200
246
  guard let session = captureSession else {
201
247
  call.reject("Preview not started")
202
248
  return
@@ -212,23 +258,20 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,
212
258
  call.keepAlive = true
213
259
 
214
260
  barcodeRequest = VNDetectBarcodesRequest { request, _ in
215
- guard let results = request.results as? [VNBarcodeObservation],
216
- let barcode = results.first,
217
- let value = barcode.payloadStringValue
218
- else { return }
261
+ guard let barcode = (request.results as? [VNBarcodeObservation])?.first,
262
+ let value = barcode.payloadStringValue else { return }
219
263
 
220
- // Debounce
221
264
  let now = Date()
222
- if let last = self.lastScannedValue,
265
+ if self.lastScannedValue == value,
223
266
  let lastTime = self.lastScanTime,
224
- last == value && now.timeIntervalSince(lastTime) < self.scanDebounceInterval {
267
+ now.timeIntervalSince(lastTime) < self.scanDebounceInterval {
225
268
  return
226
269
  }
227
270
 
228
271
  self.lastScannedValue = value
229
272
  self.lastScanTime = now
230
-
231
273
  self.isScanning = false
274
+
232
275
  self.scanCall?.resolve([
233
276
  "rawValue": value,
234
277
  "format": barcode.symbology.rawValue
@@ -237,7 +280,6 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,
237
280
  self.scanCall = nil
238
281
  }
239
282
 
240
-
241
283
  if videoDataOutput == nil {
242
284
  let output = AVCaptureVideoDataOutput()
243
285
  output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "barcode.queue"))
@@ -247,59 +289,78 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,
247
289
  }
248
290
 
249
291
  @objc func stopBarcodeScan(_ call: CAPPluginCall) {
250
-
251
292
  isScanning = false
252
-
253
- if let output = videoDataOutput,
254
- let session = captureSession {
293
+ if let output = videoDataOutput, let session = captureSession {
255
294
  session.removeOutput(output)
256
- videoDataOutput = nil
257
295
  }
258
-
296
+ videoDataOutput = nil
259
297
  scanCall?.keepAlive = false
260
298
  scanCall = nil
261
-
262
- lastScannedValue = nil
263
- lastScanTime = nil
264
-
265
299
  call.resolve()
266
300
  }
267
301
 
268
-
269
- public func captureOutput(
270
- _ output: AVCaptureOutput,
271
- didOutput sampleBuffer: CMSampleBuffer,
272
- from connection: AVCaptureConnection
273
- ) {
302
+ public func captureOutput(_ output: AVCaptureOutput,
303
+ didOutput sampleBuffer: CMSampleBuffer,
304
+ from connection: AVCaptureConnection) {
274
305
  guard isScanning,
275
- let request = barcodeRequest
276
- else { return }
277
-
278
- guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
279
- return
280
- }
306
+ let request = barcodeRequest,
307
+ let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
281
308
 
282
309
  let handler = VNImageRequestHandler(
283
310
  cvPixelBuffer: pixelBuffer,
284
- orientation: cgOrientation(from: UIDevice.current.orientation),
311
+ orientation: .right,
285
312
  options: [:]
286
313
  )
287
314
 
288
- do {
289
- try handler.perform([request])
290
- } catch {
315
+ try? handler.perform([request])
316
+ }
317
+ }
318
+
319
+ // MARK: - UIImagePicker Delegate
320
+
321
+ extension CameraModulePlugin: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
291
322
 
323
+ public func imagePickerController(_ picker: UIImagePickerController,
324
+ didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
325
+
326
+ picker.dismiss(animated: true)
327
+
328
+ guard let image = info[.originalImage] as? UIImage,
329
+ let resized = image.resized(maxSize: 1024),
330
+ let jpeg = resized.jpegData(compressionQuality: 0.8) else {
331
+ galleryCall?.reject("Unable to process image")
332
+ galleryCall = nil
333
+ return
292
334
  }
335
+
336
+ galleryCall?.resolve([
337
+ "base64": jpeg.base64EncodedString(),
338
+ "mimeType": "image/jpeg"
339
+ ])
340
+ galleryCall = nil
293
341
  }
294
342
 
295
- func cgOrientation(from deviceOrientation: UIDeviceOrientation) -> CGImagePropertyOrientation {
296
- switch deviceOrientation {
297
- case .portrait: return .right
298
- case .portraitUpsideDown: return .left
299
- case .landscapeLeft: return .up
300
- case .landscapeRight: return .down
301
- default: return .right
302
- }
343
+ public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
344
+ picker.dismiss(animated: true)
345
+ galleryCall?.reject("Image selection canceled")
346
+ galleryCall = nil
303
347
  }
348
+ }
349
+
350
+ // MARK: - UIImage Resize
351
+
352
+ extension UIImage {
353
+ func resized(maxSize: CGFloat) -> UIImage? {
354
+ let maxSide = max(size.width, size.height)
355
+ if maxSide <= maxSize { return self }
304
356
 
357
+ let scale = maxSize / maxSide
358
+ let newSize = CGSize(width: size.width * scale, height: size.height * scale)
359
+
360
+ UIGraphicsBeginImageContextWithOptions(newSize, false, 1)
361
+ draw(in: CGRect(origin: .zero, size: newSize))
362
+ let img = UIGraphicsGetImageFromCurrentImageContext()
363
+ UIGraphicsEndImageContext()
364
+ return img
365
+ }
305
366
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-camera-module",
3
- "version": "0.0.45",
3
+ "version": "0.0.46",
4
4
  "description": "Plugin to request permissiones view camera take phots",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",