capacitor-camera-module 0.0.45 → 0.0.47

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