@capgo/camera-preview 7.4.0-beta.10 → 7.4.0-beta.12
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.
- package/README.md +3 -3
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +281 -133
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +2028 -1467
- package/android/src/main/java/com/ahm/capacitor/camera/preview/GridOverlayView.java +71 -58
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraDevice.java +55 -46
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraLens.java +61 -52
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraSessionConfiguration.java +151 -72
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/LensInfo.java +29 -23
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/ZoomFactors.java +24 -23
- package/dist/docs.json +3 -3
- package/dist/esm/definitions.d.ts +3 -3
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +2 -2
- package/dist/esm/web.js +51 -34
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +51 -34
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +51 -34
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreview/CameraController.swift +212 -274
- package/ios/Sources/CapgoCameraPreview/Plugin.swift +467 -390
- package/package.json +1 -1
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import Foundation
|
|
2
|
-
import Capacitor
|
|
3
2
|
import AVFoundation
|
|
4
3
|
import Photos
|
|
4
|
+
import Capacitor
|
|
5
5
|
import CoreImage
|
|
6
6
|
import CoreLocation
|
|
7
7
|
import MobileCoreServices
|
|
8
8
|
|
|
9
|
-
|
|
10
9
|
extension UIWindow {
|
|
11
10
|
static var isLandscape: Bool {
|
|
12
11
|
if #available(iOS 13.0, *) {
|
|
@@ -90,42 +89,37 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
90
89
|
var currentLocation: CLLocation?
|
|
91
90
|
private var aspectRatio: String?
|
|
92
91
|
private var gridMode: String = "none"
|
|
92
|
+
private var permissionCallID: String?
|
|
93
|
+
private var waitingForLocation: Bool = false
|
|
93
94
|
|
|
94
95
|
// MARK: - Transparency Methods
|
|
95
96
|
|
|
96
|
-
|
|
97
97
|
private func makeWebViewTransparent() {
|
|
98
98
|
guard let webView = self.webView else { return }
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
100
|
+
DispatchQueue.main.async {
|
|
101
|
+
// Define a recursive function to traverse the view hierarchy
|
|
102
|
+
func makeSubviewsTransparent(_ view: UIView) {
|
|
103
|
+
// Set the background color to clear
|
|
104
|
+
view.backgroundColor = .clear
|
|
105
|
+
|
|
106
|
+
// Recurse for all subviews
|
|
107
|
+
for subview in view.subviews {
|
|
108
|
+
makeSubviewsTransparent(subview)
|
|
109
|
+
}
|
|
110
110
|
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
// Set the main webView to be transparent
|
|
115
|
-
webView.isOpaque = false
|
|
116
|
-
webView.backgroundColor = .clear
|
|
117
111
|
|
|
112
|
+
// Set the main webView to be transparent
|
|
113
|
+
webView.isOpaque = false
|
|
114
|
+
webView.backgroundColor = .clear
|
|
118
115
|
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
// Recursively make all subviews transparent
|
|
117
|
+
makeSubviewsTransparent(webView)
|
|
121
118
|
|
|
119
|
+
// Also ensure the webview's container is transparent
|
|
120
|
+
webView.superview?.backgroundColor = .clear
|
|
122
121
|
|
|
123
|
-
|
|
124
|
-
webView.superview?.backgroundColor = .clear
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
// Force a layout pass to apply changes
|
|
128
|
-
DispatchQueue.main.async {
|
|
122
|
+
// Force a layout pass to apply changes
|
|
129
123
|
webView.setNeedsLayout()
|
|
130
124
|
webView.layoutIfNeeded()
|
|
131
125
|
}
|
|
@@ -150,7 +144,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
150
144
|
// Manual positioning - use original rotation logic with no animation
|
|
151
145
|
CATransaction.begin()
|
|
152
146
|
CATransaction.setDisableActions(true)
|
|
153
|
-
|
|
147
|
+
|
|
154
148
|
if UIWindow.isLandscape {
|
|
155
149
|
previewView.frame = CGRect(x: posY, y: posX, width: max(height, width), height: min(height, width))
|
|
156
150
|
self.cameraController.previewLayer?.frame = previewView.bounds
|
|
@@ -160,7 +154,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
160
154
|
previewView.frame = CGRect(x: posX, y: posY, width: min(height, width), height: max(height, width))
|
|
161
155
|
self.cameraController.previewLayer?.frame = previewView.bounds
|
|
162
156
|
}
|
|
163
|
-
|
|
157
|
+
|
|
164
158
|
CATransaction.commit()
|
|
165
159
|
}
|
|
166
160
|
|
|
@@ -202,65 +196,67 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
202
196
|
call.reject("camera not started")
|
|
203
197
|
return
|
|
204
198
|
}
|
|
205
|
-
|
|
199
|
+
|
|
206
200
|
guard let newAspectRatio = call.getString("aspectRatio") else {
|
|
207
201
|
call.reject("aspectRatio parameter is required")
|
|
208
202
|
return
|
|
209
203
|
}
|
|
210
|
-
|
|
204
|
+
|
|
211
205
|
self.aspectRatio = newAspectRatio
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
let
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
206
|
+
|
|
207
|
+
DispatchQueue.main.async {
|
|
208
|
+
// When aspect ratio changes, calculate maximum size possible from current position
|
|
209
|
+
if let posX = self.posX, let posY = self.posY {
|
|
210
|
+
let webViewWidth = self.webView?.frame.width ?? UIScreen.main.bounds.width
|
|
211
|
+
let webViewHeight = self.webView?.frame.height ?? UIScreen.main.bounds.height
|
|
212
|
+
let paddingBottom = self.paddingBottom ?? 0
|
|
213
|
+
|
|
214
|
+
// Calculate available space from current position
|
|
215
|
+
let availableWidth: CGFloat
|
|
216
|
+
let availableHeight: CGFloat
|
|
217
|
+
|
|
218
|
+
if posX == -1 || posY == -1 {
|
|
219
|
+
// Auto-centering mode - use full dimensions
|
|
220
|
+
availableWidth = webViewWidth
|
|
221
|
+
availableHeight = webViewHeight - paddingBottom
|
|
222
|
+
} else {
|
|
223
|
+
// Manual positioning - calculate remaining space
|
|
224
|
+
availableWidth = webViewWidth - posX
|
|
225
|
+
availableHeight = webViewHeight - posY - paddingBottom
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Parse aspect ratio - convert to portrait orientation for camera use
|
|
229
|
+
let ratioParts = newAspectRatio.split(separator: ":").map { Double($0) ?? 1.0 }
|
|
230
|
+
// For camera, we want portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
|
|
231
|
+
let ratio = ratioParts[1] / ratioParts[0]
|
|
232
|
+
|
|
233
|
+
// Calculate maximum size that fits the aspect ratio in available space
|
|
234
|
+
let maxWidthByHeight = availableHeight * CGFloat(ratio)
|
|
235
|
+
let maxHeightByWidth = availableWidth / CGFloat(ratio)
|
|
236
|
+
|
|
237
|
+
if maxWidthByHeight <= availableWidth {
|
|
238
|
+
// Height is the limiting factor
|
|
239
|
+
self.width = maxWidthByHeight
|
|
240
|
+
self.height = availableHeight
|
|
241
|
+
} else {
|
|
242
|
+
// Width is the limiting factor
|
|
243
|
+
self.width = availableWidth
|
|
244
|
+
self.height = maxHeightByWidth
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
print("[CameraPreview] Aspect ratio changed to \(newAspectRatio), new size: \(self.width!)x\(self.height!)")
|
|
250
248
|
}
|
|
251
|
-
|
|
252
|
-
|
|
249
|
+
|
|
250
|
+
self.updateCameraFrame()
|
|
251
|
+
|
|
252
|
+
// Return the actual preview bounds
|
|
253
|
+
var result = JSObject()
|
|
254
|
+
result["x"] = Double(self.previewView.frame.origin.x)
|
|
255
|
+
result["y"] = Double(self.previewView.frame.origin.y)
|
|
256
|
+
result["width"] = Double(self.previewView.frame.width)
|
|
257
|
+
result["height"] = Double(self.previewView.frame.height)
|
|
258
|
+
call.resolve(result)
|
|
253
259
|
}
|
|
254
|
-
|
|
255
|
-
self.updateCameraFrame()
|
|
256
|
-
|
|
257
|
-
// Return the actual preview bounds
|
|
258
|
-
var result = JSObject()
|
|
259
|
-
result["x"] = Double(self.previewView.frame.origin.x)
|
|
260
|
-
result["y"] = Double(self.previewView.frame.origin.y)
|
|
261
|
-
result["width"] = Double(self.previewView.frame.width)
|
|
262
|
-
result["height"] = Double(self.previewView.frame.height)
|
|
263
|
-
call.resolve(result)
|
|
264
260
|
}
|
|
265
261
|
|
|
266
262
|
@objc func getAspectRatio(_ call: CAPPluginCall) {
|
|
@@ -312,7 +308,6 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
312
308
|
}
|
|
313
309
|
}
|
|
314
310
|
|
|
315
|
-
|
|
316
311
|
@objc func appWillEnterForeground() {
|
|
317
312
|
if self.isInitialized {
|
|
318
313
|
DispatchQueue.main.async {
|
|
@@ -464,7 +459,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
464
459
|
|
|
465
460
|
print("[CameraPreview] Camera start parameters - aspectRatio: \(String(describing: self.aspectRatio)), gridMode: \(self.gridMode)")
|
|
466
461
|
print("[CameraPreview] Screen dimensions: \(UIScreen.main.bounds.size)")
|
|
467
|
-
print("[CameraPreview] Final frame dimensions - width: \(self.width), height: \(self.height), x: \(self.posX), y: \(self.posY)")
|
|
462
|
+
print("[CameraPreview] Final frame dimensions - width: \(String(describing: self.width)), height: \(String(describing: self.height)), x: \(String(describing: self.posX)), y: \(String(describing: self.posY))")
|
|
468
463
|
|
|
469
464
|
AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted: Bool) in
|
|
470
465
|
guard granted else {
|
|
@@ -472,16 +467,21 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
472
467
|
return
|
|
473
468
|
}
|
|
474
469
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
470
|
+
if self.cameraController.captureSession?.isRunning ?? false {
|
|
471
|
+
call.reject("camera already started")
|
|
472
|
+
} else {
|
|
473
|
+
// Pre-initialize session if not already done
|
|
474
|
+
if self.cameraController.captureSession == nil {
|
|
475
|
+
self.cameraController.prepareFullSession()
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
self.cameraController.prepare(cameraPosition: self.cameraPosition, deviceId: deviceId, disableAudio: self.disableAudio, cameraMode: cameraMode, aspectRatio: self.aspectRatio) {error in
|
|
479
|
+
if let error = error {
|
|
480
|
+
print(error)
|
|
481
|
+
call.reject(error.localizedDescription)
|
|
482
|
+
return
|
|
483
|
+
}
|
|
484
|
+
DispatchQueue.main.async {
|
|
485
485
|
self.completeStartCamera(call: call)
|
|
486
486
|
}
|
|
487
487
|
}
|
|
@@ -489,25 +489,6 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
489
489
|
})
|
|
490
490
|
}
|
|
491
491
|
|
|
492
|
-
override public func load() {
|
|
493
|
-
super.load()
|
|
494
|
-
// Initialize camera session in background for faster startup
|
|
495
|
-
prepareBackgroundCamera()
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
private func prepareBackgroundCamera() {
|
|
499
|
-
DispatchQueue.global(qos: .background).async {
|
|
500
|
-
AVCaptureDevice.requestAccess(for: .video) { granted in
|
|
501
|
-
guard granted else { return }
|
|
502
|
-
|
|
503
|
-
// Pre-initialize camera controller for faster startup
|
|
504
|
-
DispatchQueue.main.async {
|
|
505
|
-
self.cameraController.prepareBasicSession()
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
492
|
private func completeStartCamera(call: CAPPluginCall) {
|
|
512
493
|
// Create and configure the preview view first
|
|
513
494
|
self.updateCameraFrame()
|
|
@@ -557,74 +538,44 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
557
538
|
return
|
|
558
539
|
}
|
|
559
540
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
call.reject("Camera controller deallocated")
|
|
563
|
-
return
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
// Disable user interaction during flip
|
|
567
|
-
self.previewView.isUserInteractionEnabled = false
|
|
568
|
-
|
|
569
|
-
// Perform camera switch on background thread
|
|
570
|
-
DispatchQueue.global(qos: .userInitiated).async {
|
|
571
|
-
var retryCount = 0
|
|
572
|
-
let maxRetries = 3
|
|
573
|
-
|
|
574
|
-
func attemptFlip() {
|
|
575
|
-
do {
|
|
576
|
-
try self.cameraController.switchCameras()
|
|
577
|
-
|
|
578
|
-
DispatchQueue.main.async {
|
|
579
|
-
// Update preview layer frame without animation
|
|
580
|
-
CATransaction.begin()
|
|
581
|
-
CATransaction.setDisableActions(true)
|
|
582
|
-
self.cameraController.previewLayer?.frame = self.previewView.bounds
|
|
583
|
-
self.cameraController.previewLayer?.videoGravity = .resizeAspectFill
|
|
584
|
-
CATransaction.commit()
|
|
585
|
-
|
|
586
|
-
self.previewView.isUserInteractionEnabled = true
|
|
541
|
+
// Disable user interaction during flip
|
|
542
|
+
self.previewView.isUserInteractionEnabled = false
|
|
587
543
|
|
|
544
|
+
do {
|
|
545
|
+
try self.cameraController.switchCameras()
|
|
588
546
|
|
|
589
|
-
|
|
590
|
-
|
|
547
|
+
// Update preview layer frame without animation
|
|
548
|
+
CATransaction.begin()
|
|
549
|
+
CATransaction.setDisableActions(true)
|
|
550
|
+
self.cameraController.previewLayer?.frame = self.previewView.bounds
|
|
551
|
+
self.cameraController.previewLayer?.videoGravity = .resizeAspectFill
|
|
552
|
+
CATransaction.commit()
|
|
591
553
|
|
|
554
|
+
self.previewView.isUserInteractionEnabled = true
|
|
592
555
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
} catch {
|
|
596
|
-
retryCount += 1
|
|
597
|
-
|
|
598
|
-
if retryCount < maxRetries {
|
|
599
|
-
DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 0.5) {
|
|
600
|
-
attemptFlip()
|
|
601
|
-
}
|
|
602
|
-
} else {
|
|
603
|
-
DispatchQueue.main.async {
|
|
604
|
-
self.previewView.isUserInteractionEnabled = true
|
|
605
|
-
print("Failed to flip camera after \(maxRetries) attempts: \(error.localizedDescription)")
|
|
606
|
-
call.reject("Failed to flip camera: \(error.localizedDescription)")
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
}
|
|
556
|
+
// Ensure webview remains transparent after flip
|
|
557
|
+
self.makeWebViewTransparent()
|
|
611
558
|
|
|
612
|
-
|
|
613
|
-
|
|
559
|
+
call.resolve()
|
|
560
|
+
} catch {
|
|
561
|
+
self.previewView.isUserInteractionEnabled = true
|
|
562
|
+
print("Failed to flip camera: \(error.localizedDescription)")
|
|
563
|
+
call.reject("Failed to flip camera: \(error.localizedDescription)")
|
|
614
564
|
}
|
|
615
565
|
}
|
|
616
566
|
|
|
617
567
|
@objc func stop(_ call: CAPPluginCall) {
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
}
|
|
568
|
+
if self.isInitializing {
|
|
569
|
+
call.reject("cannot stop camera while initialization is in progress")
|
|
570
|
+
return
|
|
571
|
+
}
|
|
572
|
+
if !self.isInitialized {
|
|
573
|
+
call.reject("camera not initialized")
|
|
574
|
+
return
|
|
575
|
+
}
|
|
627
576
|
|
|
577
|
+
// UI operations must be on main thread
|
|
578
|
+
DispatchQueue.main.async {
|
|
628
579
|
// Always attempt to stop and clean up, regardless of captureSession state
|
|
629
580
|
self.cameraController.removeGridOverlay()
|
|
630
581
|
if let previewView = self.previewView {
|
|
@@ -637,7 +588,6 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
637
588
|
self.isInitializing = false
|
|
638
589
|
self.cameraController.cleanup()
|
|
639
590
|
|
|
640
|
-
|
|
641
591
|
// Remove notification observers
|
|
642
592
|
NotificationCenter.default.removeObserver(self)
|
|
643
593
|
|
|
@@ -656,75 +606,138 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
656
606
|
}
|
|
657
607
|
|
|
658
608
|
@objc func capture(_ call: CAPPluginCall) {
|
|
659
|
-
|
|
609
|
+
let withExifLocation = call.getBool("withExifLocation", false)
|
|
610
|
+
print("[CameraPreview] capture called, withExifLocation: \(withExifLocation)")
|
|
611
|
+
|
|
612
|
+
if withExifLocation {
|
|
613
|
+
print("[CameraPreview] Location required for capture")
|
|
614
|
+
|
|
615
|
+
// Check location services before main thread dispatch
|
|
616
|
+
guard CLLocationManager.locationServicesEnabled() else {
|
|
617
|
+
print("[CameraPreview] Location services are disabled")
|
|
618
|
+
call.reject("Location services are disabled")
|
|
619
|
+
return
|
|
620
|
+
}
|
|
660
621
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
if withExifLocation {
|
|
668
|
-
self.locationManager = CLLocationManager()
|
|
669
|
-
self.locationManager?.delegate = self
|
|
670
|
-
self.locationManager?.requestWhenInUseAuthorization()
|
|
671
|
-
self.locationManager?.startUpdatingLocation()
|
|
622
|
+
// Check if Info.plist has the required key
|
|
623
|
+
guard Bundle.main.object(forInfoDictionaryKey: "NSLocationWhenInUseUsageDescription") != nil else {
|
|
624
|
+
print("[CameraPreview] ERROR: NSLocationWhenInUseUsageDescription key missing from Info.plist")
|
|
625
|
+
call.reject("NSLocationWhenInUseUsageDescription key missing from Info.plist. Add this key with a description of how your app uses location.")
|
|
626
|
+
return
|
|
672
627
|
}
|
|
673
628
|
|
|
674
|
-
|
|
629
|
+
// Ensure location manager setup happens on main thread
|
|
630
|
+
DispatchQueue.main.async {
|
|
631
|
+
if self.locationManager == nil {
|
|
632
|
+
print("[CameraPreview] Creating location manager on main thread")
|
|
633
|
+
self.locationManager = CLLocationManager()
|
|
634
|
+
self.locationManager?.delegate = self
|
|
635
|
+
self.locationManager?.desiredAccuracy = kCLLocationAccuracyBest
|
|
636
|
+
print("[CameraPreview] Location manager created, delegate set to: \(self)")
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Check current authorization status
|
|
640
|
+
let currentStatus = self.locationManager?.authorizationStatus ?? .notDetermined
|
|
641
|
+
print("[CameraPreview] Current authorization status: \(currentStatus.rawValue)")
|
|
642
|
+
|
|
643
|
+
switch currentStatus {
|
|
644
|
+
case .authorizedWhenInUse, .authorizedAlways:
|
|
645
|
+
// Already authorized, get location and capture
|
|
646
|
+
print("[CameraPreview] Already authorized, getting location immediately")
|
|
647
|
+
self.getCurrentLocation { _ in
|
|
648
|
+
self.performCapture(call: call)
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
case .denied, .restricted:
|
|
652
|
+
// Permission denied
|
|
653
|
+
print("[CameraPreview] Location permission denied")
|
|
654
|
+
call.reject("Location permission denied")
|
|
655
|
+
|
|
656
|
+
case .notDetermined:
|
|
657
|
+
// Need to request permission
|
|
658
|
+
print("[CameraPreview] Location permission not determined, requesting...")
|
|
659
|
+
// Save the call for the delegate callback
|
|
660
|
+
print("[CameraPreview] Saving call for location authorization flow")
|
|
661
|
+
self.bridge?.saveCall(call)
|
|
662
|
+
self.permissionCallID = call.callbackId
|
|
663
|
+
self.waitingForLocation = true
|
|
664
|
+
|
|
665
|
+
// Request authorization - this will trigger locationManagerDidChangeAuthorization
|
|
666
|
+
print("[CameraPreview] Requesting location authorization...")
|
|
667
|
+
self.locationManager?.requestWhenInUseAuthorization()
|
|
668
|
+
// The delegate will handle the rest
|
|
669
|
+
|
|
670
|
+
@unknown default:
|
|
671
|
+
print("[CameraPreview] Unknown authorization status")
|
|
672
|
+
call.reject("Unknown location permission status")
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
} else {
|
|
676
|
+
print("[CameraPreview] No location required, performing capture directly")
|
|
677
|
+
self.performCapture(call: call)
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
private func performCapture(call: CAPPluginCall) {
|
|
682
|
+
print("[CameraPreview] performCapture called")
|
|
683
|
+
let quality = call.getFloat("quality", 85)
|
|
684
|
+
let saveToGallery = call.getBool("saveToGallery", false)
|
|
685
|
+
let withExifLocation = call.getBool("withExifLocation", false)
|
|
686
|
+
let width = call.getInt("width")
|
|
687
|
+
let height = call.getInt("height")
|
|
688
|
+
|
|
689
|
+
print("[CameraPreview] Capture params - quality: \(quality), saveToGallery: \(saveToGallery), withExifLocation: \(withExifLocation), width: \(width ?? -1), height: \(height ?? -1)")
|
|
690
|
+
print("[CameraPreview] Current location: \(self.currentLocation?.description ?? "nil")")
|
|
691
|
+
|
|
692
|
+
self.cameraController.captureImage(width: width, height: height, quality: quality, gpsLocation: self.currentLocation) { (image, error) in
|
|
693
|
+
print("[CameraPreview] captureImage callback received")
|
|
694
|
+
DispatchQueue.main.async {
|
|
695
|
+
print("[CameraPreview] Processing capture on main thread")
|
|
675
696
|
if let error = error {
|
|
697
|
+
print("[CameraPreview] Capture error: \(error.localizedDescription)")
|
|
676
698
|
call.reject(error.localizedDescription)
|
|
677
699
|
return
|
|
678
700
|
}
|
|
679
701
|
|
|
680
|
-
var gallerySuccess = true
|
|
681
|
-
var galleryError: String?
|
|
682
|
-
|
|
683
|
-
let group = DispatchGroup()
|
|
684
|
-
|
|
685
|
-
group.notify(queue: .main) {
|
|
686
702
|
guard let imageDataWithExif = self.createImageDataWithExif(from: image!, quality: Int(quality), location: withExifLocation ? self.currentLocation : nil) else {
|
|
703
|
+
print("[CameraPreview] Failed to create image data with EXIF")
|
|
687
704
|
call.reject("Failed to create image data with EXIF")
|
|
688
705
|
return
|
|
689
706
|
}
|
|
690
707
|
|
|
708
|
+
print("[CameraPreview] Image data created, size: \(imageDataWithExif.count) bytes")
|
|
709
|
+
|
|
691
710
|
if saveToGallery {
|
|
692
|
-
|
|
711
|
+
print("[CameraPreview] Saving to gallery...")
|
|
693
712
|
self.saveImageDataToGallery(imageData: imageDataWithExif) { success, error in
|
|
694
|
-
|
|
695
|
-
if !success {
|
|
696
|
-
galleryError = error?.localizedDescription ?? "Unknown error"
|
|
697
|
-
print("CameraPreview: Error saving image to gallery: \(galleryError!)")
|
|
698
|
-
}
|
|
699
|
-
group.leave()
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
group.notify(queue: .main) {
|
|
713
|
+
print("[CameraPreview] Save to gallery completed, success: \(success), error: \(error?.localizedDescription ?? "none")")
|
|
703
714
|
let exifData = self.getExifData(from: imageDataWithExif)
|
|
704
715
|
let base64Image = imageDataWithExif.base64EncodedString()
|
|
705
716
|
|
|
706
717
|
var result = JSObject()
|
|
707
718
|
result["value"] = base64Image
|
|
708
719
|
result["exif"] = exifData
|
|
709
|
-
result["gallerySaved"] =
|
|
710
|
-
if !
|
|
711
|
-
result["galleryError"] = error
|
|
720
|
+
result["gallerySaved"] = success
|
|
721
|
+
if !success, let error = error {
|
|
722
|
+
result["galleryError"] = error.localizedDescription
|
|
712
723
|
}
|
|
713
|
-
|
|
724
|
+
|
|
725
|
+
print("[CameraPreview] Resolving capture call with gallery save")
|
|
714
726
|
call.resolve(result)
|
|
715
727
|
}
|
|
716
728
|
} else {
|
|
729
|
+
print("[CameraPreview] Not saving to gallery, returning image data")
|
|
717
730
|
let exifData = self.getExifData(from: imageDataWithExif)
|
|
718
731
|
let base64Image = imageDataWithExif.base64EncodedString()
|
|
719
732
|
|
|
720
733
|
var result = JSObject()
|
|
721
734
|
result["value"] = base64Image
|
|
722
735
|
result["exif"] = exifData
|
|
723
|
-
|
|
736
|
+
|
|
737
|
+
print("[CameraPreview] Resolving capture call")
|
|
724
738
|
call.resolve(result)
|
|
725
739
|
}
|
|
726
740
|
}
|
|
727
|
-
}
|
|
728
741
|
}
|
|
729
742
|
}
|
|
730
743
|
|
|
@@ -735,7 +748,6 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
735
748
|
return [:]
|
|
736
749
|
}
|
|
737
750
|
|
|
738
|
-
|
|
739
751
|
var exifData = JSObject()
|
|
740
752
|
for (key, value) in exifDict {
|
|
741
753
|
// Convert value to JSValue-compatible type
|
|
@@ -755,7 +767,6 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
755
767
|
}
|
|
756
768
|
}
|
|
757
769
|
|
|
758
|
-
|
|
759
770
|
return exifData
|
|
760
771
|
}
|
|
761
772
|
|
|
@@ -763,26 +774,26 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
763
774
|
guard let originalImageData = image.jpegData(compressionQuality: CGFloat(Double(quality) / 100.0)) else {
|
|
764
775
|
return nil
|
|
765
776
|
}
|
|
766
|
-
|
|
777
|
+
|
|
767
778
|
guard let imageSource = CGImageSourceCreateWithData(originalImageData as CFData, nil),
|
|
768
779
|
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [String: Any],
|
|
769
780
|
let cgImage = image.cgImage else {
|
|
770
781
|
return originalImageData
|
|
771
782
|
}
|
|
772
|
-
|
|
783
|
+
|
|
773
784
|
let mutableData = NSMutableData()
|
|
774
785
|
guard let destination = CGImageDestinationCreateWithData(mutableData, kUTTypeJPEG, 1, nil) else {
|
|
775
786
|
return originalImageData
|
|
776
787
|
}
|
|
777
|
-
|
|
788
|
+
|
|
778
789
|
var finalProperties = imageProperties
|
|
779
|
-
|
|
790
|
+
|
|
780
791
|
// Add GPS location if available
|
|
781
792
|
if let location = location {
|
|
782
793
|
let formatter = DateFormatter()
|
|
783
794
|
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
|
|
784
795
|
formatter.timeZone = TimeZone(abbreviation: "UTC")
|
|
785
|
-
|
|
796
|
+
|
|
786
797
|
let gpsDict: [String: Any] = [
|
|
787
798
|
kCGImagePropertyGPSLatitude as String: abs(location.coordinate.latitude),
|
|
788
799
|
kCGImagePropertyGPSLatitudeRef as String: location.coordinate.latitude >= 0 ? "N" : "S",
|
|
@@ -792,79 +803,53 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
792
803
|
kCGImagePropertyGPSAltitude as String: location.altitude,
|
|
793
804
|
kCGImagePropertyGPSAltitudeRef as String: location.altitude >= 0 ? 0 : 1
|
|
794
805
|
]
|
|
795
|
-
|
|
806
|
+
|
|
796
807
|
finalProperties[kCGImagePropertyGPSDictionary as String] = gpsDict
|
|
797
808
|
}
|
|
798
|
-
|
|
799
|
-
//
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
var exifDict = finalProperties[kCGImagePropertyExifDictionary as String] as? [String: Any] ?? [:]
|
|
806
|
-
|
|
807
|
-
// Add focal length (in mm)
|
|
808
|
-
exifDict[kCGImagePropertyExifFocalLength as String] = lensInfo.focalLength
|
|
809
|
-
|
|
810
|
-
// Add digital zoom ratio
|
|
811
|
-
let digitalZoom = Float(currentZoom.current) / lensInfo.baseZoomRatio
|
|
812
|
-
exifDict[kCGImagePropertyExifDigitalZoomRatio as String] = digitalZoom
|
|
813
|
-
|
|
814
|
-
// Add lens model info
|
|
815
|
-
exifDict[kCGImagePropertyExifLensModel as String] = lensInfo.deviceType
|
|
816
|
-
|
|
817
|
-
finalProperties[kCGImagePropertyExifDictionary as String] = exifDict
|
|
818
|
-
|
|
819
|
-
// Create or update TIFF dictionary for device info
|
|
820
|
-
var tiffDict = finalProperties[kCGImagePropertyTIFFDictionary as String] as? [String: Any] ?? [:]
|
|
821
|
-
tiffDict[kCGImagePropertyTIFFMake as String] = "Apple"
|
|
822
|
-
tiffDict[kCGImagePropertyTIFFModel as String] = UIDevice.current.model
|
|
823
|
-
finalProperties[kCGImagePropertyTIFFDictionary as String] = tiffDict
|
|
824
|
-
|
|
825
|
-
} catch {
|
|
826
|
-
print("CameraPreview: Failed to get lens information: \(error)")
|
|
827
|
-
}
|
|
828
|
-
|
|
809
|
+
|
|
810
|
+
// Create or update TIFF dictionary for device info
|
|
811
|
+
var tiffDict = finalProperties[kCGImagePropertyTIFFDictionary as String] as? [String: Any] ?? [:]
|
|
812
|
+
tiffDict[kCGImagePropertyTIFFMake as String] = "Apple"
|
|
813
|
+
tiffDict[kCGImagePropertyTIFFModel as String] = UIDevice.current.model
|
|
814
|
+
finalProperties[kCGImagePropertyTIFFDictionary as String] = tiffDict
|
|
815
|
+
|
|
829
816
|
CGImageDestinationAddImage(destination, cgImage, finalProperties as CFDictionary)
|
|
830
|
-
|
|
817
|
+
|
|
831
818
|
if CGImageDestinationFinalize(destination) {
|
|
832
819
|
return mutableData as Data
|
|
833
820
|
}
|
|
834
|
-
|
|
821
|
+
|
|
835
822
|
return originalImageData
|
|
836
823
|
}
|
|
837
824
|
|
|
838
825
|
@objc func captureSample(_ call: CAPPluginCall) {
|
|
839
|
-
|
|
840
|
-
let quality: Int? = call.getInt("quality", 85)
|
|
826
|
+
let quality: Int? = call.getInt("quality", 85)
|
|
841
827
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
828
|
+
self.cameraController.captureSample { image, error in
|
|
829
|
+
guard let image = image else {
|
|
830
|
+
print("Image capture error: \(String(describing: error))")
|
|
831
|
+
call.reject("Image capture error: \(String(describing: error))")
|
|
832
|
+
return
|
|
833
|
+
}
|
|
848
834
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
835
|
+
let imageData: Data?
|
|
836
|
+
if self.cameraPosition == "front" {
|
|
837
|
+
let flippedImage = image.withHorizontallyFlippedOrientation()
|
|
838
|
+
imageData = flippedImage.jpegData(compressionQuality: CGFloat(quality!/100))
|
|
839
|
+
} else {
|
|
840
|
+
imageData = image.jpegData(compressionQuality: CGFloat(quality!/100))
|
|
841
|
+
}
|
|
856
842
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
}
|
|
843
|
+
if self.storeToFile == false {
|
|
844
|
+
let imageBase64 = imageData?.base64EncodedString()
|
|
845
|
+
call.resolve(["value": imageBase64!])
|
|
846
|
+
} else {
|
|
847
|
+
do {
|
|
848
|
+
let fileUrl = self.getTempFilePath()
|
|
849
|
+
try imageData?.write(to: fileUrl)
|
|
850
|
+
call.resolve(["value": fileUrl.absoluteString])
|
|
851
|
+
} catch {
|
|
852
|
+
call.reject("Error writing image to file")
|
|
868
853
|
}
|
|
869
854
|
}
|
|
870
855
|
}
|
|
@@ -919,31 +904,27 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
919
904
|
}
|
|
920
905
|
|
|
921
906
|
@objc func startRecordVideo(_ call: CAPPluginCall) {
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
call.reject(error.localizedDescription)
|
|
928
|
-
}
|
|
907
|
+
do {
|
|
908
|
+
try self.cameraController.captureVideo()
|
|
909
|
+
call.resolve()
|
|
910
|
+
} catch {
|
|
911
|
+
call.reject(error.localizedDescription)
|
|
929
912
|
}
|
|
930
913
|
}
|
|
931
914
|
|
|
932
915
|
@objc func stopRecordVideo(_ call: CAPPluginCall) {
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
call.reject("Video capture error")
|
|
939
|
-
return
|
|
940
|
-
}
|
|
941
|
-
call.reject(error.localizedDescription)
|
|
916
|
+
self.cameraController.stopRecording { (fileURL, error) in
|
|
917
|
+
guard let fileURL = fileURL else {
|
|
918
|
+
print(error ?? "Video capture error")
|
|
919
|
+
guard let error = error else {
|
|
920
|
+
call.reject("Video capture error")
|
|
942
921
|
return
|
|
943
922
|
}
|
|
944
|
-
|
|
945
|
-
|
|
923
|
+
call.reject(error.localizedDescription)
|
|
924
|
+
return
|
|
946
925
|
}
|
|
926
|
+
|
|
927
|
+
call.resolve(["videoFilePath": fileURL.absoluteString])
|
|
947
928
|
}
|
|
948
929
|
}
|
|
949
930
|
|
|
@@ -975,21 +956,19 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
975
956
|
for device in session.devices {
|
|
976
957
|
var lenses: [[String: Any]] = []
|
|
977
958
|
|
|
978
|
-
|
|
979
959
|
let constituentDevices = device.isVirtualDevice ? device.constituentDevices : [device]
|
|
980
960
|
|
|
981
|
-
|
|
982
961
|
for lensDevice in constituentDevices {
|
|
983
962
|
var deviceType: String
|
|
984
963
|
switch lensDevice.deviceType {
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
964
|
+
case .builtInWideAngleCamera: deviceType = "wideAngle"
|
|
965
|
+
case .builtInUltraWideCamera: deviceType = "ultraWide"
|
|
966
|
+
case .builtInTelephotoCamera: deviceType = "telephoto"
|
|
967
|
+
case .builtInDualCamera: deviceType = "dual"
|
|
968
|
+
case .builtInDualWideCamera: deviceType = "dualWide"
|
|
969
|
+
case .builtInTripleCamera: deviceType = "triple"
|
|
970
|
+
case .builtInTrueDepthCamera: deviceType = "trueDepth"
|
|
971
|
+
default: deviceType = "unknown"
|
|
993
972
|
}
|
|
994
973
|
|
|
995
974
|
var baseZoomRatio: Float = 1.0
|
|
@@ -999,7 +978,6 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
999
978
|
baseZoomRatio = 2.0 // A common value for telephoto lenses
|
|
1000
979
|
}
|
|
1001
980
|
|
|
1002
|
-
|
|
1003
981
|
let lensInfo: [String: Any] = [
|
|
1004
982
|
"label": lensDevice.localizedName,
|
|
1005
983
|
"deviceType": deviceType,
|
|
@@ -1011,7 +989,6 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1011
989
|
lenses.append(lensInfo)
|
|
1012
990
|
}
|
|
1013
991
|
|
|
1014
|
-
|
|
1015
992
|
let deviceData: [String: Any] = [
|
|
1016
993
|
"deviceId": device.uniqueID,
|
|
1017
994
|
"label": device.localizedName,
|
|
@@ -1022,7 +999,6 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1022
999
|
"isLogical": device.isVirtualDevice
|
|
1023
1000
|
]
|
|
1024
1001
|
|
|
1025
|
-
|
|
1026
1002
|
devices.append(deviceData)
|
|
1027
1003
|
}
|
|
1028
1004
|
|
|
@@ -1039,7 +1015,6 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1039
1015
|
let zoomInfo = try self.cameraController.getZoom()
|
|
1040
1016
|
let lensInfo = try self.cameraController.getCurrentLensInfo()
|
|
1041
1017
|
|
|
1042
|
-
|
|
1043
1018
|
var minZoom = zoomInfo.min
|
|
1044
1019
|
var maxZoom = zoomInfo.max
|
|
1045
1020
|
var currentZoom = zoomInfo.current
|
|
@@ -1118,47 +1093,32 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1118
1093
|
return
|
|
1119
1094
|
}
|
|
1120
1095
|
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
call.reject("Camera controller deallocated")
|
|
1124
|
-
return
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
// Disable user interaction during device swap
|
|
1128
|
-
self.previewView.isUserInteractionEnabled = false
|
|
1129
|
-
|
|
1130
|
-
DispatchQueue.global(qos: .userInitiated).async {
|
|
1131
|
-
do {
|
|
1132
|
-
try self.cameraController.swapToDevice(deviceId: deviceId)
|
|
1096
|
+
// Disable user interaction during device swap
|
|
1097
|
+
self.previewView.isUserInteractionEnabled = false
|
|
1133
1098
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
CATransaction.begin()
|
|
1137
|
-
CATransaction.setDisableActions(true)
|
|
1138
|
-
self.cameraController.previewLayer?.frame = self.previewView.bounds
|
|
1139
|
-
self.cameraController.previewLayer?.videoGravity = .resizeAspectFill
|
|
1140
|
-
CATransaction.commit()
|
|
1141
|
-
|
|
1142
|
-
self.previewView.isUserInteractionEnabled = true
|
|
1099
|
+
do {
|
|
1100
|
+
try self.cameraController.swapToDevice(deviceId: deviceId)
|
|
1143
1101
|
|
|
1102
|
+
// Update preview layer frame without animation
|
|
1103
|
+
CATransaction.begin()
|
|
1104
|
+
CATransaction.setDisableActions(true)
|
|
1105
|
+
self.cameraController.previewLayer?.frame = self.previewView.bounds
|
|
1106
|
+
self.cameraController.previewLayer?.videoGravity = .resizeAspectFill
|
|
1107
|
+
CATransaction.commit()
|
|
1144
1108
|
|
|
1145
|
-
|
|
1146
|
-
self.makeWebViewTransparent()
|
|
1109
|
+
self.previewView.isUserInteractionEnabled = true
|
|
1147
1110
|
|
|
1111
|
+
// Ensure webview remains transparent after device switch
|
|
1112
|
+
self.makeWebViewTransparent()
|
|
1148
1113
|
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
self.previewView.isUserInteractionEnabled = true
|
|
1154
|
-
call.reject("Failed to swap to device \(deviceId): \(error.localizedDescription)")
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1114
|
+
call.resolve()
|
|
1115
|
+
} catch {
|
|
1116
|
+
self.previewView.isUserInteractionEnabled = true
|
|
1117
|
+
call.reject("Failed to swap to device \(deviceId): \(error.localizedDescription)")
|
|
1158
1118
|
}
|
|
1159
1119
|
}
|
|
1160
1120
|
|
|
1161
|
-
|
|
1121
|
+
@objc func getDeviceId(_ call: CAPPluginCall) {
|
|
1162
1122
|
guard isInitialized else {
|
|
1163
1123
|
call.reject("Camera not initialized")
|
|
1164
1124
|
return
|
|
@@ -1172,13 +1132,123 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1172
1132
|
}
|
|
1173
1133
|
}
|
|
1174
1134
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1135
|
+
// MARK: - Capacitor Permissions
|
|
1136
|
+
|
|
1137
|
+
private func requestLocationPermission(completion: @escaping (Bool) -> Void) {
|
|
1138
|
+
print("[CameraPreview] requestLocationPermission called")
|
|
1139
|
+
if self.locationManager == nil {
|
|
1140
|
+
print("[CameraPreview] Creating location manager")
|
|
1141
|
+
self.locationManager = CLLocationManager()
|
|
1142
|
+
self.locationManager?.delegate = self
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
let authStatus = self.locationManager?.authorizationStatus
|
|
1146
|
+
print("[CameraPreview] Current authorization status: \(String(describing: authStatus))")
|
|
1147
|
+
|
|
1148
|
+
switch authStatus {
|
|
1149
|
+
case .authorizedWhenInUse, .authorizedAlways:
|
|
1150
|
+
print("[CameraPreview] Location already authorized")
|
|
1151
|
+
completion(true)
|
|
1152
|
+
case .notDetermined:
|
|
1153
|
+
print("[CameraPreview] Location not determined, requesting authorization...")
|
|
1154
|
+
self.permissionCompletion = completion
|
|
1155
|
+
self.locationManager?.requestWhenInUseAuthorization()
|
|
1156
|
+
case .denied, .restricted:
|
|
1157
|
+
print("[CameraPreview] Location denied or restricted")
|
|
1158
|
+
completion(false)
|
|
1159
|
+
case .none:
|
|
1160
|
+
print("[CameraPreview] Location manager authorization status is nil")
|
|
1161
|
+
completion(false)
|
|
1162
|
+
@unknown default:
|
|
1163
|
+
print("[CameraPreview] Unknown authorization status")
|
|
1164
|
+
completion(false)
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
private var permissionCompletion: ((Bool) -> Void)?
|
|
1169
|
+
|
|
1170
|
+
public func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
|
|
1171
|
+
let status = manager.authorizationStatus
|
|
1172
|
+
print("[CameraPreview] locationManagerDidChangeAuthorization called, status: \(status.rawValue), thread: \(Thread.current)")
|
|
1173
|
+
|
|
1174
|
+
// Handle pending capture call if we have one
|
|
1175
|
+
if let callID = self.permissionCallID, self.waitingForLocation {
|
|
1176
|
+
print("[CameraPreview] Found pending capture call ID: \(callID)")
|
|
1177
|
+
|
|
1178
|
+
let handleAuthorization = {
|
|
1179
|
+
print("[CameraPreview] Getting saved call on thread: \(Thread.current)")
|
|
1180
|
+
guard let call = self.bridge?.savedCall(withID: callID) else {
|
|
1181
|
+
print("[CameraPreview] ERROR: Could not retrieve saved call")
|
|
1182
|
+
self.permissionCallID = nil
|
|
1183
|
+
self.waitingForLocation = false
|
|
1184
|
+
return
|
|
1185
|
+
}
|
|
1186
|
+
print("[CameraPreview] Successfully retrieved saved call")
|
|
1187
|
+
|
|
1188
|
+
switch status {
|
|
1189
|
+
case .authorizedWhenInUse, .authorizedAlways:
|
|
1190
|
+
print("[CameraPreview] Location authorized, getting location for capture")
|
|
1191
|
+
self.getCurrentLocation { _ in
|
|
1192
|
+
self.performCapture(call: call)
|
|
1193
|
+
self.bridge?.releaseCall(call)
|
|
1194
|
+
self.permissionCallID = nil
|
|
1195
|
+
self.waitingForLocation = false
|
|
1196
|
+
}
|
|
1197
|
+
case .denied, .restricted:
|
|
1198
|
+
print("[CameraPreview] Location denied, rejecting capture")
|
|
1199
|
+
call.reject("Location permission denied")
|
|
1200
|
+
self.bridge?.releaseCall(call)
|
|
1201
|
+
self.permissionCallID = nil
|
|
1202
|
+
self.waitingForLocation = false
|
|
1203
|
+
case .notDetermined:
|
|
1204
|
+
print("[CameraPreview] Authorization not determined yet")
|
|
1205
|
+
// Don't do anything, wait for user response
|
|
1206
|
+
@unknown default:
|
|
1207
|
+
print("[CameraPreview] Unknown status, rejecting capture")
|
|
1208
|
+
call.reject("Unknown location permission status")
|
|
1209
|
+
self.bridge?.releaseCall(call)
|
|
1210
|
+
self.permissionCallID = nil
|
|
1211
|
+
self.waitingForLocation = false
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// Check if we're already on main thread
|
|
1216
|
+
if Thread.isMainThread {
|
|
1217
|
+
print("[CameraPreview] Already on main thread")
|
|
1218
|
+
handleAuthorization()
|
|
1219
|
+
} else {
|
|
1220
|
+
print("[CameraPreview] Not on main thread, dispatching")
|
|
1221
|
+
DispatchQueue.main.async(execute: handleAuthorization)
|
|
1222
|
+
}
|
|
1223
|
+
} else {
|
|
1224
|
+
print("[CameraPreview] No pending capture call")
|
|
1225
|
+
}
|
|
1178
1226
|
}
|
|
1179
1227
|
|
|
1180
1228
|
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
|
1181
|
-
print("CameraPreview
|
|
1229
|
+
print("[CameraPreview] locationManager didFailWithError: \(error.localizedDescription)")
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
private func getCurrentLocation(completion: @escaping (CLLocation?) -> Void) {
|
|
1233
|
+
print("[CameraPreview] getCurrentLocation called")
|
|
1234
|
+
self.locationCompletion = completion
|
|
1235
|
+
self.locationManager?.startUpdatingLocation()
|
|
1236
|
+
print("[CameraPreview] Started updating location")
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
private var locationCompletion: ((CLLocation?) -> Void)?
|
|
1240
|
+
|
|
1241
|
+
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
|
1242
|
+
print("[CameraPreview] locationManager didUpdateLocations called, locations count: \(locations.count)")
|
|
1243
|
+
self.currentLocation = locations.last
|
|
1244
|
+
if let completion = locationCompletion {
|
|
1245
|
+
print("[CameraPreview] Calling location completion with location: \(self.currentLocation?.description ?? "nil")")
|
|
1246
|
+
self.locationManager?.stopUpdatingLocation()
|
|
1247
|
+
completion(self.currentLocation)
|
|
1248
|
+
locationCompletion = nil
|
|
1249
|
+
} else {
|
|
1250
|
+
print("[CameraPreview] No location completion handler found")
|
|
1251
|
+
}
|
|
1182
1252
|
}
|
|
1183
1253
|
|
|
1184
1254
|
private func saveImageDataToGallery(imageData: Data, completion: @escaping (Bool, Error?) -> Void) {
|
|
@@ -1190,20 +1260,18 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1190
1260
|
completion(false, error)
|
|
1191
1261
|
return
|
|
1192
1262
|
}
|
|
1193
|
-
|
|
1263
|
+
|
|
1194
1264
|
let status = PHPhotoLibrary.authorizationStatus()
|
|
1195
|
-
|
|
1265
|
+
|
|
1196
1266
|
switch status {
|
|
1197
1267
|
case .authorized:
|
|
1198
1268
|
performSaveDataToGallery(imageData: imageData, completion: completion)
|
|
1199
1269
|
case .notDetermined:
|
|
1200
1270
|
PHPhotoLibrary.requestAuthorization { newStatus in
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
completion(false, NSError(domain: "CameraPreview", code: 1, userInfo: [NSLocalizedDescriptionKey: "Photo library access denied"]))
|
|
1206
|
-
}
|
|
1271
|
+
if newStatus == .authorized {
|
|
1272
|
+
self.performSaveDataToGallery(imageData: imageData, completion: completion)
|
|
1273
|
+
} else {
|
|
1274
|
+
completion(false, NSError(domain: "CameraPreview", code: 1, userInfo: [NSLocalizedDescriptionKey: "Photo library access denied"]))
|
|
1207
1275
|
}
|
|
1208
1276
|
}
|
|
1209
1277
|
case .denied, .restricted:
|
|
@@ -1214,28 +1282,24 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1214
1282
|
completion(false, NSError(domain: "CameraPreview", code: 1, userInfo: [NSLocalizedDescriptionKey: "Unknown photo library authorization status"]))
|
|
1215
1283
|
}
|
|
1216
1284
|
}
|
|
1217
|
-
|
|
1285
|
+
|
|
1218
1286
|
private func performSaveDataToGallery(imageData: Data, completion: @escaping (Bool, Error?) -> Void) {
|
|
1219
1287
|
// Create a temporary file to write the JPEG data with EXIF
|
|
1220
1288
|
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".jpg")
|
|
1221
|
-
|
|
1289
|
+
|
|
1222
1290
|
do {
|
|
1223
1291
|
try imageData.write(to: tempURL)
|
|
1224
|
-
|
|
1292
|
+
|
|
1225
1293
|
PHPhotoLibrary.shared().performChanges({
|
|
1226
1294
|
PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: tempURL)
|
|
1227
1295
|
}, completionHandler: { success, error in
|
|
1228
1296
|
// Clean up temporary file
|
|
1229
1297
|
try? FileManager.default.removeItem(at: tempURL)
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
completion(success, error)
|
|
1233
|
-
}
|
|
1298
|
+
|
|
1299
|
+
completion(success, error)
|
|
1234
1300
|
})
|
|
1235
1301
|
} catch {
|
|
1236
|
-
|
|
1237
|
-
completion(false, error)
|
|
1238
|
-
}
|
|
1302
|
+
completion(false, error)
|
|
1239
1303
|
}
|
|
1240
1304
|
}
|
|
1241
1305
|
|
|
@@ -1244,6 +1308,14 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1244
1308
|
return
|
|
1245
1309
|
}
|
|
1246
1310
|
|
|
1311
|
+
// Ensure UI operations happen on main thread
|
|
1312
|
+
guard Thread.isMainThread else {
|
|
1313
|
+
DispatchQueue.main.async {
|
|
1314
|
+
self.updateCameraFrame()
|
|
1315
|
+
}
|
|
1316
|
+
return
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1247
1319
|
let paddingBottom = self.paddingBottom ?? 0
|
|
1248
1320
|
height -= paddingBottom
|
|
1249
1321
|
|
|
@@ -1259,7 +1331,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1259
1331
|
// Handle auto-centering when position is -1
|
|
1260
1332
|
if posX == -1 || posY == -1 {
|
|
1261
1333
|
finalWidth = webViewWidth
|
|
1262
|
-
|
|
1334
|
+
|
|
1263
1335
|
// Calculate height based on aspect ratio or use provided height
|
|
1264
1336
|
if let aspectRatio = self.aspectRatio {
|
|
1265
1337
|
let ratioParts = aspectRatio.split(separator: ":").compactMap { Double($0) }
|
|
@@ -1269,9 +1341,9 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1269
1341
|
finalHeight = finalWidth / CGFloat(ratio)
|
|
1270
1342
|
}
|
|
1271
1343
|
}
|
|
1272
|
-
|
|
1344
|
+
|
|
1273
1345
|
finalX = posX == -1 ? 0 : posX
|
|
1274
|
-
|
|
1346
|
+
|
|
1275
1347
|
if posY == -1 {
|
|
1276
1348
|
let availableHeight = webViewHeight - paddingBottom
|
|
1277
1349
|
finalY = finalHeight < availableHeight ? (availableHeight - finalHeight) / 2 : 0
|
|
@@ -1287,7 +1359,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1287
1359
|
// For camera, use portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
|
|
1288
1360
|
let ratio = ratioParts[1] / ratioParts[0]
|
|
1289
1361
|
let currentRatio = Double(finalWidth) / Double(finalHeight)
|
|
1290
|
-
|
|
1362
|
+
|
|
1291
1363
|
if currentRatio > ratio {
|
|
1292
1364
|
let newWidth = Double(finalHeight) * ratio
|
|
1293
1365
|
frame.origin.x = finalX + (Double(finalWidth) - newWidth) / 2
|
|
@@ -1303,7 +1375,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1303
1375
|
// Disable ALL animations for frame updates - we want instant positioning
|
|
1304
1376
|
CATransaction.begin()
|
|
1305
1377
|
CATransaction.setDisableActions(true)
|
|
1306
|
-
|
|
1378
|
+
|
|
1307
1379
|
// Batch UI updates for better performance
|
|
1308
1380
|
if self.previewView == nil {
|
|
1309
1381
|
self.previewView = UIView(frame: frame)
|
|
@@ -1316,12 +1388,12 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1316
1388
|
if let previewLayer = self.cameraController.previewLayer {
|
|
1317
1389
|
previewLayer.frame = self.previewView.bounds
|
|
1318
1390
|
}
|
|
1319
|
-
|
|
1391
|
+
|
|
1320
1392
|
// Update grid overlay frame if it exists
|
|
1321
1393
|
if let gridOverlay = self.cameraController.gridOverlayView {
|
|
1322
1394
|
gridOverlay.frame = self.previewView.bounds
|
|
1323
1395
|
}
|
|
1324
|
-
|
|
1396
|
+
|
|
1325
1397
|
CATransaction.commit()
|
|
1326
1398
|
}
|
|
1327
1399
|
|
|
@@ -1330,12 +1402,15 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1330
1402
|
call.reject("camera not started")
|
|
1331
1403
|
return
|
|
1332
1404
|
}
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1405
|
+
|
|
1406
|
+
DispatchQueue.main.async {
|
|
1407
|
+
var result = JSObject()
|
|
1408
|
+
result["x"] = Double(self.previewView.frame.origin.x)
|
|
1409
|
+
result["y"] = Double(self.previewView.frame.origin.y)
|
|
1410
|
+
result["width"] = Double(self.previewView.frame.width)
|
|
1411
|
+
result["height"] = Double(self.previewView.frame.height)
|
|
1412
|
+
call.resolve(result)
|
|
1413
|
+
}
|
|
1339
1414
|
}
|
|
1340
1415
|
|
|
1341
1416
|
@objc func setPreviewSize(_ call: CAPPluginCall) {
|
|
@@ -1343,27 +1418,29 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1343
1418
|
call.reject("camera not started")
|
|
1344
1419
|
return
|
|
1345
1420
|
}
|
|
1346
|
-
|
|
1421
|
+
|
|
1347
1422
|
// Only update position if explicitly provided, otherwise keep auto-centering
|
|
1348
|
-
if let x = call.getInt("x") {
|
|
1349
|
-
self.posX = CGFloat(x)
|
|
1423
|
+
if let x = call.getInt("x") {
|
|
1424
|
+
self.posX = CGFloat(x)
|
|
1350
1425
|
}
|
|
1351
|
-
if let y = call.getInt("y") {
|
|
1352
|
-
self.posY = CGFloat(y)
|
|
1426
|
+
if let y = call.getInt("y") {
|
|
1427
|
+
self.posY = CGFloat(y)
|
|
1353
1428
|
}
|
|
1354
1429
|
if let width = call.getInt("width") { self.width = CGFloat(width) }
|
|
1355
1430
|
if let height = call.getInt("height") { self.height = CGFloat(height) }
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1431
|
+
|
|
1432
|
+
DispatchQueue.main.async {
|
|
1433
|
+
// Direct update without animation for better performance
|
|
1434
|
+
self.updateCameraFrame()
|
|
1435
|
+
self.makeWebViewTransparent()
|
|
1436
|
+
|
|
1437
|
+
// Return the actual preview bounds
|
|
1438
|
+
var result = JSObject()
|
|
1439
|
+
result["x"] = Double(self.previewView.frame.origin.x)
|
|
1440
|
+
result["y"] = Double(self.previewView.frame.origin.y)
|
|
1441
|
+
result["width"] = Double(self.previewView.frame.width)
|
|
1442
|
+
result["height"] = Double(self.previewView.frame.height)
|
|
1443
|
+
call.resolve(result)
|
|
1444
|
+
}
|
|
1368
1445
|
}
|
|
1369
1446
|
}
|