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