@capgo/camera-preview 7.4.0-beta.2 → 7.4.0-beta.4

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.
Files changed (31) hide show
  1. package/README.md +120 -30
  2. package/android/.gradle/8.14.2/checksums/checksums.lock +0 -0
  3. package/android/.gradle/8.14.2/checksums/md5-checksums.bin +0 -0
  4. package/android/.gradle/8.14.2/checksums/sha1-checksums.bin +0 -0
  5. package/android/.gradle/8.14.2/executionHistory/executionHistory.bin +0 -0
  6. package/android/.gradle/8.14.2/executionHistory/executionHistory.lock +0 -0
  7. package/android/.gradle/8.14.2/fileHashes/fileHashes.bin +0 -0
  8. package/android/.gradle/8.14.2/fileHashes/fileHashes.lock +0 -0
  9. package/android/.gradle/8.14.2/fileHashes/resourceHashesCache.bin +0 -0
  10. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  11. package/android/.gradle/file-system.probe +0 -0
  12. package/android/build.gradle +2 -1
  13. package/android/src/main/AndroidManifest.xml +1 -4
  14. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +111 -29
  15. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +152 -17
  16. package/android/src/main/java/com/ahm/capacitor/camera/preview/GridOverlayView.java +80 -0
  17. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraSessionConfiguration.java +19 -5
  18. package/dist/docs.json +117 -4
  19. package/dist/esm/definitions.d.ts +52 -3
  20. package/dist/esm/definitions.js.map +1 -1
  21. package/dist/esm/web.d.ts +16 -2
  22. package/dist/esm/web.js +180 -78
  23. package/dist/esm/web.js.map +1 -1
  24. package/dist/plugin.cjs.js +178 -78
  25. package/dist/plugin.cjs.js.map +1 -1
  26. package/dist/plugin.js +178 -78
  27. package/dist/plugin.js.map +1 -1
  28. package/ios/Sources/CapgoCameraPreview/CameraController.swift +131 -13
  29. package/ios/Sources/CapgoCameraPreview/GridOverlayView.swift +65 -0
  30. package/ios/Sources/CapgoCameraPreview/Plugin.swift +176 -37
  31. package/package.json +1 -1
@@ -8,6 +8,7 @@
8
8
 
9
9
  import AVFoundation
10
10
  import UIKit
11
+ import CoreLocation
11
12
 
12
13
  class CameraController: NSObject {
13
14
  var captureSession: AVCaptureSession?
@@ -26,6 +27,7 @@ class CameraController: NSObject {
26
27
  var fileVideoOutput: AVCaptureMovieFileOutput?
27
28
 
28
29
  var previewLayer: AVCaptureVideoPreviewLayer?
30
+ var gridOverlayView: GridOverlayView?
29
31
 
30
32
  var flashMode = AVCaptureDevice.FlashMode.off
31
33
  var photoCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?
@@ -50,7 +52,7 @@ class CameraController: NSObject {
50
52
  }
51
53
 
52
54
  extension CameraController {
53
- func prepare(cameraPosition: String, deviceId: String? = nil, disableAudio: Bool, cameraMode: Bool, completionHandler: @escaping (Error?) -> Void) {
55
+ func prepare(cameraPosition: String, deviceId: String? = nil, disableAudio: Bool, cameraMode: Bool, aspectRatio: String? = nil, completionHandler: @escaping (Error?) -> Void) {
54
56
  func createCaptureSession() {
55
57
  self.captureSession = AVCaptureSession()
56
58
  }
@@ -203,11 +205,54 @@ extension CameraController {
203
205
  func configurePhotoOutput(cameraMode: Bool) throws {
204
206
  guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
205
207
 
206
- // TODO: check if that really useful
207
- if !cameraMode && self.highResolutionOutput && captureSession.canSetSessionPreset(.photo) {
208
- captureSession.sessionPreset = .photo
209
- } else if cameraMode && self.highResolutionOutput && captureSession.canSetSessionPreset(.high) {
210
- captureSession.sessionPreset = .high
208
+ // Configure session preset based on aspect ratio and other settings
209
+ var targetPreset: AVCaptureSession.Preset = .photo // Default preset
210
+
211
+ if let aspectRatio = aspectRatio {
212
+ switch aspectRatio {
213
+ case "16:9":
214
+ // Use HD presets for 16:9 aspect ratio
215
+ if self.highResolutionOutput && captureSession.canSetSessionPreset(.hd1920x1080) {
216
+ targetPreset = .hd1920x1080
217
+ } else if captureSession.canSetSessionPreset(.hd1280x720) {
218
+ targetPreset = .hd1280x720
219
+ } else {
220
+ targetPreset = .high
221
+ }
222
+ case "4:3":
223
+ // Use photo preset for 4:3 aspect ratio (traditional photo format)
224
+ if self.highResolutionOutput && captureSession.canSetSessionPreset(.photo) {
225
+ targetPreset = .photo
226
+ } else {
227
+ targetPreset = .medium
228
+ }
229
+ default:
230
+ // Default behavior for unrecognized aspect ratios
231
+ if !cameraMode && self.highResolutionOutput && captureSession.canSetSessionPreset(.photo) {
232
+ targetPreset = .photo
233
+ } else if cameraMode && self.highResolutionOutput && captureSession.canSetSessionPreset(.high) {
234
+ targetPreset = .high
235
+ }
236
+ }
237
+ } else {
238
+ // Original logic when no aspect ratio is specified
239
+ if !cameraMode && self.highResolutionOutput && captureSession.canSetSessionPreset(.photo) {
240
+ targetPreset = .photo
241
+ } else if cameraMode && self.highResolutionOutput && captureSession.canSetSessionPreset(.high) {
242
+ targetPreset = .high
243
+ }
244
+ }
245
+
246
+ // Apply the determined preset
247
+ if captureSession.canSetSessionPreset(targetPreset) {
248
+ captureSession.sessionPreset = targetPreset
249
+ print("[CameraPreview] Set session preset to \(targetPreset) for aspect ratio: \(aspectRatio ?? "default")")
250
+ } else {
251
+ // Fallback to a basic preset if the target preset is not supported
252
+ print("[CameraPreview] Target preset \(targetPreset) not supported, falling back to .medium")
253
+ if captureSession.canSetSessionPreset(.medium) {
254
+ captureSession.sessionPreset = .medium
255
+ }
211
256
  }
212
257
 
213
258
  self.photoOutput = AVCapturePhotoOutput()
@@ -275,6 +320,19 @@ extension CameraController {
275
320
  updateVideoOrientation()
276
321
  }
277
322
 
323
+ func addGridOverlay(to view: UIView, gridMode: String) {
324
+ removeGridOverlay()
325
+
326
+ gridOverlayView = GridOverlayView(frame: view.bounds)
327
+ gridOverlayView?.gridMode = gridMode
328
+ view.addSubview(gridOverlayView!)
329
+ }
330
+
331
+ func removeGridOverlay() {
332
+ gridOverlayView?.removeFromSuperview()
333
+ gridOverlayView = nil
334
+ }
335
+
278
336
  func setupGestures(target: UIView, enableZoom: Bool) {
279
337
  setupTapGesture(target: target, selector: #selector(handleTap(_:)), delegate: self)
280
338
  if enableZoom {
@@ -427,15 +485,75 @@ extension CameraController {
427
485
  }
428
486
  }
429
487
 
430
- func captureImage(completion: @escaping (UIImage?, Error?) -> Void) {
431
- guard let captureSession = captureSession, captureSession.isRunning else { completion(nil, CameraControllerError.captureSessionIsMissing); return }
488
+ func captureImage(width: Int?, height: Int?, quality: Float, gpsLocation: CLLocation?, completion: @escaping (UIImage?, Error?) -> Void) {
489
+ guard let photoOutput = self.photoOutput else {
490
+ completion(nil, NSError(domain: "Camera", code: 0, userInfo: [NSLocalizedDescriptionKey: "Photo output is not available"]))
491
+ return
492
+ }
493
+
432
494
  let settings = AVCapturePhotoSettings()
433
495
 
434
- settings.flashMode = self.flashMode
435
- settings.isHighResolutionPhotoEnabled = self.highResolutionOutput
496
+ self.photoCaptureCompletionBlock = { (image, error) in
497
+ if let error = error {
498
+ completion(nil, error)
499
+ return
500
+ }
501
+
502
+ guard let image = image else {
503
+ completion(nil, NSError(domain: "Camera", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to capture image"]))
504
+ return
505
+ }
506
+
507
+ if let location = gpsLocation {
508
+ self.addGPSMetadata(to: image, location: location)
509
+ }
510
+
511
+ if let width = width, let height = height {
512
+ let resizedImage = self.resizeImage(image: image, to: CGSize(width: width, height: height))
513
+ completion(resizedImage, nil)
514
+ } else {
515
+ completion(image, nil)
516
+ }
517
+ }
518
+
519
+ photoOutput.capturePhoto(with: settings, delegate: self)
520
+ }
436
521
 
437
- self.photoOutput?.capturePhoto(with: settings, delegate: self)
438
- self.photoCaptureCompletionBlock = completion
522
+ func addGPSMetadata(to image: UIImage, location: CLLocation) {
523
+ guard let jpegData = image.jpegData(compressionQuality: 1.0),
524
+ let source = CGImageSourceCreateWithData(jpegData as CFData, nil),
525
+ let uti = CGImageSourceGetType(source) else { return }
526
+
527
+ var metadata = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [String: Any] ?? [:]
528
+
529
+ let formatter = DateFormatter()
530
+ formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
531
+ formatter.timeZone = TimeZone(abbreviation: "UTC")
532
+
533
+ let gpsDict: [String: Any] = [
534
+ kCGImagePropertyGPSLatitude as String: abs(location.coordinate.latitude),
535
+ kCGImagePropertyGPSLatitudeRef as String: location.coordinate.latitude >= 0 ? "N" : "S",
536
+ kCGImagePropertyGPSLongitude as String: abs(location.coordinate.longitude),
537
+ kCGImagePropertyGPSLongitudeRef as String: location.coordinate.longitude >= 0 ? "E" : "W",
538
+ kCGImagePropertyGPSTimeStamp as String: formatter.string(from: location.timestamp),
539
+ kCGImagePropertyGPSAltitude as String: location.altitude,
540
+ kCGImagePropertyGPSAltitudeRef as String: location.altitude >= 0 ? 0 : 1
541
+ ]
542
+
543
+ metadata[kCGImagePropertyGPSDictionary as String] = gpsDict
544
+
545
+ let destData = NSMutableData()
546
+ guard let destination = CGImageDestinationCreateWithData(destData, uti, 1, nil) else { return }
547
+ CGImageDestinationAddImageFromSource(destination, source, 0, metadata as CFDictionary)
548
+ CGImageDestinationFinalize(destination)
549
+ }
550
+
551
+ func resizeImage(image: UIImage, to size: CGSize) -> UIImage? {
552
+ let renderer = UIGraphicsImageRenderer(size: size)
553
+ let resizedImage = renderer.image { (context) in
554
+ image.draw(in: CGRect(origin: .zero, size: size))
555
+ }
556
+ return resizedImage
439
557
  }
440
558
 
441
559
  func captureSample(completion: @escaping (UIImage?, Error?) -> Void) {
@@ -677,7 +795,7 @@ extension CameraController {
677
795
 
678
796
  return device.uniqueID
679
797
  }
680
-
798
+
681
799
  func getCurrentLensInfo() throws -> (focalLength: Float, deviceType: String, baseZoomRatio: Float) {
682
800
  var currentCamera: AVCaptureDevice?
683
801
  switch currentCameraPosition {
@@ -0,0 +1,65 @@
1
+ import UIKit
2
+
3
+ class GridOverlayView: UIView {
4
+
5
+ var gridMode: String = "none" {
6
+ didSet {
7
+ isHidden = gridMode == "none"
8
+ setNeedsDisplay()
9
+ }
10
+ }
11
+
12
+ override init(frame: CGRect) {
13
+ super.init(frame: frame)
14
+ setup()
15
+ }
16
+
17
+ required init?(coder: NSCoder) {
18
+ super.init(coder: coder)
19
+ setup()
20
+ }
21
+
22
+ private func setup() {
23
+ backgroundColor = UIColor.clear
24
+ isUserInteractionEnabled = false
25
+ isHidden = true
26
+ }
27
+
28
+ override func draw(_ rect: CGRect) {
29
+ guard let context = UIGraphicsGetCurrentContext() else { return }
30
+
31
+ if gridMode == "none" {
32
+ return
33
+ }
34
+
35
+ context.setStrokeColor(UIColor.white.withAlphaComponent(0.5).cgColor)
36
+ context.setLineWidth(1.0)
37
+
38
+ if gridMode == "3x3" {
39
+ drawGrid(context: context, rect: rect, divisions: 3)
40
+ } else if gridMode == "4x4" {
41
+ drawGrid(context: context, rect: rect, divisions: 4)
42
+ }
43
+ }
44
+
45
+ private func drawGrid(context: CGContext, rect: CGRect, divisions: Int) {
46
+ let stepX = rect.width / CGFloat(divisions)
47
+ let stepY = rect.height / CGFloat(divisions)
48
+
49
+ // Draw vertical lines
50
+ for i in 1..<divisions {
51
+ let x = CGFloat(i) * stepX
52
+ context.move(to: CGPoint(x: x, y: 0))
53
+ context.addLine(to: CGPoint(x: x, y: rect.height))
54
+ }
55
+
56
+ // Draw horizontal lines
57
+ for i in 1..<divisions {
58
+ let y = CGFloat(i) * stepY
59
+ context.move(to: CGPoint(x: 0, y: y))
60
+ context.addLine(to: CGPoint(x: rect.width, y: y))
61
+ }
62
+
63
+ context.strokePath()
64
+ }
65
+ }
@@ -3,6 +3,8 @@ import Capacitor
3
3
  import AVFoundation
4
4
  import Photos
5
5
  import CoreImage
6
+ import CoreLocation
7
+
6
8
 
7
9
  extension UIWindow {
8
10
  static var isLandscape: Bool {
@@ -34,7 +36,7 @@ extension UIWindow {
34
36
  * here: https://capacitor.ionicframework.com/docs/plugins/ios
35
37
  */
36
38
  @objc(CameraPreview)
37
- public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
39
+ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelegate {
38
40
  public let identifier = "CameraPreviewPlugin"
39
41
  public let jsName = "CameraPreview"
40
42
  public let pluginMethods: [CAPPluginMethod] = [
@@ -56,7 +58,9 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
56
58
  CAPPluginMethod(name: "setZoom", returnType: CAPPluginReturnPromise),
57
59
  CAPPluginMethod(name: "getFlashMode", returnType: CAPPluginReturnPromise),
58
60
  CAPPluginMethod(name: "setDeviceId", returnType: CAPPluginReturnPromise),
59
- CAPPluginMethod(name: "getDeviceId", returnType: CAPPluginReturnPromise)
61
+ CAPPluginMethod(name: "getDeviceId", returnType: CAPPluginReturnPromise),
62
+ CAPPluginMethod(name: "setAspectRatio", returnType: CAPPluginReturnPromise),
63
+ CAPPluginMethod(name: "getAspectRatio", returnType: CAPPluginReturnPromise)
60
64
  ]
61
65
  // Camera state tracking
62
66
  private var isInitializing: Bool = false
@@ -76,33 +80,43 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
76
80
  var enableZoom: Bool?
77
81
  var highResolutionOutput: Bool = false
78
82
  var disableAudio: Bool = false
79
-
80
- // MARK: - Transparency Methods
83
+ var locationManager: CLLocationManager?
84
+ var currentLocation: CLLocation?
85
+ private var aspectRatio: String?
81
86
 
87
+ // MARK: - Transparency Methods
88
+
89
+
82
90
  private func makeWebViewTransparent() {
83
91
  guard let webView = self.webView else { return }
84
-
92
+
93
+
85
94
  // Define a recursive function to traverse the view hierarchy
86
95
  func makeSubviewsTransparent(_ view: UIView) {
87
96
  // Set the background color to clear
88
97
  view.backgroundColor = .clear
89
-
98
+
99
+
90
100
  // Recurse for all subviews
91
101
  for subview in view.subviews {
92
102
  makeSubviewsTransparent(subview)
93
103
  }
94
104
  }
95
-
105
+
106
+
96
107
  // Set the main webView to be transparent
97
108
  webView.isOpaque = false
98
109
  webView.backgroundColor = .clear
99
-
110
+
111
+
100
112
  // Recursively make all subviews transparent
101
113
  makeSubviewsTransparent(webView)
102
-
114
+
115
+
103
116
  // Also ensure the webview's container is transparent
104
117
  webView.superview?.backgroundColor = .clear
105
-
118
+
119
+
106
120
  // Force a layout pass to apply changes
107
121
  DispatchQueue.main.async {
108
122
  webView.setNeedsLayout()
@@ -147,13 +161,38 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
147
161
  }
148
162
 
149
163
  cameraController.updateVideoOrientation()
150
-
164
+
165
+ cameraController.updateVideoOrientation()
166
+
167
+ // Update grid overlay frame if it exists
168
+ if let gridOverlay = self.cameraController.gridOverlayView {
169
+ gridOverlay.frame = previewView.bounds
170
+ }
171
+
151
172
  // Ensure webview remains transparent after rotation
152
173
  if self.isInitialized {
153
174
  self.makeWebViewTransparent()
154
175
  }
155
176
  }
156
177
 
178
+ @objc func setAspectRatio(_ call: CAPPluginCall) {
179
+ guard self.isInitialized else {
180
+ call.reject("camera not started")
181
+ return
182
+ }
183
+ self.aspectRatio = call.getString("aspectRatio")
184
+ self.updateCameraFrame()
185
+ call.resolve()
186
+ }
187
+
188
+ @objc func getAspectRatio(_ call: CAPPluginCall) {
189
+ guard self.isInitialized else {
190
+ call.reject("camera not started")
191
+ return
192
+ }
193
+ call.resolve(["aspectRatio": self.aspectRatio ?? "fill"])
194
+ }
195
+
157
196
  @objc func appDidBecomeActive() {
158
197
  if self.isInitialized {
159
198
  DispatchQueue.main.async {
@@ -161,7 +200,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
161
200
  }
162
201
  }
163
202
  }
164
-
203
+
204
+
165
205
  @objc func appWillEnterForeground() {
166
206
  if self.isInitialized {
167
207
  DispatchQueue.main.async {
@@ -288,7 +328,9 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
288
328
  self.toBack = call.getBool("toBack") ?? true
289
329
  self.storeToFile = call.getBool("storeToFile") ?? false
290
330
  self.enableZoom = call.getBool("enableZoom") ?? false
291
- self.disableAudio = call.getBool("disableAudio") ?? false
331
+ self.disableAudio = call.getBool("disableAudio") ?? true
332
+ self.aspectRatio = call.getString("aspectRatio")
333
+ let gridMode = call.getString("gridMode") ?? "none"
292
334
 
293
335
  AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted: Bool) in
294
336
  guard granted else {
@@ -300,18 +342,18 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
300
342
  if self.cameraController.captureSession?.isRunning ?? false {
301
343
  call.reject("camera already started")
302
344
  } else {
303
- self.cameraController.prepare(cameraPosition: self.cameraPosition, deviceId: deviceId, disableAudio: self.disableAudio, cameraMode: cameraMode) {error in
345
+ self.cameraController.prepare(cameraPosition: self.cameraPosition, deviceId: deviceId, disableAudio: self.disableAudio, cameraMode: cameraMode, aspectRatio: self.aspectRatio) {error in
304
346
  if let error = error {
305
347
  print(error)
306
348
  call.reject(error.localizedDescription)
307
349
  return
308
350
  }
309
- let height = self.paddingBottom != nil ? self.height! - self.paddingBottom!: self.height!
310
- self.previewView = UIView(frame: CGRect(x: self.posX ?? 0, y: self.posY ?? 0, width: self.width!, height: height))
351
+ self.updateCameraFrame()
311
352
 
312
353
  // Make webview transparent - comprehensive approach
313
354
  self.makeWebViewTransparent()
314
-
355
+
356
+
315
357
  self.webView?.superview?.addSubview(self.previewView)
316
358
  if self.toBack! {
317
359
  self.webView?.superview?.bringSubviewToFront(self.webView!)
@@ -321,17 +363,34 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
321
363
  let frontView = self.toBack! ? self.webView : self.previewView
322
364
  self.cameraController.setupGestures(target: frontView ?? self.previewView, enableZoom: self.enableZoom!)
323
365
 
366
+ // Add grid overlay if enabled
367
+ if gridMode != "none" {
368
+ self.cameraController.addGridOverlay(to: self.previewView, gridMode: gridMode)
369
+ }
370
+
371
+ // Add grid overlay if enabled
372
+ if gridMode != "none" {
373
+ self.cameraController.addGridOverlay(to: self.previewView, gridMode: gridMode)
374
+ }
375
+
324
376
  if self.rotateWhenOrientationChanged == true {
325
377
  NotificationCenter.default.addObserver(self, selector: #selector(CameraPreview.rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
326
378
  }
327
-
379
+
380
+
328
381
  // Add observers for app state changes to maintain transparency
329
382
  NotificationCenter.default.addObserver(self, selector: #selector(CameraPreview.appDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
330
383
  NotificationCenter.default.addObserver(self, selector: #selector(CameraPreview.appWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
331
384
 
332
385
  self.isInitializing = false
333
386
  self.isInitialized = true
334
- call.resolve()
387
+
388
+ var returnedObject = JSObject()
389
+ returnedObject["width"] = self.previewView.frame.width as any JSValue
390
+ returnedObject["height"] = self.previewView.frame.height as any JSValue
391
+ returnedObject["x"] = self.previewView.frame.origin.x as any JSValue
392
+ returnedObject["y"] = self.previewView.frame.origin.y as any JSValue
393
+ call.resolve(returnedObject)
335
394
 
336
395
  }
337
396
  }
@@ -368,10 +427,12 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
368
427
  self.cameraController.previewLayer?.frame = self.previewView.bounds
369
428
  self.cameraController.previewLayer?.videoGravity = .resizeAspectFill
370
429
  self.previewView.isUserInteractionEnabled = true
371
-
430
+
431
+
372
432
  // Ensure webview remains transparent after flip
373
433
  self.makeWebViewTransparent()
374
-
434
+
435
+
375
436
  call.resolve()
376
437
  }
377
438
  } catch {
@@ -408,6 +469,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
408
469
  }
409
470
 
410
471
  // Always attempt to stop and clean up, regardless of captureSession state
472
+ self.cameraController.removeGridOverlay()
411
473
  if let previewView = self.previewView {
412
474
  previewView.removeFromSuperview()
413
475
  self.previewView = nil
@@ -417,7 +479,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
417
479
  self.isInitialized = false
418
480
  self.isInitializing = false
419
481
  self.cameraController.cleanup()
420
-
482
+
483
+
421
484
  // Remove notification observers
422
485
  NotificationCenter.default.removeObserver(self)
423
486
 
@@ -440,8 +503,18 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
440
503
 
441
504
  let quality = call.getFloat("quality", 85)
442
505
  let saveToGallery = call.getBool("saveToGallery", false)
506
+ let withExifLocation = call.getBool("withExifLocation", false)
507
+ let width = call.getInt("width")
508
+ let height = call.getInt("height")
509
+
510
+ if withExifLocation {
511
+ self.locationManager = CLLocationManager()
512
+ self.locationManager?.delegate = self
513
+ self.locationManager?.requestWhenInUseAuthorization()
514
+ self.locationManager?.startUpdatingLocation()
515
+ }
443
516
 
444
- self.cameraController.captureImage(quality: quality) { (image, error) in
517
+ self.cameraController.captureImage(width: width, height: height, quality: quality, gpsLocation: self.currentLocation) { (image, error) in
445
518
  if let error = error {
446
519
  call.reject(error.localizedDescription)
447
520
  return
@@ -452,7 +525,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
452
525
  PHAssetChangeRequest.creationRequestForAsset(from: image!)
453
526
  }, completionHandler: { (success, error) in
454
527
  if !success {
455
- Logger.error("CameraPreview", "Error saving image to gallery", error)
528
+ print("CameraPreview: Error saving image to gallery: \(error?.localizedDescription ?? "Unknown error")")
456
529
  }
457
530
  })
458
531
  }
@@ -461,10 +534,12 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
461
534
  call.reject("Failed to get JPEG data from image")
462
535
  return
463
536
  }
464
-
537
+
538
+
465
539
  let exifData = self.getExifData(from: imageData)
466
540
  let base64Image = imageData.base64EncodedString()
467
-
541
+
542
+
468
543
  var result = JSObject()
469
544
  result["value"] = base64Image
470
545
  result["exif"] = exifData
@@ -479,12 +554,28 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
479
554
  let exifDict = imageProperties[kCGImagePropertyExifDictionary as String] as? [String: Any] else {
480
555
  return [:]
481
556
  }
482
-
557
+
558
+
483
559
  var exifData = JSObject()
484
560
  for (key, value) in exifDict {
485
- exifData[key] = value
561
+ // Convert value to JSValue-compatible type
562
+ if let stringValue = value as? String {
563
+ exifData[key] = stringValue
564
+ } else if let numberValue = value as? NSNumber {
565
+ exifData[key] = numberValue
566
+ } else if let boolValue = value as? Bool {
567
+ exifData[key] = boolValue
568
+ } else if let arrayValue = value as? [Any] {
569
+ exifData[key] = arrayValue
570
+ } else if let dictValue = value as? [String: Any] {
571
+ exifData[key] = JSObject(_immutableCocoaDictionary: NSMutableDictionary(dictionary: dictValue))
572
+ } else {
573
+ // Convert other types to string as fallback
574
+ exifData[key] = String(describing: value)
575
+ }
486
576
  }
487
-
577
+
578
+
488
579
  return exifData
489
580
  }
490
581
 
@@ -627,9 +718,11 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
627
718
  // Collect all devices by position
628
719
  for device in session.devices {
629
720
  var lenses: [[String: Any]] = []
630
-
721
+
722
+
631
723
  let constituentDevices = device.isVirtualDevice ? device.constituentDevices : [device]
632
-
724
+
725
+
633
726
  for lensDevice in constituentDevices {
634
727
  var deviceType: String
635
728
  switch lensDevice.deviceType {
@@ -649,7 +742,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
649
742
  } else if lensDevice.deviceType == .builtInTelephotoCamera {
650
743
  baseZoomRatio = 2.0 // A common value for telephoto lenses
651
744
  }
652
-
745
+
746
+
653
747
  let lensInfo: [String: Any] = [
654
748
  "label": lensDevice.localizedName,
655
749
  "deviceType": deviceType,
@@ -660,7 +754,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
660
754
  ]
661
755
  lenses.append(lensInfo)
662
756
  }
663
-
757
+
758
+
664
759
  let deviceData: [String: Any] = [
665
760
  "deviceId": device.uniqueID,
666
761
  "label": device.localizedName,
@@ -670,7 +765,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
670
765
  "maxZoom": Float(device.maxAvailableVideoZoomFactor),
671
766
  "isLogical": device.isVirtualDevice
672
767
  ]
673
-
768
+
769
+
674
770
  devices.append(deviceData)
675
771
  }
676
772
 
@@ -686,7 +782,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
686
782
  do {
687
783
  let zoomInfo = try self.cameraController.getZoom()
688
784
  let lensInfo = try self.cameraController.getCurrentLensInfo()
689
-
785
+
786
+
690
787
  var minZoom = zoomInfo.min
691
788
  var maxZoom = zoomInfo.max
692
789
  var currentZoom = zoomInfo.current
@@ -782,10 +879,12 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
782
879
  self.cameraController.previewLayer?.frame = self.previewView.bounds
783
880
  self.cameraController.previewLayer?.videoGravity = .resizeAspectFill
784
881
  self.previewView.isUserInteractionEnabled = true
785
-
882
+
883
+
786
884
  // Ensure webview remains transparent after device switch
787
885
  self.makeWebViewTransparent()
788
-
886
+
887
+
789
888
  call.resolve()
790
889
  }
791
890
  } catch {
@@ -812,6 +911,46 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
812
911
  }
813
912
  }
814
913
 
914
+ public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
915
+ self.currentLocation = locations.last
916
+ self.locationManager?.stopUpdatingLocation()
917
+ }
815
918
 
919
+ public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
920
+ print("CameraPreview: Failed to get location: \(error.localizedDescription)")
921
+ }
816
922
 
923
+ private func updateCameraFrame() {
924
+ guard let width = self.width, var height = self.height, let posX = self.posX, let posY = self.posY else { return }
925
+ let paddingBottom = self.paddingBottom ?? 0
926
+ height -= paddingBottom
927
+
928
+ var frame = CGRect(x: posX, y: posY, width: width, height: height)
929
+
930
+ if let aspectRatio = self.aspectRatio, aspectRatio != "fill" {
931
+ let ratioParts = aspectRatio.split(separator: ":").map { Double($0) ?? 1.0 }
932
+ let ratio = ratioParts[0] / ratioParts[1]
933
+ let viewWidth = Double(width)
934
+ let viewHeight = Double(height)
935
+
936
+ if viewWidth / ratio > viewHeight {
937
+ let newWidth = viewHeight * ratio
938
+ frame.origin.x += (viewWidth - newWidth) / 2
939
+ frame.size.width = newWidth
940
+ } else {
941
+ let newHeight = viewWidth / ratio
942
+ frame.origin.y += (viewHeight - newHeight) / 2
943
+ frame.size.height = newHeight
944
+ }
945
+ }
946
+
947
+ if self.previewView == nil {
948
+ self.previewView = UIView(frame: frame)
949
+ } else {
950
+ self.previewView.frame = frame
951
+ }
952
+
953
+ // Update the preview layer frame to match the preview view
954
+ self.cameraController.previewLayer?.frame = frame
955
+ }
817
956
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/camera-preview",
3
- "version": "7.4.0-beta.2",
3
+ "version": "7.4.0-beta.4",
4
4
  "description": "Camera preview",
5
5
  "license": "MIT",
6
6
  "repository": {