@capgo/camera-preview 7.4.0-alpha.3 → 7.4.0-alpha.35
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 +179 -12
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +585 -11
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +994 -423
- package/dist/docs.json +312 -11
- package/dist/esm/definitions.d.ts +115 -11
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +24 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +16 -1
- package/dist/esm/web.js +82 -5
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +105 -5
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +105 -5
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift +143 -34
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +486 -132
- package/package.json +10 -2
|
@@ -55,6 +55,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
55
55
|
CAPPluginMethod(name: "isRunning", returnType: CAPPluginReturnPromise),
|
|
56
56
|
CAPPluginMethod(name: "getAvailableDevices", returnType: CAPPluginReturnPromise),
|
|
57
57
|
CAPPluginMethod(name: "getZoom", returnType: CAPPluginReturnPromise),
|
|
58
|
+
CAPPluginMethod(name: "getZoomButtonValues", returnType: CAPPluginReturnPromise),
|
|
58
59
|
CAPPluginMethod(name: "setZoom", returnType: CAPPluginReturnPromise),
|
|
59
60
|
CAPPluginMethod(name: "getFlashMode", returnType: CAPPluginReturnPromise),
|
|
60
61
|
CAPPluginMethod(name: "setDeviceId", returnType: CAPPluginReturnPromise),
|
|
@@ -65,7 +66,11 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
65
66
|
CAPPluginMethod(name: "getGridMode", returnType: CAPPluginReturnPromise),
|
|
66
67
|
CAPPluginMethod(name: "getPreviewSize", returnType: CAPPluginReturnPromise),
|
|
67
68
|
CAPPluginMethod(name: "setPreviewSize", returnType: CAPPluginReturnPromise),
|
|
68
|
-
CAPPluginMethod(name: "setFocus", returnType: CAPPluginReturnPromise)
|
|
69
|
+
CAPPluginMethod(name: "setFocus", returnType: CAPPluginReturnPromise),
|
|
70
|
+
CAPPluginMethod(name: "deleteFile", returnType: CAPPluginReturnPromise),
|
|
71
|
+
CAPPluginMethod(name: "getOrientation", returnType: CAPPluginReturnPromise),
|
|
72
|
+
CAPPluginMethod(name: "getSafeAreaInsets", returnType: CAPPluginReturnPromise)
|
|
73
|
+
|
|
69
74
|
]
|
|
70
75
|
// Camera state tracking
|
|
71
76
|
private var isInitializing: Bool = false
|
|
@@ -93,6 +98,44 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
93
98
|
private var permissionCallID: String?
|
|
94
99
|
private var waitingForLocation: Bool = false
|
|
95
100
|
|
|
101
|
+
// MARK: - Helper Methods for Aspect Ratio
|
|
102
|
+
|
|
103
|
+
/// Validates that aspectRatio and size (width/height) are not both set
|
|
104
|
+
private func validateAspectRatioParameters(aspectRatio: String?, width: Int?, height: Int?) -> String? {
|
|
105
|
+
if aspectRatio != nil && (width != nil || height != nil) {
|
|
106
|
+
return "Cannot set both aspectRatio and size (width/height). Use setPreviewSize after start."
|
|
107
|
+
}
|
|
108
|
+
return nil
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// Parses aspect ratio string and returns the appropriate ratio for the current orientation
|
|
112
|
+
private func parseAspectRatio(_ ratio: String, isPortrait: Bool) -> CGFloat {
|
|
113
|
+
let parts = ratio.split(separator: ":").compactMap { Double($0) }
|
|
114
|
+
guard parts.count == 2 else { return 1.0 }
|
|
115
|
+
|
|
116
|
+
// For camera (portrait), we want portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
|
|
117
|
+
return isPortrait ?
|
|
118
|
+
CGFloat(parts[1] / parts[0]) :
|
|
119
|
+
CGFloat(parts[0] / parts[1])
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// Calculates dimensions based on aspect ratio and available space
|
|
123
|
+
private func calculateDimensionsForAspectRatio(_ aspectRatio: String, availableWidth: CGFloat, availableHeight: CGFloat, isPortrait: Bool) -> (width: CGFloat, height: CGFloat) {
|
|
124
|
+
let ratio = parseAspectRatio(aspectRatio, isPortrait: isPortrait)
|
|
125
|
+
|
|
126
|
+
// Calculate maximum size that fits the aspect ratio in available space
|
|
127
|
+
let maxWidthByHeight = availableHeight * ratio
|
|
128
|
+
let maxHeightByWidth = availableWidth / ratio
|
|
129
|
+
|
|
130
|
+
if maxWidthByHeight <= availableWidth {
|
|
131
|
+
// Height is the limiting factor
|
|
132
|
+
return (width: maxWidthByHeight, height: availableHeight)
|
|
133
|
+
} else {
|
|
134
|
+
// Width is the limiting factor
|
|
135
|
+
return (width: availableWidth, height: maxHeightByWidth)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
96
139
|
// MARK: - Transparency Methods
|
|
97
140
|
|
|
98
141
|
private func makeWebViewTransparent() {
|
|
@@ -126,6 +169,103 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
126
169
|
}
|
|
127
170
|
}
|
|
128
171
|
|
|
172
|
+
@objc func getZoomButtonValues(_ call: CAPPluginCall) {
|
|
173
|
+
guard isInitialized else {
|
|
174
|
+
call.reject("Camera not initialized")
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Determine current device based on active position
|
|
179
|
+
var currentDevice: AVCaptureDevice?
|
|
180
|
+
switch self.cameraController.currentCameraPosition {
|
|
181
|
+
case .front:
|
|
182
|
+
currentDevice = self.cameraController.frontCamera
|
|
183
|
+
case .rear:
|
|
184
|
+
currentDevice = self.cameraController.rearCamera
|
|
185
|
+
default:
|
|
186
|
+
currentDevice = nil
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
guard let device = currentDevice else {
|
|
190
|
+
call.reject("No active camera device")
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
var hasUltraWide = false
|
|
195
|
+
var hasWide = false
|
|
196
|
+
var hasTele = false
|
|
197
|
+
|
|
198
|
+
let lenses = device.isVirtualDevice ? device.constituentDevices : [device]
|
|
199
|
+
for lens in lenses {
|
|
200
|
+
switch lens.deviceType {
|
|
201
|
+
case .builtInUltraWideCamera:
|
|
202
|
+
hasUltraWide = true
|
|
203
|
+
case .builtInWideAngleCamera:
|
|
204
|
+
hasWide = true
|
|
205
|
+
case .builtInTelephotoCamera:
|
|
206
|
+
hasTele = true
|
|
207
|
+
default:
|
|
208
|
+
break
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
var values: [Float] = []
|
|
213
|
+
if hasUltraWide {
|
|
214
|
+
values.append(0.5)
|
|
215
|
+
}
|
|
216
|
+
if hasWide {
|
|
217
|
+
values.append(1.0)
|
|
218
|
+
if self.isProModelSupportingOptical2x() {
|
|
219
|
+
values.append(2.0)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if hasTele {
|
|
223
|
+
// Use the virtual device's switch-over zoom factors when available
|
|
224
|
+
let displayMultiplier = self.cameraController.getDisplayZoomMultiplier()
|
|
225
|
+
var teleStep: Float
|
|
226
|
+
|
|
227
|
+
if #available(iOS 13.0, *) {
|
|
228
|
+
let switchFactors = device.virtualDeviceSwitchOverVideoZoomFactors
|
|
229
|
+
if !switchFactors.isEmpty {
|
|
230
|
+
// Choose the highest switch-over factor (typically the wide->tele threshold)
|
|
231
|
+
let maxSwitch = switchFactors.map { $0.floatValue }.max() ?? Float(device.maxAvailableVideoZoomFactor)
|
|
232
|
+
teleStep = maxSwitch * displayMultiplier
|
|
233
|
+
} else {
|
|
234
|
+
teleStep = Float(device.maxAvailableVideoZoomFactor) * displayMultiplier
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
teleStep = Float(device.maxAvailableVideoZoomFactor) * displayMultiplier
|
|
238
|
+
}
|
|
239
|
+
values.append(teleStep)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Deduplicate and sort
|
|
243
|
+
let uniqueSorted = Array(Set(values)).sorted()
|
|
244
|
+
call.resolve(["values": uniqueSorted])
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private func isProModelSupportingOptical2x() -> Bool {
|
|
248
|
+
// Detects iPhone 14 Pro/Pro Max, 15 Pro/Pro Max, and 16 Pro/Pro Max
|
|
249
|
+
var systemInfo = utsname()
|
|
250
|
+
uname(&systemInfo)
|
|
251
|
+
let mirror = Mirror(reflecting: systemInfo.machine)
|
|
252
|
+
let identifier = mirror.children.reduce("") { partialResult, element in
|
|
253
|
+
guard let value = element.value as? Int8, value != 0 else { return partialResult }
|
|
254
|
+
return partialResult + String(UnicodeScalar(UInt8(value)))
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Known identifiers: 14 Pro (iPhone15,2), 14 Pro Max (iPhone15,3),
|
|
258
|
+
// 15 Pro (iPhone16,1), 15 Pro Max (iPhone16,2),
|
|
259
|
+
// 16 Pro (iPhone17,1), 16 Pro Max (iPhone17,2),
|
|
260
|
+
// 17 Pro (iPhone18,1), 17 Pro Max (iPhone18,2)
|
|
261
|
+
let supportedIdentifiers: Set<String> = [
|
|
262
|
+
"iPhone15,2", "iPhone15,3", // 14 Pro / 14 Pro Max
|
|
263
|
+
"iPhone16,1", "iPhone16,2", // 15 Pro / 15 Pro Max
|
|
264
|
+
"iPhone17,1", "iPhone17,2" // 16 Pro / 16 Pro Max
|
|
265
|
+
]
|
|
266
|
+
return supportedIdentifiers.contains(identifier)
|
|
267
|
+
}
|
|
268
|
+
|
|
129
269
|
@objc func rotated() {
|
|
130
270
|
guard let previewView = self.previewView,
|
|
131
271
|
let posX = self.posX,
|
|
@@ -141,23 +281,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
141
281
|
// Always use the factorized method for consistent positioning
|
|
142
282
|
self.updateCameraFrame()
|
|
143
283
|
|
|
144
|
-
|
|
145
|
-
switch UIDevice.current.orientation {
|
|
146
|
-
case .landscapeRight:
|
|
147
|
-
connection.videoOrientation = .landscapeLeft
|
|
148
|
-
case .landscapeLeft:
|
|
149
|
-
connection.videoOrientation = .landscapeRight
|
|
150
|
-
case .portrait:
|
|
151
|
-
connection.videoOrientation = .portrait
|
|
152
|
-
case .portraitUpsideDown:
|
|
153
|
-
connection.videoOrientation = .portraitUpsideDown
|
|
154
|
-
default:
|
|
155
|
-
connection.videoOrientation = .portrait
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
cameraController.updateVideoOrientation()
|
|
160
|
-
|
|
284
|
+
// Centralize orientation update to use interface orientation consistently
|
|
161
285
|
cameraController.updateVideoOrientation()
|
|
162
286
|
|
|
163
287
|
// Update grid overlay frame if it exists - no animation
|
|
@@ -186,61 +310,54 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
186
310
|
}
|
|
187
311
|
|
|
188
312
|
self.aspectRatio = newAspectRatio
|
|
189
|
-
|
|
190
313
|
DispatchQueue.main.async {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
self.posY = -1
|
|
195
|
-
|
|
196
|
-
// Calculate maximum size based on aspect ratio
|
|
197
|
-
let webViewWidth = self.webView?.frame.width ?? UIScreen.main.bounds.width
|
|
198
|
-
let webViewHeight = self.webView?.frame.height ?? UIScreen.main.bounds.height
|
|
199
|
-
let paddingBottom = self.paddingBottom ?? 0
|
|
200
|
-
|
|
201
|
-
// Calculate available space
|
|
202
|
-
let availableWidth: CGFloat
|
|
203
|
-
let availableHeight: CGFloat
|
|
204
|
-
|
|
205
|
-
if self.posX == -1 || self.posY == -1 {
|
|
206
|
-
// Auto-centering mode - use full dimensions
|
|
207
|
-
availableWidth = webViewWidth
|
|
208
|
-
availableHeight = webViewHeight - paddingBottom
|
|
209
|
-
} else {
|
|
210
|
-
// Manual positioning - calculate remaining space
|
|
211
|
-
availableWidth = webViewWidth - self.posX!
|
|
212
|
-
availableHeight = webViewHeight - self.posY! - paddingBottom
|
|
213
|
-
}
|
|
314
|
+
call.resolve(self.rawSetAspectRatio())
|
|
315
|
+
}
|
|
316
|
+
}
|
|
214
317
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
318
|
+
func rawSetAspectRatio() -> JSObject {
|
|
319
|
+
// When aspect ratio changes, always auto-center the view
|
|
320
|
+
// This ensures consistent behavior where changing aspect ratio recenters the view
|
|
321
|
+
self.posX = -1
|
|
322
|
+
self.posY = -1
|
|
219
323
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
324
|
+
// Calculate maximum size based on aspect ratio
|
|
325
|
+
let webViewWidth = self.webView?.frame.width ?? UIScreen.main.bounds.width
|
|
326
|
+
let webViewHeight = self.webView?.frame.height ?? UIScreen.main.bounds.height
|
|
327
|
+
let paddingBottom = self.paddingBottom ?? 0
|
|
328
|
+
let isPortrait = self.isPortrait()
|
|
223
329
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
self.height = availableHeight
|
|
228
|
-
} else {
|
|
229
|
-
// Width is the limiting factor
|
|
230
|
-
self.width = availableWidth
|
|
231
|
-
self.height = maxHeightByWidth
|
|
232
|
-
}
|
|
330
|
+
// Calculate available space
|
|
331
|
+
let availableWidth: CGFloat
|
|
332
|
+
let availableHeight: CGFloat
|
|
233
333
|
|
|
234
|
-
|
|
334
|
+
if self.posX == -1 || self.posY == -1 {
|
|
335
|
+
// Auto-centering mode - use full dimensions
|
|
336
|
+
availableWidth = webViewWidth
|
|
337
|
+
availableHeight = webViewHeight - paddingBottom
|
|
338
|
+
} else {
|
|
339
|
+
// Manual positioning - calculate remaining space
|
|
340
|
+
availableWidth = webViewWidth - self.posX!
|
|
341
|
+
availableHeight = webViewHeight - self.posY! - paddingBottom
|
|
342
|
+
}
|
|
235
343
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
call.resolve(result)
|
|
344
|
+
// Parse aspect ratio - convert to portrait orientation for camera use
|
|
345
|
+
// Use the centralized calculation method
|
|
346
|
+
if let aspectRatio = self.aspectRatio {
|
|
347
|
+
let dimensions = calculateDimensionsForAspectRatio(aspectRatio, availableWidth: availableWidth, availableHeight: availableHeight, isPortrait: isPortrait)
|
|
348
|
+
self.width = dimensions.width
|
|
349
|
+
self.height = dimensions.height
|
|
243
350
|
}
|
|
351
|
+
|
|
352
|
+
self.updateCameraFrame()
|
|
353
|
+
|
|
354
|
+
// Return the actual preview bounds
|
|
355
|
+
var result = JSObject()
|
|
356
|
+
result["x"] = Double(self.previewView.frame.origin.x)
|
|
357
|
+
result["y"] = Double(self.previewView.frame.origin.y)
|
|
358
|
+
result["width"] = Double(self.previewView.frame.width)
|
|
359
|
+
result["height"] = Double(self.previewView.frame.height)
|
|
360
|
+
return result
|
|
244
361
|
}
|
|
245
362
|
|
|
246
363
|
@objc func getAspectRatio(_ call: CAPPluginCall) {
|
|
@@ -385,6 +502,26 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
385
502
|
let startTime = CFAbsoluteTimeGetCurrent()
|
|
386
503
|
print("[CameraPreview] 🚀 START CALLED at \(Date())")
|
|
387
504
|
|
|
505
|
+
// Log all received settings
|
|
506
|
+
print("[CameraPreview] 📋 Settings received:")
|
|
507
|
+
print(" - position: \(call.getString("position") ?? "rear")")
|
|
508
|
+
print(" - deviceId: \(call.getString("deviceId") ?? "nil")")
|
|
509
|
+
print(" - cameraMode: \(call.getBool("cameraMode") ?? false)")
|
|
510
|
+
print(" - width: \(call.getInt("width") ?? 0)")
|
|
511
|
+
print(" - height: \(call.getInt("height") ?? 0)")
|
|
512
|
+
print(" - x: \(call.getInt("x") ?? -1)")
|
|
513
|
+
print(" - y: \(call.getInt("y") ?? -1)")
|
|
514
|
+
print(" - paddingBottom: \(call.getInt("paddingBottom") ?? 0)")
|
|
515
|
+
print(" - rotateWhenOrientationChanged: \(call.getBool("rotateWhenOrientationChanged") ?? true)")
|
|
516
|
+
print(" - toBack: \(call.getBool("toBack") ?? true)")
|
|
517
|
+
print(" - storeToFile: \(call.getBool("storeToFile") ?? false)")
|
|
518
|
+
print(" - enableZoom: \(call.getBool("enableZoom") ?? false)")
|
|
519
|
+
print(" - disableAudio: \(call.getBool("disableAudio") ?? true)")
|
|
520
|
+
print(" - aspectRatio: \(call.getString("aspectRatio") ?? "4:3")")
|
|
521
|
+
print(" - gridMode: \(call.getString("gridMode") ?? "none")")
|
|
522
|
+
print(" - positioning: \(call.getString("positioning") ?? "top")")
|
|
523
|
+
print(" - initialZoomLevel: \(call.getFloat("initialZoomLevel") ?? 1.0)")
|
|
524
|
+
|
|
388
525
|
if self.isInitializing {
|
|
389
526
|
call.reject("camera initialization in progress")
|
|
390
527
|
return
|
|
@@ -435,15 +572,16 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
435
572
|
self.storeToFile = call.getBool("storeToFile") ?? false
|
|
436
573
|
self.enableZoom = call.getBool("enableZoom") ?? false
|
|
437
574
|
self.disableAudio = call.getBool("disableAudio") ?? true
|
|
438
|
-
|
|
575
|
+
// Default to 4:3 aspect ratio if not provided
|
|
576
|
+
self.aspectRatio = call.getString("aspectRatio") ?? "4:3"
|
|
439
577
|
self.gridMode = call.getString("gridMode") ?? "none"
|
|
440
578
|
self.positioning = call.getString("positioning") ?? "top"
|
|
441
579
|
|
|
442
|
-
let
|
|
443
|
-
let initialZoomLevel = userProvidedZoom ?? 1.5
|
|
580
|
+
let initialZoomLevel = call.getFloat("initialZoomLevel")
|
|
444
581
|
|
|
445
|
-
|
|
446
|
-
|
|
582
|
+
// Validate aspect ratio parameters using centralized method
|
|
583
|
+
if let validationError = validateAspectRatioParameters(aspectRatio: self.aspectRatio, width: call.getInt("width"), height: call.getInt("height")) {
|
|
584
|
+
call.reject(validationError)
|
|
447
585
|
return
|
|
448
586
|
}
|
|
449
587
|
|
|
@@ -457,13 +595,19 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
457
595
|
if self.cameraController.captureSession?.isRunning ?? false {
|
|
458
596
|
call.reject("camera already started")
|
|
459
597
|
} else {
|
|
460
|
-
self.cameraController.prepare(cameraPosition: self.cameraPosition, deviceId: deviceId, disableAudio: self.disableAudio, cameraMode: cameraMode, aspectRatio: self.aspectRatio, initialZoomLevel:
|
|
598
|
+
self.cameraController.prepare(cameraPosition: self.cameraPosition, deviceId: deviceId, disableAudio: self.disableAudio, cameraMode: cameraMode, aspectRatio: self.aspectRatio, initialZoomLevel: initialZoomLevel) {error in
|
|
461
599
|
if let error = error {
|
|
462
600
|
print(error)
|
|
463
601
|
call.reject(error.localizedDescription)
|
|
464
602
|
return
|
|
465
603
|
}
|
|
604
|
+
|
|
466
605
|
DispatchQueue.main.async {
|
|
606
|
+
UIDevice.current.beginGeneratingDeviceOrientationNotifications()
|
|
607
|
+
NotificationCenter.default.addObserver(self,
|
|
608
|
+
selector: #selector(self.handleOrientationChange),
|
|
609
|
+
name: UIDevice.orientationDidChangeNotification,
|
|
610
|
+
object: nil)
|
|
467
611
|
self.completeStartCamera(call: call)
|
|
468
612
|
}
|
|
469
613
|
}
|
|
@@ -487,6 +631,9 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
487
631
|
// Display the camera preview on the configured view
|
|
488
632
|
try? self.cameraController.displayPreview(on: self.previewView)
|
|
489
633
|
|
|
634
|
+
// Ensure the preview orientation matches the current interface orientation at startup
|
|
635
|
+
self.cameraController.updateVideoOrientation()
|
|
636
|
+
|
|
490
637
|
self.cameraController.setupGestures(target: self.previewView, enableZoom: self.enableZoom!)
|
|
491
638
|
|
|
492
639
|
// Add grid overlay if enabled
|
|
@@ -519,14 +666,16 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
519
666
|
}
|
|
520
667
|
}
|
|
521
668
|
|
|
522
|
-
// If already received first frame (unlikely but possible), resolve immediately
|
|
669
|
+
// If already received first frame (unlikely but possible), resolve immediately on main thread
|
|
523
670
|
if self.cameraController.hasReceivedFirstFrame {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
671
|
+
DispatchQueue.main.async {
|
|
672
|
+
var returnedObject = JSObject()
|
|
673
|
+
returnedObject["width"] = self.previewView.frame.width as any JSValue
|
|
674
|
+
returnedObject["height"] = self.previewView.frame.height as any JSValue
|
|
675
|
+
returnedObject["x"] = self.previewView.frame.origin.x as any JSValue
|
|
676
|
+
returnedObject["y"] = self.previewView.frame.origin.y as any JSValue
|
|
677
|
+
call.resolve(returnedObject)
|
|
678
|
+
}
|
|
530
679
|
}
|
|
531
680
|
}
|
|
532
681
|
|
|
@@ -589,6 +738,9 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
589
738
|
// Remove notification observers
|
|
590
739
|
NotificationCenter.default.removeObserver(self)
|
|
591
740
|
|
|
741
|
+
NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
|
|
742
|
+
UIDevice.current.endGeneratingDeviceOrientationNotifications()
|
|
743
|
+
|
|
592
744
|
call.resolve()
|
|
593
745
|
}
|
|
594
746
|
}
|
|
@@ -685,10 +837,10 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
685
837
|
|
|
686
838
|
print("[CameraPreview] Raw parameter values - width: \(String(describing: width)), height: \(String(describing: height)), aspectRatio: \(String(describing: aspectRatio))")
|
|
687
839
|
|
|
688
|
-
// Check for conflicting parameters
|
|
689
|
-
if
|
|
690
|
-
print("[CameraPreview] Error:
|
|
691
|
-
call.reject(
|
|
840
|
+
// Check for conflicting parameters using centralized validation
|
|
841
|
+
if let validationError = validateAspectRatioParameters(aspectRatio: aspectRatio, width: width, height: height) {
|
|
842
|
+
print("[CameraPreview] Error: \(validationError)")
|
|
843
|
+
call.reject(validationError)
|
|
692
844
|
return
|
|
693
845
|
}
|
|
694
846
|
|
|
@@ -703,9 +855,22 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
703
855
|
|
|
704
856
|
print("[CameraPreview] Capture params - quality: \(quality), saveToGallery: \(saveToGallery), withExifLocation: \(withExifLocation), width: \(width ?? -1), height: \(height ?? -1), aspectRatio: \(aspectRatio ?? "nil"), using aspectRatio: \(captureAspectRatio ?? "nil")")
|
|
705
857
|
print("[CameraPreview] Current location: \(self.currentLocation?.description ?? "nil")")
|
|
706
|
-
|
|
858
|
+
// Safely read frame from main thread for logging
|
|
859
|
+
let (previewWidth, previewHeight): (CGFloat, CGFloat) = {
|
|
860
|
+
if Thread.isMainThread {
|
|
861
|
+
return (self.previewView.frame.width, self.previewView.frame.height)
|
|
862
|
+
}
|
|
863
|
+
var w: CGFloat = 0
|
|
864
|
+
var h: CGFloat = 0
|
|
865
|
+
DispatchQueue.main.sync {
|
|
866
|
+
w = self.previewView.frame.width
|
|
867
|
+
h = self.previewView.frame.height
|
|
868
|
+
}
|
|
869
|
+
return (w, h)
|
|
870
|
+
}()
|
|
871
|
+
print("[CameraPreview] Preview dimensions: \(previewWidth)x\(previewHeight)")
|
|
707
872
|
|
|
708
|
-
self.cameraController.captureImage(width: width, height: height, aspectRatio: captureAspectRatio, quality: quality, gpsLocation: self.currentLocation) { (image, error) in
|
|
873
|
+
self.cameraController.captureImage(width: width, height: height, aspectRatio: captureAspectRatio, quality: quality, gpsLocation: self.currentLocation) { (image, originalPhotoData, _, error) in
|
|
709
874
|
print("[CameraPreview] captureImage callback received")
|
|
710
875
|
DispatchQueue.main.async {
|
|
711
876
|
print("[CameraPreview] Processing capture on main thread")
|
|
@@ -719,7 +884,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
719
884
|
let imageDataWithExif = self.createImageDataWithExif(
|
|
720
885
|
from: image,
|
|
721
886
|
quality: Int(quality),
|
|
722
|
-
location: withExifLocation ? self.currentLocation : nil
|
|
887
|
+
location: withExifLocation ? self.currentLocation : nil,
|
|
888
|
+
originalPhotoData: originalPhotoData
|
|
723
889
|
)
|
|
724
890
|
else {
|
|
725
891
|
print("[CameraPreview] Failed to create image data with EXIF")
|
|
@@ -734,30 +900,56 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
734
900
|
self.saveImageDataToGallery(imageData: imageDataWithExif) { success, error in
|
|
735
901
|
print("[CameraPreview] Save to gallery completed, success: \(success), error: \(error?.localizedDescription ?? "none")")
|
|
736
902
|
let exifData = self.getExifData(from: imageDataWithExif)
|
|
737
|
-
let base64Image = imageDataWithExif.base64EncodedString()
|
|
738
903
|
|
|
739
904
|
var result = JSObject()
|
|
740
|
-
result["value"] = base64Image
|
|
741
905
|
result["exif"] = exifData
|
|
742
906
|
result["gallerySaved"] = success
|
|
743
907
|
if !success, let error = error {
|
|
744
908
|
result["galleryError"] = error.localizedDescription
|
|
745
909
|
}
|
|
746
910
|
|
|
911
|
+
if self.storeToFile == false {
|
|
912
|
+
let base64Image = imageDataWithExif.base64EncodedString()
|
|
913
|
+
result["value"] = base64Image
|
|
914
|
+
} else {
|
|
915
|
+
do {
|
|
916
|
+
let fileUrl = self.getTempFilePath()
|
|
917
|
+
try imageDataWithExif.write(to: fileUrl)
|
|
918
|
+
result["value"] = fileUrl.absoluteString
|
|
919
|
+
} catch {
|
|
920
|
+
call.reject("Error writing image to file")
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
747
924
|
print("[CameraPreview] Resolving capture call with gallery save")
|
|
748
925
|
call.resolve(result)
|
|
749
926
|
}
|
|
750
927
|
} else {
|
|
751
928
|
print("[CameraPreview] Not saving to gallery, returning image data")
|
|
752
929
|
let exifData = self.getExifData(from: imageDataWithExif)
|
|
753
|
-
let base64Image = imageDataWithExif.base64EncodedString()
|
|
754
930
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
931
|
+
if self.storeToFile == false {
|
|
932
|
+
let base64Image = imageDataWithExif.base64EncodedString()
|
|
933
|
+
var result = JSObject()
|
|
934
|
+
result["value"] = base64Image
|
|
935
|
+
result["exif"] = exifData
|
|
936
|
+
|
|
937
|
+
print("[CameraPreview] base64 - Resolving capture call")
|
|
938
|
+
call.resolve(result)
|
|
939
|
+
} else {
|
|
940
|
+
do {
|
|
941
|
+
let fileUrl = self.getTempFilePath()
|
|
942
|
+
try imageDataWithExif.write(to: fileUrl)
|
|
943
|
+
var result = JSObject()
|
|
944
|
+
result["value"] = fileUrl.absoluteString
|
|
945
|
+
result["exif"] = exifData
|
|
946
|
+
print("[CameraPreview] filePath - Resolving capture call")
|
|
947
|
+
call.resolve(result)
|
|
948
|
+
} catch {
|
|
949
|
+
call.reject("Error writing image to file")
|
|
950
|
+
}
|
|
951
|
+
}
|
|
758
952
|
|
|
759
|
-
print("[CameraPreview] Resolving capture call")
|
|
760
|
-
call.resolve(result)
|
|
761
953
|
}
|
|
762
954
|
}
|
|
763
955
|
}
|
|
@@ -792,24 +984,85 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
792
984
|
return exifData
|
|
793
985
|
}
|
|
794
986
|
|
|
795
|
-
|
|
796
|
-
|
|
987
|
+
@objc func getSafeAreaInsets(_ call: CAPPluginCall) {
|
|
988
|
+
DispatchQueue.main.async {
|
|
989
|
+
var notchInset: CGFloat = 0
|
|
990
|
+
var orientation: Int = 0
|
|
991
|
+
|
|
992
|
+
// Get the current interface orientation
|
|
993
|
+
let interfaceOrientation: UIInterfaceOrientation? = {
|
|
994
|
+
return (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.interfaceOrientation
|
|
995
|
+
}()
|
|
996
|
+
|
|
997
|
+
// Convert to orientation number (matching Android values for consistency)
|
|
998
|
+
switch interfaceOrientation {
|
|
999
|
+
case .portrait, .portraitUpsideDown:
|
|
1000
|
+
orientation = 1 // Portrait
|
|
1001
|
+
case .landscapeLeft, .landscapeRight:
|
|
1002
|
+
orientation = 2 // Landscape
|
|
1003
|
+
default:
|
|
1004
|
+
orientation = 0 // Unknown
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// Get safe area insets
|
|
1008
|
+
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
|
1009
|
+
let window = windowScene.windows.first {
|
|
1010
|
+
let safeAreaInsets = window.safeAreaInsets
|
|
1011
|
+
|
|
1012
|
+
switch interfaceOrientation {
|
|
1013
|
+
case .portrait:
|
|
1014
|
+
// Portrait: notch is at the top
|
|
1015
|
+
notchInset = safeAreaInsets.top
|
|
1016
|
+
case .portraitUpsideDown:
|
|
1017
|
+
// Portrait upside down: notch is at the bottom (but we still call it "top" for consistency)
|
|
1018
|
+
notchInset = safeAreaInsets.bottom
|
|
1019
|
+
case .landscapeLeft:
|
|
1020
|
+
// Landscape left: notch is typically on the left
|
|
1021
|
+
notchInset = safeAreaInsets.left
|
|
1022
|
+
case .landscapeRight:
|
|
1023
|
+
// Landscape right: notch is typically on the right (but we use left for consistency with Android)
|
|
1024
|
+
notchInset = safeAreaInsets.right
|
|
1025
|
+
default:
|
|
1026
|
+
// Unknown orientation, default to top
|
|
1027
|
+
notchInset = safeAreaInsets.top
|
|
1028
|
+
}
|
|
1029
|
+
} else {
|
|
1030
|
+
// Fallback: use status bar height as approximation
|
|
1031
|
+
notchInset = UIApplication.shared.statusBarFrame.height
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
let result: [String: Any] = [
|
|
1035
|
+
"orientation": orientation,
|
|
1036
|
+
"top": Double(notchInset)
|
|
1037
|
+
]
|
|
1038
|
+
|
|
1039
|
+
call.resolve(result)
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
private func createImageDataWithExif(from image: UIImage, quality: Int, location: CLLocation?, originalPhotoData: Data?) -> Data? {
|
|
1044
|
+
guard let jpegDataAtQuality = image.jpegData(compressionQuality: CGFloat(Double(quality) / 100.0)) else {
|
|
797
1045
|
return nil
|
|
798
1046
|
}
|
|
799
1047
|
|
|
800
|
-
|
|
1048
|
+
// Prefer metadata from the original AVCapturePhoto file data to preserve lens/EXIF
|
|
1049
|
+
let sourceDataForMetadata = (originalPhotoData ?? jpegDataAtQuality) as CFData
|
|
1050
|
+
guard let imageSource = CGImageSourceCreateWithData(sourceDataForMetadata, nil),
|
|
801
1051
|
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [String: Any],
|
|
802
1052
|
let cgImage = image.cgImage else {
|
|
803
|
-
return
|
|
1053
|
+
return jpegDataAtQuality
|
|
804
1054
|
}
|
|
805
1055
|
|
|
806
1056
|
let mutableData = NSMutableData()
|
|
807
1057
|
guard let destination = CGImageDestinationCreateWithData(mutableData, kUTTypeJPEG, 1, nil) else {
|
|
808
|
-
return
|
|
1058
|
+
return jpegDataAtQuality
|
|
809
1059
|
}
|
|
810
1060
|
|
|
811
1061
|
var finalProperties = imageProperties
|
|
812
1062
|
|
|
1063
|
+
// Ensure orientation reflects the pixel data (we pass an orientation-fixed UIImage)
|
|
1064
|
+
finalProperties[kCGImagePropertyOrientation as String] = 1
|
|
1065
|
+
|
|
813
1066
|
// Add GPS location if available
|
|
814
1067
|
if let location = location {
|
|
815
1068
|
let formatter = DateFormatter()
|
|
@@ -829,10 +1082,11 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
829
1082
|
finalProperties[kCGImagePropertyGPSDictionary as String] = gpsDict
|
|
830
1083
|
}
|
|
831
1084
|
|
|
832
|
-
// Create or update TIFF dictionary for device info
|
|
1085
|
+
// Create or update TIFF dictionary for device info and set orientation to Up
|
|
833
1086
|
var tiffDict = finalProperties[kCGImagePropertyTIFFDictionary as String] as? [String: Any] ?? [:]
|
|
834
1087
|
tiffDict[kCGImagePropertyTIFFMake as String] = "Apple"
|
|
835
1088
|
tiffDict[kCGImagePropertyTIFFModel as String] = UIDevice.current.model
|
|
1089
|
+
tiffDict[kCGImagePropertyTIFFOrientation as String] = 1
|
|
836
1090
|
finalProperties[kCGImagePropertyTIFFDictionary as String] = tiffDict
|
|
837
1091
|
|
|
838
1092
|
CGImageDestinationAddImage(destination, cgImage, finalProperties as CFDictionary)
|
|
@@ -841,7 +1095,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
841
1095
|
return mutableData as Data
|
|
842
1096
|
}
|
|
843
1097
|
|
|
844
|
-
return
|
|
1098
|
+
return jpegDataAtQuality
|
|
845
1099
|
}
|
|
846
1100
|
|
|
847
1101
|
@objc func captureSample(_ call: CAPPluginCall) {
|
|
@@ -1036,16 +1290,17 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1036
1290
|
do {
|
|
1037
1291
|
let zoomInfo = try self.cameraController.getZoom()
|
|
1038
1292
|
let lensInfo = try self.cameraController.getCurrentLensInfo()
|
|
1293
|
+
let displayMultiplier = self.cameraController.getDisplayZoomMultiplier()
|
|
1039
1294
|
|
|
1040
1295
|
var minZoom = zoomInfo.min
|
|
1041
1296
|
var maxZoom = zoomInfo.max
|
|
1042
1297
|
var currentZoom = zoomInfo.current
|
|
1043
1298
|
|
|
1044
|
-
//
|
|
1045
|
-
if
|
|
1046
|
-
minZoom
|
|
1047
|
-
maxZoom
|
|
1048
|
-
currentZoom
|
|
1299
|
+
// Apply iOS 18+ display multiplier so UI sees the expected values
|
|
1300
|
+
if displayMultiplier != 1.0 {
|
|
1301
|
+
minZoom *= displayMultiplier
|
|
1302
|
+
maxZoom *= displayMultiplier
|
|
1303
|
+
currentZoom *= displayMultiplier
|
|
1049
1304
|
}
|
|
1050
1305
|
|
|
1051
1306
|
call.resolve([
|
|
@@ -1076,12 +1331,14 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1076
1331
|
}
|
|
1077
1332
|
|
|
1078
1333
|
// If using the multi-lens camera, translate the JS zoom value for the native layer
|
|
1079
|
-
|
|
1080
|
-
|
|
1334
|
+
// First, convert from UI/display zoom to native zoom using the iOS 18 multiplier
|
|
1335
|
+
let displayMultiplier = self.cameraController.getDisplayZoomMultiplier()
|
|
1336
|
+
if displayMultiplier != 1.0 {
|
|
1337
|
+
level = level / displayMultiplier
|
|
1081
1338
|
}
|
|
1082
1339
|
|
|
1083
1340
|
let ramp = call.getBool("ramp") ?? true
|
|
1084
|
-
let autoFocus = call.getBool("autoFocus") ??
|
|
1341
|
+
let autoFocus = call.getBool("autoFocus") ?? false
|
|
1085
1342
|
|
|
1086
1343
|
do {
|
|
1087
1344
|
try self.cameraController.setZoom(level: CGFloat(level), ramp: ramp, autoFocus: autoFocus)
|
|
@@ -1326,6 +1583,26 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1326
1583
|
}
|
|
1327
1584
|
}
|
|
1328
1585
|
|
|
1586
|
+
private func isPortrait() -> Bool {
|
|
1587
|
+
let orientation = UIDevice.current.orientation
|
|
1588
|
+
if orientation.isValidInterfaceOrientation {
|
|
1589
|
+
return orientation.isPortrait
|
|
1590
|
+
} else {
|
|
1591
|
+
let interfaceOrientation: UIInterfaceOrientation? = {
|
|
1592
|
+
if Thread.isMainThread {
|
|
1593
|
+
return (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.interfaceOrientation
|
|
1594
|
+
} else {
|
|
1595
|
+
var value: UIInterfaceOrientation?
|
|
1596
|
+
DispatchQueue.main.sync {
|
|
1597
|
+
value = (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.interfaceOrientation
|
|
1598
|
+
}
|
|
1599
|
+
return value
|
|
1600
|
+
}
|
|
1601
|
+
}()
|
|
1602
|
+
return interfaceOrientation?.isPortrait ?? false
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1329
1606
|
private func calculateCameraFrame(x: CGFloat? = nil, y: CGFloat? = nil, width: CGFloat? = nil, height: CGFloat? = nil, aspectRatio: String? = nil) -> CGRect {
|
|
1330
1607
|
// Use provided values or existing ones
|
|
1331
1608
|
let currentWidth = width ?? self.width ?? UIScreen.main.bounds.size.width
|
|
@@ -1341,6 +1618,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1341
1618
|
let webViewWidth = self.webView?.frame.width ?? UIScreen.main.bounds.width
|
|
1342
1619
|
let webViewHeight = self.webView?.frame.height ?? UIScreen.main.bounds.height
|
|
1343
1620
|
|
|
1621
|
+
let isPortrait = self.isPortrait()
|
|
1622
|
+
|
|
1344
1623
|
var finalX = currentX
|
|
1345
1624
|
var finalY = currentY
|
|
1346
1625
|
var finalWidth = currentWidth
|
|
@@ -1354,12 +1633,20 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1354
1633
|
currentHeight == UIScreen.main.bounds.size.height {
|
|
1355
1634
|
finalWidth = webViewWidth
|
|
1356
1635
|
|
|
1357
|
-
//
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1636
|
+
// width: 428.0 height: 926.0 - portrait
|
|
1637
|
+
|
|
1638
|
+
print("[CameraPreview] width: \(UIScreen.main.bounds.size.width) height: \(UIScreen.main.bounds.size.height)")
|
|
1639
|
+
|
|
1640
|
+
// Calculate dimensions using centralized method
|
|
1641
|
+
let dimensions = calculateDimensionsForAspectRatio(ratio, availableWidth: finalWidth, availableHeight: webViewHeight - paddingBottom, isPortrait: isPortrait)
|
|
1642
|
+
if isPortrait {
|
|
1643
|
+
finalHeight = dimensions.height
|
|
1644
|
+
finalWidth = dimensions.width
|
|
1645
|
+
} else {
|
|
1646
|
+
// In landscape, recalculate based on available space
|
|
1647
|
+
let landscapeDimensions = calculateDimensionsForAspectRatio(ratio, availableWidth: webViewWidth, availableHeight: webViewHeight - paddingBottom, isPortrait: isPortrait)
|
|
1648
|
+
finalWidth = landscapeDimensions.width
|
|
1649
|
+
finalHeight = landscapeDimensions.height
|
|
1363
1650
|
}
|
|
1364
1651
|
}
|
|
1365
1652
|
|
|
@@ -1371,9 +1658,11 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1371
1658
|
}
|
|
1372
1659
|
|
|
1373
1660
|
// Position vertically if y is -1
|
|
1661
|
+
// TODO: fix top, bottom for landscape
|
|
1374
1662
|
if currentY == -1 {
|
|
1375
1663
|
// Use full screen height for positioning
|
|
1376
1664
|
let screenHeight = UIScreen.main.bounds.size.height
|
|
1665
|
+
let screenWidth = UIScreen.main.bounds.size.width
|
|
1377
1666
|
switch self.positioning {
|
|
1378
1667
|
case "top":
|
|
1379
1668
|
finalY = 0
|
|
@@ -1382,8 +1671,14 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1382
1671
|
finalY = screenHeight - finalHeight
|
|
1383
1672
|
print("[CameraPreview] Positioning at bottom: screenHeight=\(screenHeight), finalHeight=\(finalHeight), finalY=\(finalY)")
|
|
1384
1673
|
default: // "center"
|
|
1385
|
-
|
|
1386
|
-
|
|
1674
|
+
if isPortrait {
|
|
1675
|
+
finalY = (screenHeight - finalHeight) / 2
|
|
1676
|
+
print("[CameraPreview] Centering vertically: screenHeight=\(screenHeight), finalHeight=\(finalHeight), finalY=\(finalY)")
|
|
1677
|
+
} else {
|
|
1678
|
+
// In landscape, center both horizontally and vertically
|
|
1679
|
+
finalY = (screenHeight - finalHeight) / 2
|
|
1680
|
+
finalX = (screenWidth - finalWidth) / 2
|
|
1681
|
+
}
|
|
1387
1682
|
}
|
|
1388
1683
|
} else {
|
|
1389
1684
|
finalY = currentY
|
|
@@ -1411,21 +1706,18 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1411
1706
|
|
|
1412
1707
|
// Apply aspect ratio adjustments only if not auto-centering
|
|
1413
1708
|
if posX != -1 && posY != -1, let aspectRatio = self.aspectRatio {
|
|
1414
|
-
let
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
frame.origin.y = frame.origin.y + (frame.height - CGFloat(newHeight)) / 2
|
|
1427
|
-
frame.size.height = CGFloat(newHeight)
|
|
1428
|
-
}
|
|
1709
|
+
let isPortrait = self.isPortrait()
|
|
1710
|
+
let ratio = parseAspectRatio(aspectRatio, isPortrait: isPortrait)
|
|
1711
|
+
let currentRatio = frame.width / frame.height
|
|
1712
|
+
|
|
1713
|
+
if currentRatio > ratio {
|
|
1714
|
+
let newWidth = frame.height * ratio
|
|
1715
|
+
frame.origin.x = frame.origin.x + (frame.width - newWidth) / 2
|
|
1716
|
+
frame.size.width = newWidth
|
|
1717
|
+
} else {
|
|
1718
|
+
let newHeight = frame.width / ratio
|
|
1719
|
+
frame.origin.y = frame.origin.y + (frame.height - newHeight) / 2
|
|
1720
|
+
frame.size.height = newHeight
|
|
1429
1721
|
}
|
|
1430
1722
|
}
|
|
1431
1723
|
|
|
@@ -1545,4 +1837,66 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1545
1837
|
}
|
|
1546
1838
|
}
|
|
1547
1839
|
}
|
|
1840
|
+
|
|
1841
|
+
@objc private func handleOrientationChange() {
|
|
1842
|
+
DispatchQueue.main.async {
|
|
1843
|
+
let result = self.rawSetAspectRatio()
|
|
1844
|
+
self.notifyListeners("screenResize", data: result)
|
|
1845
|
+
self.notifyListeners("orientationChange", data: ["orientation": self.currentOrientationString()])
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
@objc func deleteFile(_ call: CAPPluginCall) {
|
|
1850
|
+
guard let path = call.getString("path"), !path.isEmpty else {
|
|
1851
|
+
call.reject("path parameter is required")
|
|
1852
|
+
return
|
|
1853
|
+
}
|
|
1854
|
+
let url: URL?
|
|
1855
|
+
if path.hasPrefix("file://") {
|
|
1856
|
+
url = URL(string: path)
|
|
1857
|
+
} else {
|
|
1858
|
+
url = URL(fileURLWithPath: path)
|
|
1859
|
+
}
|
|
1860
|
+
guard let fileURL = url else {
|
|
1861
|
+
call.reject("Invalid path")
|
|
1862
|
+
return
|
|
1863
|
+
}
|
|
1864
|
+
do {
|
|
1865
|
+
if FileManager.default.fileExists(atPath: fileURL.path) {
|
|
1866
|
+
try FileManager.default.removeItem(at: fileURL)
|
|
1867
|
+
call.resolve(["success": true])
|
|
1868
|
+
} else {
|
|
1869
|
+
call.resolve(["success": false])
|
|
1870
|
+
}
|
|
1871
|
+
} catch {
|
|
1872
|
+
call.reject("Failed to delete file: \(error.localizedDescription)")
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
// MARK: - Orientation
|
|
1877
|
+
private func currentOrientationString() -> String {
|
|
1878
|
+
// Prefer interface orientation for UI-consistent results
|
|
1879
|
+
let orientation: UIInterfaceOrientation? = {
|
|
1880
|
+
if Thread.isMainThread {
|
|
1881
|
+
return (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.interfaceOrientation
|
|
1882
|
+
} else {
|
|
1883
|
+
var value: UIInterfaceOrientation?
|
|
1884
|
+
DispatchQueue.main.sync {
|
|
1885
|
+
value = (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.interfaceOrientation
|
|
1886
|
+
}
|
|
1887
|
+
return value
|
|
1888
|
+
}
|
|
1889
|
+
}()
|
|
1890
|
+
switch orientation {
|
|
1891
|
+
case .portrait: return "portrait"
|
|
1892
|
+
case .portraitUpsideDown: return "portrait-upside-down"
|
|
1893
|
+
case .landscapeLeft: return "landscape-left"
|
|
1894
|
+
case .landscapeRight: return "landscape-right"
|
|
1895
|
+
default: return "unknown"
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
@objc func getOrientation(_ call: CAPPluginCall) {
|
|
1900
|
+
call.resolve(["orientation": self.currentOrientationString()])
|
|
1901
|
+
}
|
|
1548
1902
|
}
|