@capgo/camera-preview 7.4.0-alpha.1 → 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.
- package/README.md +59 -1
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +63 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +67 -8
- package/dist/docs.json +49 -0
- package/dist/esm/definitions.d.ts +15 -0
- 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 +8 -0
- package/dist/esm/web.js +18 -5
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +41 -5
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +41 -5
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift +63 -23
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +282 -104
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
229
|
+
call.resolve(self.rawSetAspectRatio())
|
|
230
|
+
}
|
|
231
|
+
}
|
|
214
232
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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
|
-
|
|
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
|
-
|
|
756
|
-
|
|
757
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
1045
|
-
if
|
|
1046
|
-
minZoom
|
|
1047
|
-
maxZoom
|
|
1048
|
-
currentZoom
|
|
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
|
-
|
|
1080
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1386
|
-
|
|
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
|
}
|