@capgo/camera-preview 7.4.0-alpha.0 → 7.4.0-alpha.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.
@@ -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,63 +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
 
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
+ }
234
258
 
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]
235
261
 
236
- self.updateCameraFrame()
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]
237
264
 
238
- // Return the actual preview bounds
239
- var result = JSObject()
240
- result["x"] = Double(self.previewView.frame.origin.x)
241
- result["y"] = Double(self.previewView.frame.origin.y)
242
- result["width"] = Double(self.previewView.frame.width)
243
- result["height"] = Double(self.previewView.frame.height)
244
- call.resolve(result)
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
245
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
246
288
  }
247
289
 
248
290
  @objc func getAspectRatio(_ call: CAPPluginCall) {
@@ -437,12 +479,12 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
437
479
  self.storeToFile = call.getBool("storeToFile") ?? false
438
480
  self.enableZoom = call.getBool("enableZoom") ?? false
439
481
  self.disableAudio = call.getBool("disableAudio") ?? true
440
- self.aspectRatio = call.getString("aspectRatio")
482
+ // Default to 4:3 aspect ratio if not provided
483
+ self.aspectRatio = call.getString("aspectRatio") ?? "4:3"
441
484
  self.gridMode = call.getString("gridMode") ?? "none"
442
485
  self.positioning = call.getString("positioning") ?? "top"
443
486
 
444
- let userProvidedZoom = call.getFloat("initialZoomLevel")
445
- let initialZoomLevel = userProvidedZoom ?? 1.5
487
+ let initialZoomLevel = call.getFloat("initialZoomLevel")
446
488
 
447
489
  if self.aspectRatio != nil && (call.getInt("width") != nil || call.getInt("height") != nil) {
448
490
  call.reject("Cannot set both aspectRatio and size (width/height). Use setPreviewSize after start.")
@@ -459,13 +501,19 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
459
501
  if self.cameraController.captureSession?.isRunning ?? false {
460
502
  call.reject("camera already started")
461
503
  } else {
462
- 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
463
505
  if let error = error {
464
506
  print(error)
465
507
  call.reject(error.localizedDescription)
466
508
  return
467
509
  }
510
+
468
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)
469
517
  self.completeStartCamera(call: call)
470
518
  }
471
519
  }
@@ -489,6 +537,9 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
489
537
  // Display the camera preview on the configured view
490
538
  try? self.cameraController.displayPreview(on: self.previewView)
491
539
 
540
+ // Ensure the preview orientation matches the current interface orientation at startup
541
+ self.cameraController.updateVideoOrientation()
542
+
492
543
  self.cameraController.setupGestures(target: self.previewView, enableZoom: self.enableZoom!)
493
544
 
494
545
  // Add grid overlay if enabled
@@ -521,14 +572,16 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
521
572
  }
522
573
  }
523
574
 
524
- // If already received first frame (unlikely but possible), resolve immediately
575
+ // If already received first frame (unlikely but possible), resolve immediately on main thread
525
576
  if self.cameraController.hasReceivedFirstFrame {
526
- var returnedObject = JSObject()
527
- returnedObject["width"] = self.previewView.frame.width as any JSValue
528
- returnedObject["height"] = self.previewView.frame.height as any JSValue
529
- returnedObject["x"] = self.previewView.frame.origin.x as any JSValue
530
- returnedObject["y"] = self.previewView.frame.origin.y as any JSValue
531
- 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
+ }
532
585
  }
533
586
  }
534
587
 
@@ -591,6 +644,9 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
591
644
  // Remove notification observers
592
645
  NotificationCenter.default.removeObserver(self)
593
646
 
647
+ NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
648
+ UIDevice.current.endGeneratingDeviceOrientationNotifications()
649
+
594
650
  call.resolve()
595
651
  }
596
652
  }
@@ -705,9 +761,22 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
705
761
 
706
762
  print("[CameraPreview] Capture params - quality: \(quality), saveToGallery: \(saveToGallery), withExifLocation: \(withExifLocation), width: \(width ?? -1), height: \(height ?? -1), aspectRatio: \(aspectRatio ?? "nil"), using aspectRatio: \(captureAspectRatio ?? "nil")")
707
763
  print("[CameraPreview] Current location: \(self.currentLocation?.description ?? "nil")")
708
- 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)")
709
778
 
710
- 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
711
780
  print("[CameraPreview] captureImage callback received")
712
781
  DispatchQueue.main.async {
713
782
  print("[CameraPreview] Processing capture on main thread")
@@ -721,7 +790,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
721
790
  let imageDataWithExif = self.createImageDataWithExif(
722
791
  from: image,
723
792
  quality: Int(quality),
724
- location: withExifLocation ? self.currentLocation : nil
793
+ location: withExifLocation ? self.currentLocation : nil,
794
+ originalPhotoData: originalPhotoData
725
795
  )
726
796
  else {
727
797
  print("[CameraPreview] Failed to create image data with EXIF")
@@ -739,27 +809,54 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
739
809
  let base64Image = imageDataWithExif.base64EncodedString()
740
810
 
741
811
  var result = JSObject()
742
- result["value"] = base64Image
743
812
  result["exif"] = exifData
744
813
  result["gallerySaved"] = success
745
814
  if !success, let error = error {
746
815
  result["galleryError"] = error.localizedDescription
747
816
  }
748
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
+
749
831
  print("[CameraPreview] Resolving capture call with gallery save")
750
832
  call.resolve(result)
751
833
  }
752
834
  } else {
753
835
  print("[CameraPreview] Not saving to gallery, returning image data")
754
836
  let exifData = self.getExifData(from: imageDataWithExif)
755
- let base64Image = imageDataWithExif.base64EncodedString()
756
837
 
757
- var result = JSObject()
758
- result["value"] = base64Image
759
- 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
+ }
760
859
 
761
- print("[CameraPreview] Resolving capture call")
762
- call.resolve(result)
763
860
  }
764
861
  }
765
862
  }
@@ -794,24 +891,29 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
794
891
  return exifData
795
892
  }
796
893
 
797
- private func createImageDataWithExif(from image: UIImage, quality: Int, location: CLLocation?) -> Data? {
798
- 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 {
799
896
  return nil
800
897
  }
801
898
 
802
- 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),
803
902
  let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [String: Any],
804
903
  let cgImage = image.cgImage else {
805
- return originalImageData
904
+ return jpegDataAtQuality
806
905
  }
807
906
 
808
907
  let mutableData = NSMutableData()
809
908
  guard let destination = CGImageDestinationCreateWithData(mutableData, kUTTypeJPEG, 1, nil) else {
810
- return originalImageData
909
+ return jpegDataAtQuality
811
910
  }
812
911
 
813
912
  var finalProperties = imageProperties
814
913
 
914
+ // Ensure orientation reflects the pixel data (we pass an orientation-fixed UIImage)
915
+ finalProperties[kCGImagePropertyOrientation as String] = 1
916
+
815
917
  // Add GPS location if available
816
918
  if let location = location {
817
919
  let formatter = DateFormatter()
@@ -831,10 +933,11 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
831
933
  finalProperties[kCGImagePropertyGPSDictionary as String] = gpsDict
832
934
  }
833
935
 
834
- // Create or update TIFF dictionary for device info
936
+ // Create or update TIFF dictionary for device info and set orientation to Up
835
937
  var tiffDict = finalProperties[kCGImagePropertyTIFFDictionary as String] as? [String: Any] ?? [:]
836
938
  tiffDict[kCGImagePropertyTIFFMake as String] = "Apple"
837
939
  tiffDict[kCGImagePropertyTIFFModel as String] = UIDevice.current.model
940
+ tiffDict[kCGImagePropertyTIFFOrientation as String] = 1
838
941
  finalProperties[kCGImagePropertyTIFFDictionary as String] = tiffDict
839
942
 
840
943
  CGImageDestinationAddImage(destination, cgImage, finalProperties as CFDictionary)
@@ -843,7 +946,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
843
946
  return mutableData as Data
844
947
  }
845
948
 
846
- return originalImageData
949
+ return jpegDataAtQuality
847
950
  }
848
951
 
849
952
  @objc func captureSample(_ call: CAPPluginCall) {
@@ -1038,16 +1141,17 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
1038
1141
  do {
1039
1142
  let zoomInfo = try self.cameraController.getZoom()
1040
1143
  let lensInfo = try self.cameraController.getCurrentLensInfo()
1144
+ let displayMultiplier = self.cameraController.getDisplayZoomMultiplier()
1041
1145
 
1042
1146
  var minZoom = zoomInfo.min
1043
1147
  var maxZoom = zoomInfo.max
1044
1148
  var currentZoom = zoomInfo.current
1045
1149
 
1046
- // If using the multi-lens camera, translate the native zoom values for JS
1047
- if self.cameraController.isUsingMultiLensVirtualCamera {
1048
- minZoom -= 0.5
1049
- maxZoom -= 0.5
1050
- 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
1051
1155
  }
1052
1156
 
1053
1157
  call.resolve([
@@ -1078,8 +1182,10 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
1078
1182
  }
1079
1183
 
1080
1184
  // If using the multi-lens camera, translate the JS zoom value for the native layer
1081
- if self.cameraController.isUsingMultiLensVirtualCamera {
1082
- 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
1083
1189
  }
1084
1190
 
1085
1191
  let ramp = call.getBool("ramp") ?? true
@@ -1328,6 +1434,26 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
1328
1434
  }
1329
1435
  }
1330
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
+
1331
1457
  private func calculateCameraFrame(x: CGFloat? = nil, y: CGFloat? = nil, width: CGFloat? = nil, height: CGFloat? = nil, aspectRatio: String? = nil) -> CGRect {
1332
1458
  // Use provided values or existing ones
1333
1459
  let currentWidth = width ?? self.width ?? UIScreen.main.bounds.size.width
@@ -1343,6 +1469,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
1343
1469
  let webViewWidth = self.webView?.frame.width ?? UIScreen.main.bounds.width
1344
1470
  let webViewHeight = self.webView?.frame.height ?? UIScreen.main.bounds.height
1345
1471
 
1472
+ let isPortrait = self.isPortrait()
1473
+
1346
1474
  var finalX = currentX
1347
1475
  var finalY = currentY
1348
1476
  var finalWidth = currentWidth
@@ -1356,12 +1484,20 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
1356
1484
  currentHeight == UIScreen.main.bounds.size.height {
1357
1485
  finalWidth = webViewWidth
1358
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
+
1359
1491
  // Calculate height based on aspect ratio
1360
1492
  let ratioParts = ratio.split(separator: ":").compactMap { Double($0) }
1361
1493
  if ratioParts.count == 2 {
1362
1494
  // For camera, use portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
1363
1495
  let ratioValue = ratioParts[1] / ratioParts[0]
1364
- finalHeight = finalWidth / CGFloat(ratioValue)
1496
+ if isPortrait {
1497
+ finalHeight = finalWidth / CGFloat(ratioValue)
1498
+ } else {
1499
+ finalWidth = finalHeight / CGFloat(ratioValue)
1500
+ }
1365
1501
  }
1366
1502
  }
1367
1503
 
@@ -1373,9 +1509,11 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
1373
1509
  }
1374
1510
 
1375
1511
  // Position vertically if y is -1
1512
+ // TODO: fix top, bottom for landscape
1376
1513
  if currentY == -1 {
1377
1514
  // Use full screen height for positioning
1378
1515
  let screenHeight = UIScreen.main.bounds.size.height
1516
+ let screenWidth = UIScreen.main.bounds.size.width
1379
1517
  switch self.positioning {
1380
1518
  case "top":
1381
1519
  finalY = 0
@@ -1384,8 +1522,12 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
1384
1522
  finalY = screenHeight - finalHeight
1385
1523
  print("[CameraPreview] Positioning at bottom: screenHeight=\(screenHeight), finalHeight=\(finalHeight), finalY=\(finalY)")
1386
1524
  default: // "center"
1387
- finalY = (screenHeight - finalHeight) / 2
1388
- 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
+ }
1389
1531
  }
1390
1532
  } else {
1391
1533
  finalY = currentY
@@ -1547,4 +1689,38 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
1547
1689
  }
1548
1690
  }
1549
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
+ }
1550
1726
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/camera-preview",
3
- "version": "7.4.0-alpha.0",
3
+ "version": "7.4.0-alpha.11",
4
4
  "description": "Camera preview",
5
5
  "license": "MIT",
6
6
  "repository": {