@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.
- 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 +65 -30
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +280 -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,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
|
-
|
|
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
|
|
|
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
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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
|
-
|
|
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
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
1047
|
-
if
|
|
1048
|
-
minZoom
|
|
1049
|
-
maxZoom
|
|
1050
|
-
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
|
|
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
|
-
|
|
1082
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1388
|
-
|
|
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
|
}
|