@capgo/camera-preview 7.4.0-alpha.1 → 7.4.0-alpha.13

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.
@@ -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,8 @@ 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)
69
71
  ]
70
72
  // Camera state tracking
71
73
  private var isInitializing: Bool = false
@@ -126,6 +128,59 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
126
128
  }
127
129
  }
128
130
 
131
+ @objc func getZoomButtonValues(_ call: CAPPluginCall) {
132
+ guard isInitialized else {
133
+ call.reject("Camera not initialized")
134
+ return
135
+ }
136
+
137
+ // Determine current device based on active position
138
+ var currentDevice: AVCaptureDevice?
139
+ switch self.cameraController.currentCameraPosition {
140
+ case .front:
141
+ currentDevice = self.cameraController.frontCamera
142
+ case .rear:
143
+ currentDevice = self.cameraController.rearCamera
144
+ default:
145
+ currentDevice = nil
146
+ }
147
+
148
+ guard let device = currentDevice else {
149
+ call.reject("No active camera device")
150
+ return
151
+ }
152
+
153
+ var hasUltraWide = false
154
+ var hasWide = false
155
+ var hasTele = false
156
+
157
+ let lenses = device.isVirtualDevice ? device.constituentDevices : [device]
158
+ for lens in lenses {
159
+ switch lens.deviceType {
160
+ case .builtInUltraWideCamera:
161
+ hasUltraWide = true
162
+ case .builtInWideAngleCamera:
163
+ hasWide = true
164
+ case .builtInTelephotoCamera:
165
+ hasTele = true
166
+ default:
167
+ break
168
+ }
169
+ }
170
+
171
+ var values: [Float] = []
172
+ if hasUltraWide { values.append(0.5) }
173
+ if hasWide {
174
+ values.append(1.0)
175
+ values.append(2.0)
176
+ }
177
+ if hasTele { values.append(3.0) }
178
+
179
+ // Deduplicate and sort
180
+ let uniqueSorted = Array(Set(values)).sorted()
181
+ call.resolve(["values": uniqueSorted])
182
+ }
183
+
129
184
  @objc func rotated() {
130
185
  guard let previewView = self.previewView,
131
186
  let posX = self.posX,
@@ -141,23 +196,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
141
196
  // Always use the factorized method for consistent positioning
142
197
  self.updateCameraFrame()
143
198
 
144
- if let connection = self.cameraController.fileVideoOutput?.connection(with: .video) {
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
-
199
+ // Centralize orientation update to use interface orientation consistently
161
200
  cameraController.updateVideoOrientation()
162
201
 
163
202
  // Update grid overlay frame if it exists - no animation
@@ -186,61 +225,66 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
186
225
  }
187
226
 
188
227
  self.aspectRatio = newAspectRatio
189
-
190
228
  DispatchQueue.main.async {
191
- // When aspect ratio changes, always auto-center the view
192
- // This ensures consistent behavior where changing aspect ratio recenters the view
193
- self.posX = -1
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
- }
229
+ call.resolve(self.rawSetAspectRatio())
230
+ }
231
+ }
214
232
 
215
- // Parse aspect ratio - convert to portrait orientation for camera use
216
- let ratioParts = newAspectRatio.split(separator: ":").map { Double($0) ?? 1.0 }
217
- // For camera, we want portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
218
- let ratio = ratioParts[1] / ratioParts[0]
233
+ func rawSetAspectRatio() -> JSObject {
234
+ // When aspect ratio changes, always auto-center the view
235
+ // This ensures consistent behavior where changing aspect ratio recenters the view
236
+ self.posX = -1
237
+ self.posY = -1
219
238
 
220
- // Calculate maximum size that fits the aspect ratio in available space
221
- let maxWidthByHeight = availableHeight * CGFloat(ratio)
222
- let maxHeightByWidth = availableWidth / CGFloat(ratio)
239
+ // Calculate maximum size based on aspect ratio
240
+ let webViewWidth = self.webView?.frame.width ?? UIScreen.main.bounds.width
241
+ let webViewHeight = self.webView?.frame.height ?? UIScreen.main.bounds.height
242
+ let paddingBottom = self.paddingBottom ?? 0
243
+ let isPortrait = self.isPortrait()
223
244
 
224
- if maxWidthByHeight <= availableWidth {
225
- // Height is the limiting factor
226
- self.width = maxWidthByHeight
227
- self.height = availableHeight
228
- } else {
229
- // Width is the limiting factor
230
- self.width = availableWidth
231
- self.height = maxHeightByWidth
232
- }
245
+ // Calculate available space
246
+ let availableWidth: CGFloat
247
+ let availableHeight: CGFloat
233
248
 
234
- self.updateCameraFrame()
249
+ if self.posX == -1 || self.posY == -1 {
250
+ // Auto-centering mode - use full dimensions
251
+ availableWidth = webViewWidth
252
+ availableHeight = webViewHeight - paddingBottom
253
+ } else {
254
+ // Manual positioning - calculate remaining space
255
+ availableWidth = webViewWidth - self.posX!
256
+ availableHeight = webViewHeight - self.posY! - paddingBottom
257
+ }
235
258
 
236
- // Return the actual preview bounds
237
- var result = JSObject()
238
- result["x"] = Double(self.previewView.frame.origin.x)
239
- result["y"] = Double(self.previewView.frame.origin.y)
240
- result["width"] = Double(self.previewView.frame.width)
241
- result["height"] = Double(self.previewView.frame.height)
242
- call.resolve(result)
259
+ // Parse aspect ratio - convert to portrait orientation for camera use
260
+ let ratioParts = self.aspectRatio?.split(separator: ":").map { Double($0) ?? 1.0 } ?? [1.0, 1.0]
261
+
262
+ // For camera (portrait), we want portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
263
+ let ratio = !isPortrait ? ratioParts[0] / ratioParts[1] : ratioParts[1] / ratioParts[0]
264
+
265
+ // Calculate maximum size that fits the aspect ratio in available space
266
+ let maxWidthByHeight = availableHeight * CGFloat(ratio)
267
+ let maxHeightByWidth = availableWidth / CGFloat(ratio)
268
+
269
+ if maxWidthByHeight <= availableWidth {
270
+ // Height is the limiting factor
271
+ self.width = maxWidthByHeight
272
+ self.height = availableHeight
273
+ } else {
274
+ // Width is the limiting factor
275
+ self.width = availableWidth
276
+ self.height = maxHeightByWidth
243
277
  }
278
+
279
+ self.updateCameraFrame()
280
+
281
+ // Return the actual preview bounds
282
+ var result = JSObject()
283
+ result["x"] = Double(self.previewView.frame.origin.x)
284
+ result["y"] = Double(self.previewView.frame.origin.y)
285
+ result["width"] = Double(self.previewView.frame.width)
286
+ result["height"] = Double(self.previewView.frame.height)
287
+ return result
244
288
  }
245
289
 
246
290
  @objc func getAspectRatio(_ call: CAPPluginCall) {
@@ -435,12 +479,12 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
435
479
  self.storeToFile = call.getBool("storeToFile") ?? false
436
480
  self.enableZoom = call.getBool("enableZoom") ?? false
437
481
  self.disableAudio = call.getBool("disableAudio") ?? true
438
- self.aspectRatio = call.getString("aspectRatio")
482
+ // Default to 4:3 aspect ratio if not provided
483
+ self.aspectRatio = call.getString("aspectRatio") ?? "4:3"
439
484
  self.gridMode = call.getString("gridMode") ?? "none"
440
485
  self.positioning = call.getString("positioning") ?? "top"
441
486
 
442
- let userProvidedZoom = call.getFloat("initialZoomLevel")
443
- let initialZoomLevel = userProvidedZoom ?? 1.5
487
+ let initialZoomLevel = call.getFloat("initialZoomLevel")
444
488
 
445
489
  if self.aspectRatio != nil && (call.getInt("width") != nil || call.getInt("height") != nil) {
446
490
  call.reject("Cannot set both aspectRatio and size (width/height). Use setPreviewSize after start.")
@@ -457,13 +501,19 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
457
501
  if self.cameraController.captureSession?.isRunning ?? false {
458
502
  call.reject("camera already started")
459
503
  } else {
460
- self.cameraController.prepare(cameraPosition: self.cameraPosition, deviceId: deviceId, disableAudio: self.disableAudio, cameraMode: cameraMode, aspectRatio: self.aspectRatio, initialZoomLevel: Float(initialZoomLevel)) {error in
504
+ self.cameraController.prepare(cameraPosition: self.cameraPosition, deviceId: deviceId, disableAudio: self.disableAudio, cameraMode: cameraMode, aspectRatio: self.aspectRatio, initialZoomLevel: initialZoomLevel) {error in
461
505
  if let error = error {
462
506
  print(error)
463
507
  call.reject(error.localizedDescription)
464
508
  return
465
509
  }
510
+
466
511
  DispatchQueue.main.async {
512
+ UIDevice.current.beginGeneratingDeviceOrientationNotifications()
513
+ NotificationCenter.default.addObserver(self,
514
+ selector: #selector(self.handleOrientationChange),
515
+ name: UIDevice.orientationDidChangeNotification,
516
+ object: nil)
467
517
  self.completeStartCamera(call: call)
468
518
  }
469
519
  }
@@ -487,6 +537,9 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
487
537
  // Display the camera preview on the configured view
488
538
  try? self.cameraController.displayPreview(on: self.previewView)
489
539
 
540
+ // Ensure the preview orientation matches the current interface orientation at startup
541
+ self.cameraController.updateVideoOrientation()
542
+
490
543
  self.cameraController.setupGestures(target: self.previewView, enableZoom: self.enableZoom!)
491
544
 
492
545
  // Add grid overlay if enabled
@@ -519,14 +572,16 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
519
572
  }
520
573
  }
521
574
 
522
- // If already received first frame (unlikely but possible), resolve immediately
575
+ // If already received first frame (unlikely but possible), resolve immediately on main thread
523
576
  if self.cameraController.hasReceivedFirstFrame {
524
- var returnedObject = JSObject()
525
- returnedObject["width"] = self.previewView.frame.width as any JSValue
526
- returnedObject["height"] = self.previewView.frame.height as any JSValue
527
- returnedObject["x"] = self.previewView.frame.origin.x as any JSValue
528
- returnedObject["y"] = self.previewView.frame.origin.y as any JSValue
529
- call.resolve(returnedObject)
577
+ DispatchQueue.main.async {
578
+ var returnedObject = JSObject()
579
+ returnedObject["width"] = self.previewView.frame.width as any JSValue
580
+ returnedObject["height"] = self.previewView.frame.height as any JSValue
581
+ returnedObject["x"] = self.previewView.frame.origin.x as any JSValue
582
+ returnedObject["y"] = self.previewView.frame.origin.y as any JSValue
583
+ call.resolve(returnedObject)
584
+ }
530
585
  }
531
586
  }
532
587
 
@@ -589,6 +644,9 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
589
644
  // Remove notification observers
590
645
  NotificationCenter.default.removeObserver(self)
591
646
 
647
+ NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
648
+ UIDevice.current.endGeneratingDeviceOrientationNotifications()
649
+
592
650
  call.resolve()
593
651
  }
594
652
  }
@@ -703,9 +761,22 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
703
761
 
704
762
  print("[CameraPreview] Capture params - quality: \(quality), saveToGallery: \(saveToGallery), withExifLocation: \(withExifLocation), width: \(width ?? -1), height: \(height ?? -1), aspectRatio: \(aspectRatio ?? "nil"), using aspectRatio: \(captureAspectRatio ?? "nil")")
705
763
  print("[CameraPreview] Current location: \(self.currentLocation?.description ?? "nil")")
706
- print("[CameraPreview] Preview dimensions: \(self.previewView.frame.width)x\(self.previewView.frame.height)")
764
+ // Safely read frame from main thread for logging
765
+ let (previewWidth, previewHeight): (CGFloat, CGFloat) = {
766
+ if Thread.isMainThread {
767
+ return (self.previewView.frame.width, self.previewView.frame.height)
768
+ }
769
+ var w: CGFloat = 0
770
+ var h: CGFloat = 0
771
+ DispatchQueue.main.sync {
772
+ w = self.previewView.frame.width
773
+ h = self.previewView.frame.height
774
+ }
775
+ return (w, h)
776
+ }()
777
+ print("[CameraPreview] Preview dimensions: \(previewWidth)x\(previewHeight)")
707
778
 
708
- self.cameraController.captureImage(width: width, height: height, aspectRatio: captureAspectRatio, quality: quality, gpsLocation: self.currentLocation) { (image, error) in
779
+ self.cameraController.captureImage(width: width, height: height, aspectRatio: captureAspectRatio, quality: quality, gpsLocation: self.currentLocation) { (image, originalPhotoData, _, error) in
709
780
  print("[CameraPreview] captureImage callback received")
710
781
  DispatchQueue.main.async {
711
782
  print("[CameraPreview] Processing capture on main thread")
@@ -719,7 +790,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
719
790
  let imageDataWithExif = self.createImageDataWithExif(
720
791
  from: image,
721
792
  quality: Int(quality),
722
- location: withExifLocation ? self.currentLocation : nil
793
+ location: withExifLocation ? self.currentLocation : nil,
794
+ originalPhotoData: originalPhotoData
723
795
  )
724
796
  else {
725
797
  print("[CameraPreview] Failed to create image data with EXIF")
@@ -737,27 +809,54 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
737
809
  let base64Image = imageDataWithExif.base64EncodedString()
738
810
 
739
811
  var result = JSObject()
740
- result["value"] = base64Image
741
812
  result["exif"] = exifData
742
813
  result["gallerySaved"] = success
743
814
  if !success, let error = error {
744
815
  result["galleryError"] = error.localizedDescription
745
816
  }
746
817
 
818
+ if self.storeToFile == false {
819
+ let base64Image = imageDataWithExif.base64EncodedString()
820
+ result["value"] = base64Image
821
+ } else {
822
+ do {
823
+ let fileUrl = self.getTempFilePath()
824
+ try imageDataWithExif.write(to: fileUrl)
825
+ result["value"] = fileUrl.absoluteString
826
+ } catch {
827
+ call.reject("Error writing image to file")
828
+ }
829
+ }
830
+
747
831
  print("[CameraPreview] Resolving capture call with gallery save")
748
832
  call.resolve(result)
749
833
  }
750
834
  } else {
751
835
  print("[CameraPreview] Not saving to gallery, returning image data")
752
836
  let exifData = self.getExifData(from: imageDataWithExif)
753
- let base64Image = imageDataWithExif.base64EncodedString()
754
837
 
755
- var result = JSObject()
756
- result["value"] = base64Image
757
- result["exif"] = exifData
838
+ if self.storeToFile == false {
839
+ let base64Image = imageDataWithExif.base64EncodedString()
840
+ var result = JSObject()
841
+ result["value"] = base64Image
842
+ result["exif"] = exifData
843
+
844
+ print("[CameraPreview] Resolving capture call")
845
+ call.resolve(result)
846
+ } else {
847
+ do {
848
+ let fileUrl = self.getTempFilePath()
849
+ try imageDataWithExif.write(to: fileUrl)
850
+ var result = JSObject()
851
+ result["value"] = fileUrl.absoluteString
852
+ result["exif"] = exifData
853
+ print("[CameraPreview] Resolving capture call")
854
+ call.resolve(result)
855
+ } catch {
856
+ call.reject("Error writing image to file")
857
+ }
858
+ }
758
859
 
759
- print("[CameraPreview] Resolving capture call")
760
- call.resolve(result)
761
860
  }
762
861
  }
763
862
  }
@@ -792,24 +891,29 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
792
891
  return exifData
793
892
  }
794
893
 
795
- private func createImageDataWithExif(from image: UIImage, quality: Int, location: CLLocation?) -> Data? {
796
- guard let originalImageData = image.jpegData(compressionQuality: CGFloat(Double(quality) / 100.0)) else {
894
+ private func createImageDataWithExif(from image: UIImage, quality: Int, location: CLLocation?, originalPhotoData: Data?) -> Data? {
895
+ guard let jpegDataAtQuality = image.jpegData(compressionQuality: CGFloat(Double(quality) / 100.0)) else {
797
896
  return nil
798
897
  }
799
898
 
800
- guard let imageSource = CGImageSourceCreateWithData(originalImageData as CFData, nil),
899
+ // Prefer metadata from the original AVCapturePhoto file data to preserve lens/EXIF
900
+ let sourceDataForMetadata = (originalPhotoData ?? jpegDataAtQuality) as CFData
901
+ guard let imageSource = CGImageSourceCreateWithData(sourceDataForMetadata, nil),
801
902
  let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [String: Any],
802
903
  let cgImage = image.cgImage else {
803
- return originalImageData
904
+ return jpegDataAtQuality
804
905
  }
805
906
 
806
907
  let mutableData = NSMutableData()
807
908
  guard let destination = CGImageDestinationCreateWithData(mutableData, kUTTypeJPEG, 1, nil) else {
808
- return originalImageData
909
+ return jpegDataAtQuality
809
910
  }
810
911
 
811
912
  var finalProperties = imageProperties
812
913
 
914
+ // Ensure orientation reflects the pixel data (we pass an orientation-fixed UIImage)
915
+ finalProperties[kCGImagePropertyOrientation as String] = 1
916
+
813
917
  // Add GPS location if available
814
918
  if let location = location {
815
919
  let formatter = DateFormatter()
@@ -829,10 +933,11 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
829
933
  finalProperties[kCGImagePropertyGPSDictionary as String] = gpsDict
830
934
  }
831
935
 
832
- // Create or update TIFF dictionary for device info
936
+ // Create or update TIFF dictionary for device info and set orientation to Up
833
937
  var tiffDict = finalProperties[kCGImagePropertyTIFFDictionary as String] as? [String: Any] ?? [:]
834
938
  tiffDict[kCGImagePropertyTIFFMake as String] = "Apple"
835
939
  tiffDict[kCGImagePropertyTIFFModel as String] = UIDevice.current.model
940
+ tiffDict[kCGImagePropertyTIFFOrientation as String] = 1
836
941
  finalProperties[kCGImagePropertyTIFFDictionary as String] = tiffDict
837
942
 
838
943
  CGImageDestinationAddImage(destination, cgImage, finalProperties as CFDictionary)
@@ -841,7 +946,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
841
946
  return mutableData as Data
842
947
  }
843
948
 
844
- return originalImageData
949
+ return jpegDataAtQuality
845
950
  }
846
951
 
847
952
  @objc func captureSample(_ call: CAPPluginCall) {
@@ -1036,16 +1141,17 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
1036
1141
  do {
1037
1142
  let zoomInfo = try self.cameraController.getZoom()
1038
1143
  let lensInfo = try self.cameraController.getCurrentLensInfo()
1144
+ let displayMultiplier = self.cameraController.getDisplayZoomMultiplier()
1039
1145
 
1040
1146
  var minZoom = zoomInfo.min
1041
1147
  var maxZoom = zoomInfo.max
1042
1148
  var currentZoom = zoomInfo.current
1043
1149
 
1044
- // If using the multi-lens camera, translate the native zoom values for JS
1045
- if self.cameraController.isUsingMultiLensVirtualCamera {
1046
- minZoom -= 0.5
1047
- maxZoom -= 0.5
1048
- currentZoom -= 0.5
1150
+ // Apply iOS 18+ display multiplier so UI sees the expected values
1151
+ if displayMultiplier != 1.0 {
1152
+ minZoom *= displayMultiplier
1153
+ maxZoom *= displayMultiplier
1154
+ currentZoom *= displayMultiplier
1049
1155
  }
1050
1156
 
1051
1157
  call.resolve([
@@ -1076,8 +1182,10 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
1076
1182
  }
1077
1183
 
1078
1184
  // If using the multi-lens camera, translate the JS zoom value for the native layer
1079
- if self.cameraController.isUsingMultiLensVirtualCamera {
1080
- level += 0.5
1185
+ // First, convert from UI/display zoom to native zoom using the iOS 18 multiplier
1186
+ let displayMultiplier = self.cameraController.getDisplayZoomMultiplier()
1187
+ if displayMultiplier != 1.0 {
1188
+ level = level / displayMultiplier
1081
1189
  }
1082
1190
 
1083
1191
  let ramp = call.getBool("ramp") ?? true
@@ -1326,6 +1434,26 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
1326
1434
  }
1327
1435
  }
1328
1436
 
1437
+ private func isPortrait() -> Bool {
1438
+ let orientation = UIDevice.current.orientation
1439
+ if orientation.isValidInterfaceOrientation {
1440
+ return orientation.isPortrait
1441
+ } else {
1442
+ let interfaceOrientation: UIInterfaceOrientation? = {
1443
+ if Thread.isMainThread {
1444
+ return (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.interfaceOrientation
1445
+ } else {
1446
+ var value: UIInterfaceOrientation?
1447
+ DispatchQueue.main.sync {
1448
+ value = (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.interfaceOrientation
1449
+ }
1450
+ return value
1451
+ }
1452
+ }()
1453
+ return interfaceOrientation?.isPortrait ?? false
1454
+ }
1455
+ }
1456
+
1329
1457
  private func calculateCameraFrame(x: CGFloat? = nil, y: CGFloat? = nil, width: CGFloat? = nil, height: CGFloat? = nil, aspectRatio: String? = nil) -> CGRect {
1330
1458
  // Use provided values or existing ones
1331
1459
  let currentWidth = width ?? self.width ?? UIScreen.main.bounds.size.width
@@ -1341,6 +1469,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
1341
1469
  let webViewWidth = self.webView?.frame.width ?? UIScreen.main.bounds.width
1342
1470
  let webViewHeight = self.webView?.frame.height ?? UIScreen.main.bounds.height
1343
1471
 
1472
+ let isPortrait = self.isPortrait()
1473
+
1344
1474
  var finalX = currentX
1345
1475
  var finalY = currentY
1346
1476
  var finalWidth = currentWidth
@@ -1354,12 +1484,20 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
1354
1484
  currentHeight == UIScreen.main.bounds.size.height {
1355
1485
  finalWidth = webViewWidth
1356
1486
 
1487
+ // width: 428.0 height: 926.0 - portrait
1488
+
1489
+ print("[CameraPreview] width: \(UIScreen.main.bounds.size.width) height: \(UIScreen.main.bounds.size.height)")
1490
+
1357
1491
  // Calculate height based on aspect ratio
1358
1492
  let ratioParts = ratio.split(separator: ":").compactMap { Double($0) }
1359
1493
  if ratioParts.count == 2 {
1360
1494
  // For camera, use portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
1361
1495
  let ratioValue = ratioParts[1] / ratioParts[0]
1362
- finalHeight = finalWidth / CGFloat(ratioValue)
1496
+ if isPortrait {
1497
+ finalHeight = finalWidth / CGFloat(ratioValue)
1498
+ } else {
1499
+ finalWidth = finalHeight / CGFloat(ratioValue)
1500
+ }
1363
1501
  }
1364
1502
  }
1365
1503
 
@@ -1371,9 +1509,11 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
1371
1509
  }
1372
1510
 
1373
1511
  // Position vertically if y is -1
1512
+ // TODO: fix top, bottom for landscape
1374
1513
  if currentY == -1 {
1375
1514
  // Use full screen height for positioning
1376
1515
  let screenHeight = UIScreen.main.bounds.size.height
1516
+ let screenWidth = UIScreen.main.bounds.size.width
1377
1517
  switch self.positioning {
1378
1518
  case "top":
1379
1519
  finalY = 0
@@ -1382,8 +1522,12 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
1382
1522
  finalY = screenHeight - finalHeight
1383
1523
  print("[CameraPreview] Positioning at bottom: screenHeight=\(screenHeight), finalHeight=\(finalHeight), finalY=\(finalY)")
1384
1524
  default: // "center"
1385
- finalY = (screenHeight - finalHeight) / 2
1386
- print("[CameraPreview] Centering vertically: screenHeight=\(screenHeight), finalHeight=\(finalHeight), finalY=\(finalY)")
1525
+ if isPortrait {
1526
+ finalY = (screenHeight - finalHeight) / 2
1527
+ print("[CameraPreview] Centering vertically: screenHeight=\(screenHeight), finalHeight=\(finalHeight), finalY=\(finalY)")
1528
+ } else {
1529
+ finalX = (screenWidth - finalWidth) / 2
1530
+ }
1387
1531
  }
1388
1532
  } else {
1389
1533
  finalY = currentY
@@ -1545,4 +1689,38 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
1545
1689
  }
1546
1690
  }
1547
1691
  }
1692
+
1693
+ @objc private func handleOrientationChange() {
1694
+ DispatchQueue.main.async {
1695
+ let result = self.rawSetAspectRatio()
1696
+ self.notifyListeners("screenResize", data: result)
1697
+ }
1698
+ }
1699
+
1700
+ @objc func deleteFile(_ call: CAPPluginCall) {
1701
+ guard let path = call.getString("path"), !path.isEmpty else {
1702
+ call.reject("path parameter is required")
1703
+ return
1704
+ }
1705
+ let url: URL?
1706
+ if path.hasPrefix("file://") {
1707
+ url = URL(string: path)
1708
+ } else {
1709
+ url = URL(fileURLWithPath: path)
1710
+ }
1711
+ guard let fileURL = url else {
1712
+ call.reject("Invalid path")
1713
+ return
1714
+ }
1715
+ do {
1716
+ if FileManager.default.fileExists(atPath: fileURL.path) {
1717
+ try FileManager.default.removeItem(at: fileURL)
1718
+ call.resolve(["success": true])
1719
+ } else {
1720
+ call.resolve(["success": false])
1721
+ }
1722
+ } catch {
1723
+ call.reject("Failed to delete file: \(error.localizedDescription)")
1724
+ }
1725
+ }
1548
1726
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/camera-preview",
3
- "version": "7.4.0-alpha.1",
3
+ "version": "7.4.0-alpha.13",
4
4
  "description": "Camera preview",
5
5
  "license": "MIT",
6
6
  "repository": {