@capgo/camera-preview 7.4.0-beta.1 → 7.4.0-beta.11
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 +195 -31
- package/android/.gradle/8.14.2/checksums/checksums.lock +0 -0
- package/android/.gradle/8.14.2/checksums/md5-checksums.bin +0 -0
- package/android/.gradle/8.14.2/checksums/sha1-checksums.bin +0 -0
- package/android/.gradle/8.14.2/executionHistory/executionHistory.bin +0 -0
- package/android/.gradle/8.14.2/executionHistory/executionHistory.lock +0 -0
- package/android/.gradle/8.14.2/fileHashes/fileHashes.bin +0 -0
- package/android/.gradle/8.14.2/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.14.2/fileHashes/resourceHashesCache.bin +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/file-system.probe +0 -0
- package/android/build.gradle +3 -1
- package/android/src/main/AndroidManifest.xml +5 -3
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +473 -88
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +2065 -704
- package/android/src/main/java/com/ahm/capacitor/camera/preview/GridOverlayView.java +95 -0
- 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 +152 -59
- 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 +235 -6
- package/dist/esm/definitions.d.ts +119 -3
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +47 -3
- package/dist/esm/web.js +297 -96
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +293 -96
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +293 -96
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreview/CameraController.swift +364 -218
- package/ios/Sources/CapgoCameraPreview/GridOverlayView.swift +65 -0
- package/ios/Sources/CapgoCameraPreview/Plugin.swift +886 -242
- package/package.json +1 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import Foundation
|
|
2
|
-
import Capacitor
|
|
3
2
|
import AVFoundation
|
|
3
|
+
import Photos
|
|
4
|
+
import Capacitor
|
|
5
|
+
import CoreImage
|
|
6
|
+
import CoreLocation
|
|
7
|
+
import MobileCoreServices
|
|
4
8
|
|
|
5
9
|
extension UIWindow {
|
|
6
10
|
static var isLandscape: Bool {
|
|
@@ -32,7 +36,7 @@ extension UIWindow {
|
|
|
32
36
|
* here: https://capacitor.ionicframework.com/docs/plugins/ios
|
|
33
37
|
*/
|
|
34
38
|
@objc(CameraPreview)
|
|
35
|
-
public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
39
|
+
public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelegate {
|
|
36
40
|
public let identifier = "CameraPreviewPlugin"
|
|
37
41
|
public let jsName = "CameraPreview"
|
|
38
42
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -54,11 +58,18 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
54
58
|
CAPPluginMethod(name: "setZoom", returnType: CAPPluginReturnPromise),
|
|
55
59
|
CAPPluginMethod(name: "getFlashMode", returnType: CAPPluginReturnPromise),
|
|
56
60
|
CAPPluginMethod(name: "setDeviceId", returnType: CAPPluginReturnPromise),
|
|
57
|
-
CAPPluginMethod(name: "getDeviceId", returnType: CAPPluginReturnPromise)
|
|
61
|
+
CAPPluginMethod(name: "getDeviceId", returnType: CAPPluginReturnPromise),
|
|
62
|
+
CAPPluginMethod(name: "setAspectRatio", returnType: CAPPluginReturnPromise),
|
|
63
|
+
CAPPluginMethod(name: "getAspectRatio", returnType: CAPPluginReturnPromise),
|
|
64
|
+
CAPPluginMethod(name: "setGridMode", returnType: CAPPluginReturnPromise),
|
|
65
|
+
CAPPluginMethod(name: "getGridMode", returnType: CAPPluginReturnPromise),
|
|
66
|
+
CAPPluginMethod(name: "getPreviewSize", returnType: CAPPluginReturnPromise),
|
|
67
|
+
CAPPluginMethod(name: "setPreviewSize", returnType: CAPPluginReturnPromise)
|
|
58
68
|
]
|
|
59
69
|
// Camera state tracking
|
|
60
70
|
private var isInitializing: Bool = false
|
|
61
71
|
private var isInitialized: Bool = false
|
|
72
|
+
private var backgroundSession: AVCaptureSession?
|
|
62
73
|
|
|
63
74
|
var previewView: UIView!
|
|
64
75
|
var cameraPosition = String()
|
|
@@ -74,35 +85,41 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
74
85
|
var enableZoom: Bool?
|
|
75
86
|
var highResolutionOutput: Bool = false
|
|
76
87
|
var disableAudio: Bool = false
|
|
88
|
+
var locationManager: CLLocationManager?
|
|
89
|
+
var currentLocation: CLLocation?
|
|
90
|
+
private var aspectRatio: String?
|
|
91
|
+
private var gridMode: String = "none"
|
|
92
|
+
private var permissionCallID: String?
|
|
93
|
+
private var waitingForLocation: Bool = false
|
|
77
94
|
|
|
78
95
|
// MARK: - Transparency Methods
|
|
79
|
-
|
|
96
|
+
|
|
80
97
|
private func makeWebViewTransparent() {
|
|
81
98
|
guard let webView = self.webView else { return }
|
|
82
|
-
|
|
83
|
-
// Define a recursive function to traverse the view hierarchy
|
|
84
|
-
func makeSubviewsTransparent(_ view: UIView) {
|
|
85
|
-
// Set the background color to clear
|
|
86
|
-
view.backgroundColor = .clear
|
|
87
|
-
|
|
88
|
-
// Recurse for all subviews
|
|
89
|
-
for subview in view.subviews {
|
|
90
|
-
makeSubviewsTransparent(subview)
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Set the main webView to be transparent
|
|
95
|
-
webView.isOpaque = false
|
|
96
|
-
webView.backgroundColor = .clear
|
|
97
|
-
|
|
98
|
-
// Recursively make all subviews transparent
|
|
99
|
-
makeSubviewsTransparent(webView)
|
|
100
|
-
|
|
101
|
-
// Also ensure the webview's container is transparent
|
|
102
|
-
webView.superview?.backgroundColor = .clear
|
|
103
|
-
|
|
104
|
-
// Force a layout pass to apply changes
|
|
99
|
+
|
|
105
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
|
+
}
|
|
111
|
+
|
|
112
|
+
// Set the main webView to be transparent
|
|
113
|
+
webView.isOpaque = false
|
|
114
|
+
webView.backgroundColor = .clear
|
|
115
|
+
|
|
116
|
+
// Recursively make all subviews transparent
|
|
117
|
+
makeSubviewsTransparent(webView)
|
|
118
|
+
|
|
119
|
+
// Also ensure the webview's container is transparent
|
|
120
|
+
webView.superview?.backgroundColor = .clear
|
|
121
|
+
|
|
122
|
+
// Force a layout pass to apply changes
|
|
106
123
|
webView.setNeedsLayout()
|
|
107
124
|
webView.layoutIfNeeded()
|
|
108
125
|
}
|
|
@@ -119,14 +136,26 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
119
136
|
let paddingBottom = self.paddingBottom ?? 0
|
|
120
137
|
let height = heightValue - paddingBottom
|
|
121
138
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
139
|
+
// Handle auto-centering during rotation
|
|
140
|
+
if posX == -1 || posY == -1 {
|
|
141
|
+
// Trigger full recalculation for auto-centered views
|
|
142
|
+
self.updateCameraFrame()
|
|
143
|
+
} else {
|
|
144
|
+
// Manual positioning - use original rotation logic with no animation
|
|
145
|
+
CATransaction.begin()
|
|
146
|
+
CATransaction.setDisableActions(true)
|
|
147
|
+
|
|
148
|
+
if UIWindow.isLandscape {
|
|
149
|
+
previewView.frame = CGRect(x: posY, y: posX, width: max(height, width), height: min(height, width))
|
|
150
|
+
self.cameraController.previewLayer?.frame = previewView.bounds
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if UIWindow.isPortrait {
|
|
154
|
+
previewView.frame = CGRect(x: posX, y: posY, width: min(height, width), height: max(height, width))
|
|
155
|
+
self.cameraController.previewLayer?.frame = previewView.bounds
|
|
156
|
+
}
|
|
126
157
|
|
|
127
|
-
|
|
128
|
-
previewView.frame = CGRect(x: posX, y: posY, width: min(height, width), height: max(height, width))
|
|
129
|
-
self.cameraController.previewLayer?.frame = previewView.frame
|
|
158
|
+
CATransaction.commit()
|
|
130
159
|
}
|
|
131
160
|
|
|
132
161
|
if let connection = self.cameraController.fileVideoOutput?.connection(with: .video) {
|
|
@@ -145,13 +174,132 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
145
174
|
}
|
|
146
175
|
|
|
147
176
|
cameraController.updateVideoOrientation()
|
|
148
|
-
|
|
177
|
+
|
|
178
|
+
cameraController.updateVideoOrientation()
|
|
179
|
+
|
|
180
|
+
// Update grid overlay frame if it exists - no animation
|
|
181
|
+
if let gridOverlay = self.cameraController.gridOverlayView {
|
|
182
|
+
CATransaction.begin()
|
|
183
|
+
CATransaction.setDisableActions(true)
|
|
184
|
+
gridOverlay.frame = previewView.bounds
|
|
185
|
+
CATransaction.commit()
|
|
186
|
+
}
|
|
187
|
+
|
|
149
188
|
// Ensure webview remains transparent after rotation
|
|
150
189
|
if self.isInitialized {
|
|
151
190
|
self.makeWebViewTransparent()
|
|
152
191
|
}
|
|
153
192
|
}
|
|
154
|
-
|
|
193
|
+
|
|
194
|
+
@objc func setAspectRatio(_ call: CAPPluginCall) {
|
|
195
|
+
guard self.isInitialized else {
|
|
196
|
+
call.reject("camera not started")
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
guard let newAspectRatio = call.getString("aspectRatio") else {
|
|
201
|
+
call.reject("aspectRatio parameter is required")
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
self.aspectRatio = newAspectRatio
|
|
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!)")
|
|
248
|
+
}
|
|
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)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
@objc func getAspectRatio(_ call: CAPPluginCall) {
|
|
263
|
+
guard self.isInitialized else {
|
|
264
|
+
call.reject("camera not started")
|
|
265
|
+
return
|
|
266
|
+
}
|
|
267
|
+
call.resolve(["aspectRatio": self.aspectRatio ?? "4:3"])
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
@objc func setGridMode(_ call: CAPPluginCall) {
|
|
271
|
+
guard self.isInitialized else {
|
|
272
|
+
call.reject("camera not started")
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
guard let gridMode = call.getString("gridMode") else {
|
|
277
|
+
call.reject("gridMode parameter is required")
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
self.gridMode = gridMode
|
|
282
|
+
|
|
283
|
+
// Update grid overlay
|
|
284
|
+
DispatchQueue.main.async {
|
|
285
|
+
if gridMode == "none" {
|
|
286
|
+
self.cameraController.removeGridOverlay()
|
|
287
|
+
} else {
|
|
288
|
+
self.cameraController.addGridOverlay(to: self.previewView, gridMode: gridMode)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
call.resolve()
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
@objc func getGridMode(_ call: CAPPluginCall) {
|
|
296
|
+
guard self.isInitialized else {
|
|
297
|
+
call.reject("camera not started")
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
call.resolve(["gridMode": self.gridMode])
|
|
301
|
+
}
|
|
302
|
+
|
|
155
303
|
@objc func appDidBecomeActive() {
|
|
156
304
|
if self.isInitialized {
|
|
157
305
|
DispatchQueue.main.async {
|
|
@@ -159,7 +307,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
159
307
|
}
|
|
160
308
|
}
|
|
161
309
|
}
|
|
162
|
-
|
|
310
|
+
|
|
163
311
|
@objc func appWillEnterForeground() {
|
|
164
312
|
if self.isInitialized {
|
|
165
313
|
DispatchQueue.main.async {
|
|
@@ -266,18 +414,33 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
266
414
|
self.highResolutionOutput = call.getBool("enableHighResolution") ?? false
|
|
267
415
|
self.cameraController.highResolutionOutput = self.highResolutionOutput
|
|
268
416
|
|
|
269
|
-
|
|
270
|
-
|
|
417
|
+
// Set width - use screen width if not provided or if 0
|
|
418
|
+
if let width = call.getInt("width"), width > 0 {
|
|
419
|
+
self.width = CGFloat(width)
|
|
271
420
|
} else {
|
|
272
421
|
self.width = UIScreen.main.bounds.size.width
|
|
273
422
|
}
|
|
274
|
-
|
|
275
|
-
|
|
423
|
+
|
|
424
|
+
// Set height - use screen height if not provided or if 0
|
|
425
|
+
if let height = call.getInt("height"), height > 0 {
|
|
426
|
+
self.height = CGFloat(height)
|
|
276
427
|
} else {
|
|
277
428
|
self.height = UIScreen.main.bounds.size.height
|
|
278
429
|
}
|
|
279
|
-
|
|
280
|
-
|
|
430
|
+
|
|
431
|
+
// Set x position - use exact CSS pixel value from web view, or mark for centering
|
|
432
|
+
if let x = call.getInt("x") {
|
|
433
|
+
self.posX = CGFloat(x)
|
|
434
|
+
} else {
|
|
435
|
+
self.posX = -1 // Use -1 to indicate auto-centering
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Set y position - use exact CSS pixel value from web view, or mark for centering
|
|
439
|
+
if let y = call.getInt("y") {
|
|
440
|
+
self.posY = CGFloat(y)
|
|
441
|
+
} else {
|
|
442
|
+
self.posY = -1 // Use -1 to indicate auto-centering
|
|
443
|
+
}
|
|
281
444
|
if call.getInt("paddingBottom") != nil {
|
|
282
445
|
self.paddingBottom = CGFloat(call.getInt("paddingBottom")!)
|
|
283
446
|
}
|
|
@@ -286,7 +449,17 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
286
449
|
self.toBack = call.getBool("toBack") ?? true
|
|
287
450
|
self.storeToFile = call.getBool("storeToFile") ?? false
|
|
288
451
|
self.enableZoom = call.getBool("enableZoom") ?? false
|
|
289
|
-
self.disableAudio = call.getBool("disableAudio") ??
|
|
452
|
+
self.disableAudio = call.getBool("disableAudio") ?? true
|
|
453
|
+
self.aspectRatio = call.getString("aspectRatio")
|
|
454
|
+
self.gridMode = call.getString("gridMode") ?? "none"
|
|
455
|
+
if self.aspectRatio != nil && (call.getInt("width") != nil || call.getInt("height") != nil) {
|
|
456
|
+
call.reject("Cannot set both aspectRatio and size (width/height). Use setPreviewSize after start.")
|
|
457
|
+
return
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
print("[CameraPreview] Camera start parameters - aspectRatio: \(String(describing: self.aspectRatio)), gridMode: \(self.gridMode)")
|
|
461
|
+
print("[CameraPreview] Screen dimensions: \(UIScreen.main.bounds.size)")
|
|
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))")
|
|
290
463
|
|
|
291
464
|
AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted: Bool) in
|
|
292
465
|
guard granted else {
|
|
@@ -294,48 +467,69 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
294
467
|
return
|
|
295
468
|
}
|
|
296
469
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
call.reject(error.localizedDescription)
|
|
305
|
-
return
|
|
306
|
-
}
|
|
307
|
-
let height = self.paddingBottom != nil ? self.height! - self.paddingBottom!: self.height!
|
|
308
|
-
self.previewView = UIView(frame: CGRect(x: self.posX ?? 0, y: self.posY ?? 0, width: self.width!, height: height))
|
|
309
|
-
|
|
310
|
-
// Make webview transparent - comprehensive approach
|
|
311
|
-
self.makeWebViewTransparent()
|
|
312
|
-
|
|
313
|
-
self.webView?.superview?.addSubview(self.previewView)
|
|
314
|
-
if self.toBack! {
|
|
315
|
-
self.webView?.superview?.bringSubviewToFront(self.webView!)
|
|
316
|
-
}
|
|
317
|
-
try? self.cameraController.displayPreview(on: self.previewView)
|
|
318
|
-
|
|
319
|
-
let frontView = self.toBack! ? self.webView : self.previewView
|
|
320
|
-
self.cameraController.setupGestures(target: frontView ?? self.previewView, enableZoom: self.enableZoom!)
|
|
321
|
-
|
|
322
|
-
if self.rotateWhenOrientationChanged == true {
|
|
323
|
-
NotificationCenter.default.addObserver(self, selector: #selector(CameraPreview.rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// Add observers for app state changes to maintain transparency
|
|
327
|
-
NotificationCenter.default.addObserver(self, selector: #selector(CameraPreview.appDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
|
|
328
|
-
NotificationCenter.default.addObserver(self, selector: #selector(CameraPreview.appWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
|
|
329
|
-
|
|
330
|
-
self.isInitializing = false
|
|
331
|
-
self.isInitialized = true
|
|
332
|
-
call.resolve()
|
|
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
|
+
}
|
|
333
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
|
+
self.completeStartCamera(call: call)
|
|
334
486
|
}
|
|
335
487
|
}
|
|
336
488
|
}
|
|
337
489
|
})
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
private func completeStartCamera(call: CAPPluginCall) {
|
|
493
|
+
// Create and configure the preview view first
|
|
494
|
+
self.updateCameraFrame()
|
|
495
|
+
|
|
496
|
+
// Make webview transparent - comprehensive approach
|
|
497
|
+
self.makeWebViewTransparent()
|
|
498
|
+
|
|
499
|
+
// Add the preview view to the webview itself to use same coordinate system
|
|
500
|
+
self.webView?.addSubview(self.previewView)
|
|
501
|
+
if self.toBack! {
|
|
502
|
+
self.webView?.sendSubviewToBack(self.previewView)
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Display the camera preview on the configured view
|
|
506
|
+
try? self.cameraController.displayPreview(on: self.previewView)
|
|
507
|
+
|
|
508
|
+
let frontView = self.toBack! ? self.webView : self.previewView
|
|
509
|
+
self.cameraController.setupGestures(target: frontView ?? self.previewView, enableZoom: self.enableZoom!)
|
|
510
|
+
|
|
511
|
+
// Add grid overlay if enabled
|
|
512
|
+
if self.gridMode != "none" {
|
|
513
|
+
self.cameraController.addGridOverlay(to: self.previewView, gridMode: self.gridMode)
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if self.rotateWhenOrientationChanged == true {
|
|
517
|
+
NotificationCenter.default.addObserver(self, selector: #selector(CameraPreview.rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Add observers for app state changes to maintain transparency
|
|
521
|
+
NotificationCenter.default.addObserver(self, selector: #selector(CameraPreview.appDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
|
|
522
|
+
NotificationCenter.default.addObserver(self, selector: #selector(CameraPreview.appWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
|
|
523
|
+
|
|
524
|
+
self.isInitializing = false
|
|
525
|
+
self.isInitialized = true
|
|
338
526
|
|
|
527
|
+
var returnedObject = JSObject()
|
|
528
|
+
returnedObject["width"] = self.previewView.frame.width as any JSValue
|
|
529
|
+
returnedObject["height"] = self.previewView.frame.height as any JSValue
|
|
530
|
+
returnedObject["x"] = self.previewView.frame.origin.x as any JSValue
|
|
531
|
+
returnedObject["y"] = self.previewView.frame.origin.y as any JSValue
|
|
532
|
+
call.resolve(returnedObject)
|
|
339
533
|
}
|
|
340
534
|
|
|
341
535
|
@objc func flip(_ call: CAPPluginCall) {
|
|
@@ -344,68 +538,46 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
344
538
|
return
|
|
345
539
|
}
|
|
346
540
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
call.reject("Camera controller deallocated")
|
|
350
|
-
return
|
|
351
|
-
}
|
|
541
|
+
// Disable user interaction during flip
|
|
542
|
+
self.previewView.isUserInteractionEnabled = false
|
|
352
543
|
|
|
353
|
-
|
|
354
|
-
self.
|
|
544
|
+
do {
|
|
545
|
+
try self.cameraController.switchCameras()
|
|
355
546
|
|
|
356
|
-
//
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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()
|
|
360
553
|
|
|
361
|
-
|
|
362
|
-
do {
|
|
363
|
-
try self.cameraController.switchCameras()
|
|
554
|
+
self.previewView.isUserInteractionEnabled = true
|
|
364
555
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
self.cameraController.previewLayer?.videoGravity = .resizeAspectFill
|
|
368
|
-
self.previewView.isUserInteractionEnabled = true
|
|
369
|
-
|
|
370
|
-
// Ensure webview remains transparent after flip
|
|
371
|
-
self.makeWebViewTransparent()
|
|
372
|
-
|
|
373
|
-
call.resolve()
|
|
374
|
-
}
|
|
375
|
-
} catch {
|
|
376
|
-
retryCount += 1
|
|
377
|
-
|
|
378
|
-
if retryCount < maxRetries {
|
|
379
|
-
DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 0.5) {
|
|
380
|
-
attemptFlip()
|
|
381
|
-
}
|
|
382
|
-
} else {
|
|
383
|
-
DispatchQueue.main.async {
|
|
384
|
-
self.previewView.isUserInteractionEnabled = true
|
|
385
|
-
print("Failed to flip camera after \(maxRetries) attempts: \(error.localizedDescription)")
|
|
386
|
-
call.reject("Failed to flip camera: \(error.localizedDescription)")
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
}
|
|
556
|
+
// Ensure webview remains transparent after flip
|
|
557
|
+
self.makeWebViewTransparent()
|
|
391
558
|
|
|
392
|
-
|
|
393
|
-
|
|
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)")
|
|
394
564
|
}
|
|
395
565
|
}
|
|
396
566
|
|
|
397
567
|
@objc func stop(_ call: CAPPluginCall) {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
}
|
|
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
|
+
}
|
|
407
576
|
|
|
577
|
+
// UI operations must be on main thread
|
|
578
|
+
DispatchQueue.main.async {
|
|
408
579
|
// Always attempt to stop and clean up, regardless of captureSession state
|
|
580
|
+
self.cameraController.removeGridOverlay()
|
|
409
581
|
if let previewView = self.previewView {
|
|
410
582
|
previewView.removeFromSuperview()
|
|
411
583
|
self.previewView = nil
|
|
@@ -415,7 +587,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
415
587
|
self.isInitialized = false
|
|
416
588
|
self.isInitializing = false
|
|
417
589
|
self.cameraController.cleanup()
|
|
418
|
-
|
|
590
|
+
|
|
419
591
|
// Remove notification observers
|
|
420
592
|
NotificationCenter.default.removeObserver(self)
|
|
421
593
|
|
|
@@ -434,75 +606,250 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
434
606
|
}
|
|
435
607
|
|
|
436
608
|
@objc func capture(_ call: CAPPluginCall) {
|
|
437
|
-
|
|
609
|
+
let withExifLocation = call.getBool("withExifLocation", false)
|
|
610
|
+
print("[CameraPreview] capture called, withExifLocation: \(withExifLocation)")
|
|
438
611
|
|
|
439
|
-
|
|
612
|
+
if withExifLocation {
|
|
613
|
+
print("[CameraPreview] Location required for capture")
|
|
440
614
|
|
|
441
|
-
|
|
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
|
+
}
|
|
442
621
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
|
627
|
+
}
|
|
628
|
+
|
|
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)
|
|
448
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")
|
|
696
|
+
if let error = error {
|
|
697
|
+
print("[CameraPreview] Capture error: \(error.localizedDescription)")
|
|
449
698
|
call.reject(error.localizedDescription)
|
|
450
699
|
return
|
|
451
700
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
imageData = image.jpegData(compressionQuality: CGFloat(quality!/100))
|
|
701
|
+
|
|
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")
|
|
704
|
+
call.reject("Failed to create image data with EXIF")
|
|
705
|
+
return
|
|
458
706
|
}
|
|
459
707
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
708
|
+
print("[CameraPreview] Image data created, size: \(imageDataWithExif.count) bytes")
|
|
709
|
+
|
|
710
|
+
if saveToGallery {
|
|
711
|
+
print("[CameraPreview] Saving to gallery...")
|
|
712
|
+
self.saveImageDataToGallery(imageData: imageDataWithExif) { success, error in
|
|
713
|
+
print("[CameraPreview] Save to gallery completed, success: \(success), error: \(error?.localizedDescription ?? "none")")
|
|
714
|
+
let exifData = self.getExifData(from: imageDataWithExif)
|
|
715
|
+
let base64Image = imageDataWithExif.base64EncodedString()
|
|
716
|
+
|
|
717
|
+
var result = JSObject()
|
|
718
|
+
result["value"] = base64Image
|
|
719
|
+
result["exif"] = exifData
|
|
720
|
+
result["gallerySaved"] = success
|
|
721
|
+
if !success, let error = error {
|
|
722
|
+
result["galleryError"] = error.localizedDescription
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
print("[CameraPreview] Resolving capture call with gallery save")
|
|
726
|
+
call.resolve(result)
|
|
470
727
|
}
|
|
728
|
+
} else {
|
|
729
|
+
print("[CameraPreview] Not saving to gallery, returning image data")
|
|
730
|
+
let exifData = self.getExifData(from: imageDataWithExif)
|
|
731
|
+
let base64Image = imageDataWithExif.base64EncodedString()
|
|
732
|
+
|
|
733
|
+
var result = JSObject()
|
|
734
|
+
result["value"] = base64Image
|
|
735
|
+
result["exif"] = exifData
|
|
736
|
+
|
|
737
|
+
print("[CameraPreview] Resolving capture call")
|
|
738
|
+
call.resolve(result)
|
|
471
739
|
}
|
|
472
740
|
}
|
|
473
741
|
}
|
|
474
742
|
}
|
|
475
743
|
|
|
744
|
+
private func getExifData(from imageData: Data) -> JSObject {
|
|
745
|
+
guard let imageSource = CGImageSourceCreateWithData(imageData as CFData, nil),
|
|
746
|
+
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [String: Any],
|
|
747
|
+
let exifDict = imageProperties[kCGImagePropertyExifDictionary as String] as? [String: Any] else {
|
|
748
|
+
return [:]
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
var exifData = JSObject()
|
|
752
|
+
for (key, value) in exifDict {
|
|
753
|
+
// Convert value to JSValue-compatible type
|
|
754
|
+
if let stringValue = value as? String {
|
|
755
|
+
exifData[key] = stringValue
|
|
756
|
+
} else if let numberValue = value as? NSNumber {
|
|
757
|
+
exifData[key] = numberValue
|
|
758
|
+
} else if let boolValue = value as? Bool {
|
|
759
|
+
exifData[key] = boolValue
|
|
760
|
+
} else if let arrayValue = value as? [Any] {
|
|
761
|
+
exifData[key] = arrayValue
|
|
762
|
+
} else if let dictValue = value as? [String: Any] {
|
|
763
|
+
exifData[key] = JSObject(_immutableCocoaDictionary: NSMutableDictionary(dictionary: dictValue))
|
|
764
|
+
} else {
|
|
765
|
+
// Convert other types to string as fallback
|
|
766
|
+
exifData[key] = String(describing: value)
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
return exifData
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
private func createImageDataWithExif(from image: UIImage, quality: Int, location: CLLocation?) -> Data? {
|
|
774
|
+
guard let originalImageData = image.jpegData(compressionQuality: CGFloat(Double(quality) / 100.0)) else {
|
|
775
|
+
return nil
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
guard let imageSource = CGImageSourceCreateWithData(originalImageData as CFData, nil),
|
|
779
|
+
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [String: Any],
|
|
780
|
+
let cgImage = image.cgImage else {
|
|
781
|
+
return originalImageData
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
let mutableData = NSMutableData()
|
|
785
|
+
guard let destination = CGImageDestinationCreateWithData(mutableData, kUTTypeJPEG, 1, nil) else {
|
|
786
|
+
return originalImageData
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
var finalProperties = imageProperties
|
|
790
|
+
|
|
791
|
+
// Add GPS location if available
|
|
792
|
+
if let location = location {
|
|
793
|
+
let formatter = DateFormatter()
|
|
794
|
+
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
|
|
795
|
+
formatter.timeZone = TimeZone(abbreviation: "UTC")
|
|
796
|
+
|
|
797
|
+
let gpsDict: [String: Any] = [
|
|
798
|
+
kCGImagePropertyGPSLatitude as String: abs(location.coordinate.latitude),
|
|
799
|
+
kCGImagePropertyGPSLatitudeRef as String: location.coordinate.latitude >= 0 ? "N" : "S",
|
|
800
|
+
kCGImagePropertyGPSLongitude as String: abs(location.coordinate.longitude),
|
|
801
|
+
kCGImagePropertyGPSLongitudeRef as String: location.coordinate.longitude >= 0 ? "E" : "W",
|
|
802
|
+
kCGImagePropertyGPSTimeStamp as String: formatter.string(from: location.timestamp),
|
|
803
|
+
kCGImagePropertyGPSAltitude as String: location.altitude,
|
|
804
|
+
kCGImagePropertyGPSAltitudeRef as String: location.altitude >= 0 ? 0 : 1
|
|
805
|
+
]
|
|
806
|
+
|
|
807
|
+
finalProperties[kCGImagePropertyGPSDictionary as String] = gpsDict
|
|
808
|
+
}
|
|
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
|
+
|
|
816
|
+
CGImageDestinationAddImage(destination, cgImage, finalProperties as CFDictionary)
|
|
817
|
+
|
|
818
|
+
if CGImageDestinationFinalize(destination) {
|
|
819
|
+
return mutableData as Data
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
return originalImageData
|
|
823
|
+
}
|
|
824
|
+
|
|
476
825
|
@objc func captureSample(_ call: CAPPluginCall) {
|
|
477
|
-
|
|
478
|
-
let quality: Int? = call.getInt("quality", 85)
|
|
826
|
+
let quality: Int? = call.getInt("quality", 85)
|
|
479
827
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
+
}
|
|
486
834
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
+
}
|
|
494
842
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
}
|
|
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")
|
|
506
853
|
}
|
|
507
854
|
}
|
|
508
855
|
}
|
|
@@ -557,31 +904,27 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
557
904
|
}
|
|
558
905
|
|
|
559
906
|
@objc func startRecordVideo(_ call: CAPPluginCall) {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
call.reject(error.localizedDescription)
|
|
566
|
-
}
|
|
907
|
+
do {
|
|
908
|
+
try self.cameraController.captureVideo()
|
|
909
|
+
call.resolve()
|
|
910
|
+
} catch {
|
|
911
|
+
call.reject(error.localizedDescription)
|
|
567
912
|
}
|
|
568
913
|
}
|
|
569
914
|
|
|
570
915
|
@objc func stopRecordVideo(_ call: CAPPluginCall) {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
call.reject("Video capture error")
|
|
577
|
-
return
|
|
578
|
-
}
|
|
579
|
-
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")
|
|
580
921
|
return
|
|
581
922
|
}
|
|
582
|
-
|
|
583
|
-
|
|
923
|
+
call.reject(error.localizedDescription)
|
|
924
|
+
return
|
|
584
925
|
}
|
|
926
|
+
|
|
927
|
+
call.resolve(["videoFilePath": fileURL.absoluteString])
|
|
585
928
|
}
|
|
586
929
|
}
|
|
587
930
|
|
|
@@ -612,20 +955,20 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
612
955
|
// Collect all devices by position
|
|
613
956
|
for device in session.devices {
|
|
614
957
|
var lenses: [[String: Any]] = []
|
|
615
|
-
|
|
958
|
+
|
|
616
959
|
let constituentDevices = device.isVirtualDevice ? device.constituentDevices : [device]
|
|
617
|
-
|
|
960
|
+
|
|
618
961
|
for lensDevice in constituentDevices {
|
|
619
962
|
var deviceType: String
|
|
620
963
|
switch lensDevice.deviceType {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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"
|
|
629
972
|
}
|
|
630
973
|
|
|
631
974
|
var baseZoomRatio: Float = 1.0
|
|
@@ -634,7 +977,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
634
977
|
} else if lensDevice.deviceType == .builtInTelephotoCamera {
|
|
635
978
|
baseZoomRatio = 2.0 // A common value for telephoto lenses
|
|
636
979
|
}
|
|
637
|
-
|
|
980
|
+
|
|
638
981
|
let lensInfo: [String: Any] = [
|
|
639
982
|
"label": lensDevice.localizedName,
|
|
640
983
|
"deviceType": deviceType,
|
|
@@ -645,7 +988,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
645
988
|
]
|
|
646
989
|
lenses.append(lensInfo)
|
|
647
990
|
}
|
|
648
|
-
|
|
991
|
+
|
|
649
992
|
let deviceData: [String: Any] = [
|
|
650
993
|
"deviceId": device.uniqueID,
|
|
651
994
|
"label": device.localizedName,
|
|
@@ -655,7 +998,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
655
998
|
"maxZoom": Float(device.maxAvailableVideoZoomFactor),
|
|
656
999
|
"isLogical": device.isVirtualDevice
|
|
657
1000
|
]
|
|
658
|
-
|
|
1001
|
+
|
|
659
1002
|
devices.append(deviceData)
|
|
660
1003
|
}
|
|
661
1004
|
|
|
@@ -671,7 +1014,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
671
1014
|
do {
|
|
672
1015
|
let zoomInfo = try self.cameraController.getZoom()
|
|
673
1016
|
let lensInfo = try self.cameraController.getCurrentLensInfo()
|
|
674
|
-
|
|
1017
|
+
|
|
675
1018
|
var minZoom = zoomInfo.min
|
|
676
1019
|
var maxZoom = zoomInfo.max
|
|
677
1020
|
var currentZoom = zoomInfo.current
|
|
@@ -750,40 +1093,32 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
750
1093
|
return
|
|
751
1094
|
}
|
|
752
1095
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
call.reject("Camera controller deallocated")
|
|
756
|
-
return
|
|
757
|
-
}
|
|
1096
|
+
// Disable user interaction during device swap
|
|
1097
|
+
self.previewView.isUserInteractionEnabled = false
|
|
758
1098
|
|
|
759
|
-
|
|
760
|
-
self.
|
|
1099
|
+
do {
|
|
1100
|
+
try self.cameraController.swapToDevice(deviceId: deviceId)
|
|
761
1101
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
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()
|
|
765
1108
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
}
|
|
776
|
-
} catch {
|
|
777
|
-
DispatchQueue.main.async {
|
|
778
|
-
self.previewView.isUserInteractionEnabled = true
|
|
779
|
-
call.reject("Failed to swap to device \(deviceId): \(error.localizedDescription)")
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
}
|
|
1109
|
+
self.previewView.isUserInteractionEnabled = true
|
|
1110
|
+
|
|
1111
|
+
// Ensure webview remains transparent after device switch
|
|
1112
|
+
self.makeWebViewTransparent()
|
|
1113
|
+
|
|
1114
|
+
call.resolve()
|
|
1115
|
+
} catch {
|
|
1116
|
+
self.previewView.isUserInteractionEnabled = true
|
|
1117
|
+
call.reject("Failed to swap to device \(deviceId): \(error.localizedDescription)")
|
|
783
1118
|
}
|
|
784
1119
|
}
|
|
785
1120
|
|
|
786
|
-
|
|
1121
|
+
@objc func getDeviceId(_ call: CAPPluginCall) {
|
|
787
1122
|
guard isInitialized else {
|
|
788
1123
|
call.reject("Camera not initialized")
|
|
789
1124
|
return
|
|
@@ -797,6 +1132,315 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
797
1132
|
}
|
|
798
1133
|
}
|
|
799
1134
|
|
|
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
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
|
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
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
private func saveImageDataToGallery(imageData: Data, completion: @escaping (Bool, Error?) -> Void) {
|
|
1255
|
+
// Check if NSPhotoLibraryUsageDescription is present in Info.plist
|
|
1256
|
+
guard Bundle.main.object(forInfoDictionaryKey: "NSPhotoLibraryUsageDescription") != nil else {
|
|
1257
|
+
let error = NSError(domain: "CameraPreview", code: 2, userInfo: [
|
|
1258
|
+
NSLocalizedDescriptionKey: "NSPhotoLibraryUsageDescription key missing from Info.plist. Add this key with a description of how your app uses photo library access."
|
|
1259
|
+
])
|
|
1260
|
+
completion(false, error)
|
|
1261
|
+
return
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
let status = PHPhotoLibrary.authorizationStatus()
|
|
1265
|
+
|
|
1266
|
+
switch status {
|
|
1267
|
+
case .authorized:
|
|
1268
|
+
performSaveDataToGallery(imageData: imageData, completion: completion)
|
|
1269
|
+
case .notDetermined:
|
|
1270
|
+
PHPhotoLibrary.requestAuthorization { newStatus in
|
|
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"]))
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
case .denied, .restricted:
|
|
1278
|
+
completion(false, NSError(domain: "CameraPreview", code: 1, userInfo: [NSLocalizedDescriptionKey: "Photo library access denied"]))
|
|
1279
|
+
case .limited:
|
|
1280
|
+
performSaveDataToGallery(imageData: imageData, completion: completion)
|
|
1281
|
+
@unknown default:
|
|
1282
|
+
completion(false, NSError(domain: "CameraPreview", code: 1, userInfo: [NSLocalizedDescriptionKey: "Unknown photo library authorization status"]))
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
private func performSaveDataToGallery(imageData: Data, completion: @escaping (Bool, Error?) -> Void) {
|
|
1287
|
+
// Create a temporary file to write the JPEG data with EXIF
|
|
1288
|
+
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".jpg")
|
|
1289
|
+
|
|
1290
|
+
do {
|
|
1291
|
+
try imageData.write(to: tempURL)
|
|
1292
|
+
|
|
1293
|
+
PHPhotoLibrary.shared().performChanges({
|
|
1294
|
+
PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: tempURL)
|
|
1295
|
+
}, completionHandler: { success, error in
|
|
1296
|
+
// Clean up temporary file
|
|
1297
|
+
try? FileManager.default.removeItem(at: tempURL)
|
|
1298
|
+
|
|
1299
|
+
completion(success, error)
|
|
1300
|
+
})
|
|
1301
|
+
} catch {
|
|
1302
|
+
completion(false, error)
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
private func updateCameraFrame() {
|
|
1307
|
+
guard let width = self.width, var height = self.height, let posX = self.posX, let posY = self.posY else {
|
|
1308
|
+
return
|
|
1309
|
+
}
|
|
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
|
+
|
|
1319
|
+
let paddingBottom = self.paddingBottom ?? 0
|
|
1320
|
+
height -= paddingBottom
|
|
1321
|
+
|
|
1322
|
+
// Cache webView dimensions for performance
|
|
1323
|
+
let webViewWidth = self.webView?.frame.width ?? UIScreen.main.bounds.width
|
|
1324
|
+
let webViewHeight = self.webView?.frame.height ?? UIScreen.main.bounds.height
|
|
1325
|
+
|
|
1326
|
+
var finalX = posX
|
|
1327
|
+
var finalY = posY
|
|
1328
|
+
var finalWidth = width
|
|
1329
|
+
var finalHeight = height
|
|
1330
|
+
|
|
1331
|
+
// Handle auto-centering when position is -1
|
|
1332
|
+
if posX == -1 || posY == -1 {
|
|
1333
|
+
finalWidth = webViewWidth
|
|
1334
|
+
|
|
1335
|
+
// Calculate height based on aspect ratio or use provided height
|
|
1336
|
+
if let aspectRatio = self.aspectRatio {
|
|
1337
|
+
let ratioParts = aspectRatio.split(separator: ":").compactMap { Double($0) }
|
|
1338
|
+
if ratioParts.count == 2 {
|
|
1339
|
+
// For camera, use portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
|
|
1340
|
+
let ratio = ratioParts[1] / ratioParts[0]
|
|
1341
|
+
finalHeight = finalWidth / CGFloat(ratio)
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
finalX = posX == -1 ? 0 : posX
|
|
1346
|
+
|
|
1347
|
+
if posY == -1 {
|
|
1348
|
+
let availableHeight = webViewHeight - paddingBottom
|
|
1349
|
+
finalY = finalHeight < availableHeight ? (availableHeight - finalHeight) / 2 : 0
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
var frame = CGRect(x: finalX, y: finalY, width: finalWidth, height: finalHeight)
|
|
1354
|
+
|
|
1355
|
+
// Apply aspect ratio adjustments only if not auto-centering
|
|
1356
|
+
if posX != -1 && posY != -1, let aspectRatio = self.aspectRatio {
|
|
1357
|
+
let ratioParts = aspectRatio.split(separator: ":").compactMap { Double($0) }
|
|
1358
|
+
if ratioParts.count == 2 {
|
|
1359
|
+
// For camera, use portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
|
|
1360
|
+
let ratio = ratioParts[1] / ratioParts[0]
|
|
1361
|
+
let currentRatio = Double(finalWidth) / Double(finalHeight)
|
|
1362
|
+
|
|
1363
|
+
if currentRatio > ratio {
|
|
1364
|
+
let newWidth = Double(finalHeight) * ratio
|
|
1365
|
+
frame.origin.x = finalX + (Double(finalWidth) - newWidth) / 2
|
|
1366
|
+
frame.size.width = CGFloat(newWidth)
|
|
1367
|
+
} else {
|
|
1368
|
+
let newHeight = Double(finalWidth) / ratio
|
|
1369
|
+
frame.origin.y = finalY + (Double(finalHeight) - newHeight) / 2
|
|
1370
|
+
frame.size.height = CGFloat(newHeight)
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
// Disable ALL animations for frame updates - we want instant positioning
|
|
1376
|
+
CATransaction.begin()
|
|
1377
|
+
CATransaction.setDisableActions(true)
|
|
1378
|
+
|
|
1379
|
+
// Batch UI updates for better performance
|
|
1380
|
+
if self.previewView == nil {
|
|
1381
|
+
self.previewView = UIView(frame: frame)
|
|
1382
|
+
self.previewView.backgroundColor = UIColor.clear
|
|
1383
|
+
} else {
|
|
1384
|
+
self.previewView.frame = frame
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// Update preview layer frame efficiently
|
|
1388
|
+
if let previewLayer = self.cameraController.previewLayer {
|
|
1389
|
+
previewLayer.frame = self.previewView.bounds
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
// Update grid overlay frame if it exists
|
|
1393
|
+
if let gridOverlay = self.cameraController.gridOverlayView {
|
|
1394
|
+
gridOverlay.frame = self.previewView.bounds
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
CATransaction.commit()
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
@objc func getPreviewSize(_ call: CAPPluginCall) {
|
|
1401
|
+
guard self.isInitialized else {
|
|
1402
|
+
call.reject("camera not started")
|
|
1403
|
+
return
|
|
1404
|
+
}
|
|
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
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
@objc func setPreviewSize(_ call: CAPPluginCall) {
|
|
1417
|
+
guard self.isInitialized else {
|
|
1418
|
+
call.reject("camera not started")
|
|
1419
|
+
return
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
// Only update position if explicitly provided, otherwise keep auto-centering
|
|
1423
|
+
if let x = call.getInt("x") {
|
|
1424
|
+
self.posX = CGFloat(x)
|
|
1425
|
+
}
|
|
1426
|
+
if let y = call.getInt("y") {
|
|
1427
|
+
self.posY = CGFloat(y)
|
|
1428
|
+
}
|
|
1429
|
+
if let width = call.getInt("width") { self.width = CGFloat(width) }
|
|
1430
|
+
if let height = call.getInt("height") { self.height = CGFloat(height) }
|
|
800
1431
|
|
|
1432
|
+
DispatchQueue.main.async {
|
|
1433
|
+
// Direct update without animation for better performance
|
|
1434
|
+
self.updateCameraFrame()
|
|
1435
|
+
self.makeWebViewTransparent()
|
|
801
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
|
+
}
|
|
1445
|
+
}
|
|
802
1446
|
}
|