@capgo/camera-preview 7.4.0-beta.3 → 7.4.0-beta.5
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 +66 -23
- package/android/.gradle/8.14.2/checksums/checksums.lock +0 -0
- package/android/.gradle/8.14.2/checksums/md5-checksums.bin +0 -0
- package/android/.gradle/8.14.2/checksums/sha1-checksums.bin +0 -0
- package/android/.gradle/8.14.2/executionHistory/executionHistory.bin +0 -0
- package/android/.gradle/8.14.2/executionHistory/executionHistory.lock +0 -0
- package/android/.gradle/8.14.2/fileHashes/fileHashes.bin +0 -0
- package/android/.gradle/8.14.2/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.14.2/fileHashes/resourceHashesCache.bin +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/file-system.probe +0 -0
- package/android/src/main/AndroidManifest.xml +1 -1
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +54 -27
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +132 -19
- package/android/src/main/java/com/ahm/capacitor/camera/preview/GridOverlayView.java +80 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraSessionConfiguration.java +19 -5
- package/dist/docs.json +100 -3
- package/dist/esm/definitions.d.ts +44 -2
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +16 -2
- package/dist/esm/web.js +177 -78
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +177 -78
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +177 -78
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreview/CameraController.swift +78 -20
- package/ios/Sources/CapgoCameraPreview/GridOverlayView.swift +65 -0
- package/ios/Sources/CapgoCameraPreview/Plugin.swift +155 -37
- package/package.json +1 -1
|
@@ -27,6 +27,7 @@ class CameraController: NSObject {
|
|
|
27
27
|
var fileVideoOutput: AVCaptureMovieFileOutput?
|
|
28
28
|
|
|
29
29
|
var previewLayer: AVCaptureVideoPreviewLayer?
|
|
30
|
+
var gridOverlayView: GridOverlayView?
|
|
30
31
|
|
|
31
32
|
var flashMode = AVCaptureDevice.FlashMode.off
|
|
32
33
|
var photoCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?
|
|
@@ -51,7 +52,7 @@ class CameraController: NSObject {
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
extension CameraController {
|
|
54
|
-
func prepare(cameraPosition: String, deviceId: String? = nil, disableAudio: Bool, cameraMode: Bool, completionHandler: @escaping (Error?) -> Void) {
|
|
55
|
+
func prepare(cameraPosition: String, deviceId: String? = nil, disableAudio: Bool, cameraMode: Bool, aspectRatio: String? = nil, completionHandler: @escaping (Error?) -> Void) {
|
|
55
56
|
func createCaptureSession() {
|
|
56
57
|
self.captureSession = AVCaptureSession()
|
|
57
58
|
}
|
|
@@ -204,11 +205,54 @@ extension CameraController {
|
|
|
204
205
|
func configurePhotoOutput(cameraMode: Bool) throws {
|
|
205
206
|
guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
|
|
206
207
|
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
208
|
+
// Configure session preset based on aspect ratio and other settings
|
|
209
|
+
var targetPreset: AVCaptureSession.Preset = .photo // Default preset
|
|
210
|
+
|
|
211
|
+
if let aspectRatio = aspectRatio {
|
|
212
|
+
switch aspectRatio {
|
|
213
|
+
case "16:9":
|
|
214
|
+
// Use HD presets for 16:9 aspect ratio
|
|
215
|
+
if self.highResolutionOutput && captureSession.canSetSessionPreset(.hd1920x1080) {
|
|
216
|
+
targetPreset = .hd1920x1080
|
|
217
|
+
} else if captureSession.canSetSessionPreset(.hd1280x720) {
|
|
218
|
+
targetPreset = .hd1280x720
|
|
219
|
+
} else {
|
|
220
|
+
targetPreset = .high
|
|
221
|
+
}
|
|
222
|
+
case "4:3":
|
|
223
|
+
// Use photo preset for 4:3 aspect ratio (traditional photo format)
|
|
224
|
+
if self.highResolutionOutput && captureSession.canSetSessionPreset(.photo) {
|
|
225
|
+
targetPreset = .photo
|
|
226
|
+
} else {
|
|
227
|
+
targetPreset = .medium
|
|
228
|
+
}
|
|
229
|
+
default:
|
|
230
|
+
// Default behavior for unrecognized aspect ratios
|
|
231
|
+
if !cameraMode && self.highResolutionOutput && captureSession.canSetSessionPreset(.photo) {
|
|
232
|
+
targetPreset = .photo
|
|
233
|
+
} else if cameraMode && self.highResolutionOutput && captureSession.canSetSessionPreset(.high) {
|
|
234
|
+
targetPreset = .high
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
// Original logic when no aspect ratio is specified
|
|
239
|
+
if !cameraMode && self.highResolutionOutput && captureSession.canSetSessionPreset(.photo) {
|
|
240
|
+
targetPreset = .photo
|
|
241
|
+
} else if cameraMode && self.highResolutionOutput && captureSession.canSetSessionPreset(.high) {
|
|
242
|
+
targetPreset = .high
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Apply the determined preset
|
|
247
|
+
if captureSession.canSetSessionPreset(targetPreset) {
|
|
248
|
+
captureSession.sessionPreset = targetPreset
|
|
249
|
+
print("[CameraPreview] Set session preset to \(targetPreset) for aspect ratio: \(aspectRatio ?? "default")")
|
|
250
|
+
} else {
|
|
251
|
+
// Fallback to a basic preset if the target preset is not supported
|
|
252
|
+
print("[CameraPreview] Target preset \(targetPreset) not supported, falling back to .medium")
|
|
253
|
+
if captureSession.canSetSessionPreset(.medium) {
|
|
254
|
+
captureSession.sessionPreset = .medium
|
|
255
|
+
}
|
|
212
256
|
}
|
|
213
257
|
|
|
214
258
|
self.photoOutput = AVCapturePhotoOutput()
|
|
@@ -276,6 +320,19 @@ extension CameraController {
|
|
|
276
320
|
updateVideoOrientation()
|
|
277
321
|
}
|
|
278
322
|
|
|
323
|
+
func addGridOverlay(to view: UIView, gridMode: String) {
|
|
324
|
+
removeGridOverlay()
|
|
325
|
+
|
|
326
|
+
gridOverlayView = GridOverlayView(frame: view.bounds)
|
|
327
|
+
gridOverlayView?.gridMode = gridMode
|
|
328
|
+
view.addSubview(gridOverlayView!)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
func removeGridOverlay() {
|
|
332
|
+
gridOverlayView?.removeFromSuperview()
|
|
333
|
+
gridOverlayView = nil
|
|
334
|
+
}
|
|
335
|
+
|
|
279
336
|
func setupGestures(target: UIView, enableZoom: Bool) {
|
|
280
337
|
setupTapGesture(target: target, selector: #selector(handleTap(_:)), delegate: self)
|
|
281
338
|
if enableZoom {
|
|
@@ -433,18 +490,15 @@ extension CameraController {
|
|
|
433
490
|
completion(nil, NSError(domain: "Camera", code: 0, userInfo: [NSLocalizedDescriptionKey: "Photo output is not available"]))
|
|
434
491
|
return
|
|
435
492
|
}
|
|
436
|
-
|
|
493
|
+
|
|
437
494
|
let settings = AVCapturePhotoSettings()
|
|
438
|
-
if photoOutput.isHighResolutionCaptureEnabled {
|
|
439
|
-
settings.isHighResolutionCaptureEnabled = true
|
|
440
|
-
}
|
|
441
495
|
|
|
442
496
|
self.photoCaptureCompletionBlock = { (image, error) in
|
|
443
497
|
if let error = error {
|
|
444
498
|
completion(nil, error)
|
|
445
499
|
return
|
|
446
500
|
}
|
|
447
|
-
|
|
501
|
+
|
|
448
502
|
guard let image = image else {
|
|
449
503
|
completion(nil, NSError(domain: "Camera", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to capture image"]))
|
|
450
504
|
return
|
|
@@ -453,7 +507,7 @@ extension CameraController {
|
|
|
453
507
|
if let location = gpsLocation {
|
|
454
508
|
self.addGPSMetadata(to: image, location: location)
|
|
455
509
|
}
|
|
456
|
-
|
|
510
|
+
|
|
457
511
|
if let width = width, let height = height {
|
|
458
512
|
let resizedImage = self.resizeImage(image: image, to: CGSize(width: width, height: height))
|
|
459
513
|
completion(resizedImage, nil)
|
|
@@ -461,7 +515,7 @@ extension CameraController {
|
|
|
461
515
|
completion(image, nil)
|
|
462
516
|
}
|
|
463
517
|
}
|
|
464
|
-
|
|
518
|
+
|
|
465
519
|
photoOutput.capturePhoto(with: settings, delegate: self)
|
|
466
520
|
}
|
|
467
521
|
|
|
@@ -469,21 +523,25 @@ extension CameraController {
|
|
|
469
523
|
guard let jpegData = image.jpegData(compressionQuality: 1.0),
|
|
470
524
|
let source = CGImageSourceCreateWithData(jpegData as CFData, nil),
|
|
471
525
|
let uti = CGImageSourceGetType(source) else { return }
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
526
|
+
|
|
527
|
+
var metadata = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [String: Any] ?? [:]
|
|
528
|
+
|
|
529
|
+
let formatter = DateFormatter()
|
|
530
|
+
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
|
|
531
|
+
formatter.timeZone = TimeZone(abbreviation: "UTC")
|
|
532
|
+
|
|
475
533
|
let gpsDict: [String: Any] = [
|
|
476
534
|
kCGImagePropertyGPSLatitude as String: abs(location.coordinate.latitude),
|
|
477
535
|
kCGImagePropertyGPSLatitudeRef as String: location.coordinate.latitude >= 0 ? "N" : "S",
|
|
478
536
|
kCGImagePropertyGPSLongitude as String: abs(location.coordinate.longitude),
|
|
479
537
|
kCGImagePropertyGPSLongitudeRef as String: location.coordinate.longitude >= 0 ? "E" : "W",
|
|
480
|
-
kCGImagePropertyGPSTimeStamp as String: location.timestamp
|
|
538
|
+
kCGImagePropertyGPSTimeStamp as String: formatter.string(from: location.timestamp),
|
|
481
539
|
kCGImagePropertyGPSAltitude as String: location.altitude,
|
|
482
540
|
kCGImagePropertyGPSAltitudeRef as String: location.altitude >= 0 ? 0 : 1
|
|
483
541
|
]
|
|
484
|
-
|
|
542
|
+
|
|
485
543
|
metadata[kCGImagePropertyGPSDictionary as String] = gpsDict
|
|
486
|
-
|
|
544
|
+
|
|
487
545
|
let destData = NSMutableData()
|
|
488
546
|
guard let destination = CGImageDestinationCreateWithData(destData, uti, 1, nil) else { return }
|
|
489
547
|
CGImageDestinationAddImageFromSource(destination, source, 0, metadata as CFDictionary)
|
|
@@ -737,7 +795,7 @@ extension CameraController {
|
|
|
737
795
|
|
|
738
796
|
return device.uniqueID
|
|
739
797
|
}
|
|
740
|
-
|
|
798
|
+
|
|
741
799
|
func getCurrentLensInfo() throws -> (focalLength: Float, deviceType: String, baseZoomRatio: Float) {
|
|
742
800
|
var currentCamera: AVCaptureDevice?
|
|
743
801
|
switch currentCameraPosition {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
class GridOverlayView: UIView {
|
|
4
|
+
|
|
5
|
+
var gridMode: String = "none" {
|
|
6
|
+
didSet {
|
|
7
|
+
isHidden = gridMode == "none"
|
|
8
|
+
setNeedsDisplay()
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
override init(frame: CGRect) {
|
|
13
|
+
super.init(frame: frame)
|
|
14
|
+
setup()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
required init?(coder: NSCoder) {
|
|
18
|
+
super.init(coder: coder)
|
|
19
|
+
setup()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private func setup() {
|
|
23
|
+
backgroundColor = UIColor.clear
|
|
24
|
+
isUserInteractionEnabled = false
|
|
25
|
+
isHidden = true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
override func draw(_ rect: CGRect) {
|
|
29
|
+
guard let context = UIGraphicsGetCurrentContext() else { return }
|
|
30
|
+
|
|
31
|
+
if gridMode == "none" {
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
context.setStrokeColor(UIColor.white.withAlphaComponent(0.5).cgColor)
|
|
36
|
+
context.setLineWidth(1.0)
|
|
37
|
+
|
|
38
|
+
if gridMode == "3x3" {
|
|
39
|
+
drawGrid(context: context, rect: rect, divisions: 3)
|
|
40
|
+
} else if gridMode == "4x4" {
|
|
41
|
+
drawGrid(context: context, rect: rect, divisions: 4)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private func drawGrid(context: CGContext, rect: CGRect, divisions: Int) {
|
|
46
|
+
let stepX = rect.width / CGFloat(divisions)
|
|
47
|
+
let stepY = rect.height / CGFloat(divisions)
|
|
48
|
+
|
|
49
|
+
// Draw vertical lines
|
|
50
|
+
for i in 1..<divisions {
|
|
51
|
+
let x = CGFloat(i) * stepX
|
|
52
|
+
context.move(to: CGPoint(x: x, y: 0))
|
|
53
|
+
context.addLine(to: CGPoint(x: x, y: rect.height))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Draw horizontal lines
|
|
57
|
+
for i in 1..<divisions {
|
|
58
|
+
let y = CGFloat(i) * stepY
|
|
59
|
+
context.move(to: CGPoint(x: 0, y: y))
|
|
60
|
+
context.addLine(to: CGPoint(x: rect.width, y: y))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
context.strokePath()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -5,6 +5,7 @@ import Photos
|
|
|
5
5
|
import CoreImage
|
|
6
6
|
import CoreLocation
|
|
7
7
|
|
|
8
|
+
|
|
8
9
|
extension UIWindow {
|
|
9
10
|
static var isLandscape: Bool {
|
|
10
11
|
if #available(iOS 13.0, *) {
|
|
@@ -35,7 +36,7 @@ extension UIWindow {
|
|
|
35
36
|
* here: https://capacitor.ionicframework.com/docs/plugins/ios
|
|
36
37
|
*/
|
|
37
38
|
@objc(CameraPreview)
|
|
38
|
-
public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
39
|
+
public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelegate {
|
|
39
40
|
public let identifier = "CameraPreviewPlugin"
|
|
40
41
|
public let jsName = "CameraPreview"
|
|
41
42
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -57,7 +58,9 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
57
58
|
CAPPluginMethod(name: "setZoom", returnType: CAPPluginReturnPromise),
|
|
58
59
|
CAPPluginMethod(name: "getFlashMode", returnType: CAPPluginReturnPromise),
|
|
59
60
|
CAPPluginMethod(name: "setDeviceId", returnType: CAPPluginReturnPromise),
|
|
60
|
-
CAPPluginMethod(name: "getDeviceId", returnType: CAPPluginReturnPromise)
|
|
61
|
+
CAPPluginMethod(name: "getDeviceId", returnType: CAPPluginReturnPromise),
|
|
62
|
+
CAPPluginMethod(name: "setAspectRatio", returnType: CAPPluginReturnPromise),
|
|
63
|
+
CAPPluginMethod(name: "getAspectRatio", returnType: CAPPluginReturnPromise)
|
|
61
64
|
]
|
|
62
65
|
// Camera state tracking
|
|
63
66
|
private var isInitializing: Bool = false
|
|
@@ -79,33 +82,41 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
79
82
|
var disableAudio: Bool = false
|
|
80
83
|
var locationManager: CLLocationManager?
|
|
81
84
|
var currentLocation: CLLocation?
|
|
85
|
+
private var aspectRatio: String?
|
|
82
86
|
|
|
83
87
|
// MARK: - Transparency Methods
|
|
84
|
-
|
|
88
|
+
|
|
89
|
+
|
|
85
90
|
private func makeWebViewTransparent() {
|
|
86
91
|
guard let webView = self.webView else { return }
|
|
87
|
-
|
|
92
|
+
|
|
93
|
+
|
|
88
94
|
// Define a recursive function to traverse the view hierarchy
|
|
89
95
|
func makeSubviewsTransparent(_ view: UIView) {
|
|
90
96
|
// Set the background color to clear
|
|
91
97
|
view.backgroundColor = .clear
|
|
92
|
-
|
|
98
|
+
|
|
99
|
+
|
|
93
100
|
// Recurse for all subviews
|
|
94
101
|
for subview in view.subviews {
|
|
95
102
|
makeSubviewsTransparent(subview)
|
|
96
103
|
}
|
|
97
104
|
}
|
|
98
|
-
|
|
105
|
+
|
|
106
|
+
|
|
99
107
|
// Set the main webView to be transparent
|
|
100
108
|
webView.isOpaque = false
|
|
101
109
|
webView.backgroundColor = .clear
|
|
102
|
-
|
|
110
|
+
|
|
111
|
+
|
|
103
112
|
// Recursively make all subviews transparent
|
|
104
113
|
makeSubviewsTransparent(webView)
|
|
105
|
-
|
|
114
|
+
|
|
115
|
+
|
|
106
116
|
// Also ensure the webview's container is transparent
|
|
107
117
|
webView.superview?.backgroundColor = .clear
|
|
108
|
-
|
|
118
|
+
|
|
119
|
+
|
|
109
120
|
// Force a layout pass to apply changes
|
|
110
121
|
DispatchQueue.main.async {
|
|
111
122
|
webView.setNeedsLayout()
|
|
@@ -150,13 +161,38 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
150
161
|
}
|
|
151
162
|
|
|
152
163
|
cameraController.updateVideoOrientation()
|
|
153
|
-
|
|
164
|
+
|
|
165
|
+
cameraController.updateVideoOrientation()
|
|
166
|
+
|
|
167
|
+
// Update grid overlay frame if it exists
|
|
168
|
+
if let gridOverlay = self.cameraController.gridOverlayView {
|
|
169
|
+
gridOverlay.frame = previewView.bounds
|
|
170
|
+
}
|
|
171
|
+
|
|
154
172
|
// Ensure webview remains transparent after rotation
|
|
155
173
|
if self.isInitialized {
|
|
156
174
|
self.makeWebViewTransparent()
|
|
157
175
|
}
|
|
158
176
|
}
|
|
159
177
|
|
|
178
|
+
@objc func setAspectRatio(_ call: CAPPluginCall) {
|
|
179
|
+
guard self.isInitialized else {
|
|
180
|
+
call.reject("camera not started")
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
self.aspectRatio = call.getString("aspectRatio")
|
|
184
|
+
self.updateCameraFrame()
|
|
185
|
+
call.resolve()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@objc func getAspectRatio(_ call: CAPPluginCall) {
|
|
189
|
+
guard self.isInitialized else {
|
|
190
|
+
call.reject("camera not started")
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
call.resolve(["aspectRatio": self.aspectRatio ?? "fill"])
|
|
194
|
+
}
|
|
195
|
+
|
|
160
196
|
@objc func appDidBecomeActive() {
|
|
161
197
|
if self.isInitialized {
|
|
162
198
|
DispatchQueue.main.async {
|
|
@@ -164,7 +200,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
164
200
|
}
|
|
165
201
|
}
|
|
166
202
|
}
|
|
167
|
-
|
|
203
|
+
|
|
204
|
+
|
|
168
205
|
@objc func appWillEnterForeground() {
|
|
169
206
|
if self.isInitialized {
|
|
170
207
|
DispatchQueue.main.async {
|
|
@@ -292,6 +329,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
292
329
|
self.storeToFile = call.getBool("storeToFile") ?? false
|
|
293
330
|
self.enableZoom = call.getBool("enableZoom") ?? false
|
|
294
331
|
self.disableAudio = call.getBool("disableAudio") ?? true
|
|
332
|
+
self.aspectRatio = call.getString("aspectRatio")
|
|
333
|
+
let gridMode = call.getString("gridMode") ?? "none"
|
|
295
334
|
|
|
296
335
|
AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted: Bool) in
|
|
297
336
|
guard granted else {
|
|
@@ -303,18 +342,18 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
303
342
|
if self.cameraController.captureSession?.isRunning ?? false {
|
|
304
343
|
call.reject("camera already started")
|
|
305
344
|
} else {
|
|
306
|
-
self.cameraController.prepare(cameraPosition: self.cameraPosition, deviceId: deviceId, disableAudio: self.disableAudio, cameraMode: cameraMode) {error in
|
|
345
|
+
self.cameraController.prepare(cameraPosition: self.cameraPosition, deviceId: deviceId, disableAudio: self.disableAudio, cameraMode: cameraMode, aspectRatio: self.aspectRatio) {error in
|
|
307
346
|
if let error = error {
|
|
308
347
|
print(error)
|
|
309
348
|
call.reject(error.localizedDescription)
|
|
310
349
|
return
|
|
311
350
|
}
|
|
312
|
-
|
|
313
|
-
self.previewView = UIView(frame: CGRect(x: self.posX ?? 0, y: self.posY ?? 0, width: self.width!, height: height))
|
|
351
|
+
self.updateCameraFrame()
|
|
314
352
|
|
|
315
353
|
// Make webview transparent - comprehensive approach
|
|
316
354
|
self.makeWebViewTransparent()
|
|
317
|
-
|
|
355
|
+
|
|
356
|
+
|
|
318
357
|
self.webView?.superview?.addSubview(self.previewView)
|
|
319
358
|
if self.toBack! {
|
|
320
359
|
self.webView?.superview?.bringSubviewToFront(self.webView!)
|
|
@@ -324,17 +363,34 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
324
363
|
let frontView = self.toBack! ? self.webView : self.previewView
|
|
325
364
|
self.cameraController.setupGestures(target: frontView ?? self.previewView, enableZoom: self.enableZoom!)
|
|
326
365
|
|
|
366
|
+
// Add grid overlay if enabled
|
|
367
|
+
if gridMode != "none" {
|
|
368
|
+
self.cameraController.addGridOverlay(to: self.previewView, gridMode: gridMode)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Add grid overlay if enabled
|
|
372
|
+
if gridMode != "none" {
|
|
373
|
+
self.cameraController.addGridOverlay(to: self.previewView, gridMode: gridMode)
|
|
374
|
+
}
|
|
375
|
+
|
|
327
376
|
if self.rotateWhenOrientationChanged == true {
|
|
328
377
|
NotificationCenter.default.addObserver(self, selector: #selector(CameraPreview.rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
|
|
329
378
|
}
|
|
330
|
-
|
|
379
|
+
|
|
380
|
+
|
|
331
381
|
// Add observers for app state changes to maintain transparency
|
|
332
382
|
NotificationCenter.default.addObserver(self, selector: #selector(CameraPreview.appDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
|
|
333
383
|
NotificationCenter.default.addObserver(self, selector: #selector(CameraPreview.appWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
|
|
334
384
|
|
|
335
385
|
self.isInitializing = false
|
|
336
386
|
self.isInitialized = true
|
|
337
|
-
|
|
387
|
+
|
|
388
|
+
var returnedObject = JSObject()
|
|
389
|
+
returnedObject["width"] = self.previewView.frame.width as any JSValue
|
|
390
|
+
returnedObject["height"] = self.previewView.frame.height as any JSValue
|
|
391
|
+
returnedObject["x"] = self.previewView.frame.origin.x as any JSValue
|
|
392
|
+
returnedObject["y"] = self.previewView.frame.origin.y as any JSValue
|
|
393
|
+
call.resolve(returnedObject)
|
|
338
394
|
|
|
339
395
|
}
|
|
340
396
|
}
|
|
@@ -371,10 +427,12 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
371
427
|
self.cameraController.previewLayer?.frame = self.previewView.bounds
|
|
372
428
|
self.cameraController.previewLayer?.videoGravity = .resizeAspectFill
|
|
373
429
|
self.previewView.isUserInteractionEnabled = true
|
|
374
|
-
|
|
430
|
+
|
|
431
|
+
|
|
375
432
|
// Ensure webview remains transparent after flip
|
|
376
433
|
self.makeWebViewTransparent()
|
|
377
|
-
|
|
434
|
+
|
|
435
|
+
|
|
378
436
|
call.resolve()
|
|
379
437
|
}
|
|
380
438
|
} catch {
|
|
@@ -411,6 +469,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
411
469
|
}
|
|
412
470
|
|
|
413
471
|
// Always attempt to stop and clean up, regardless of captureSession state
|
|
472
|
+
self.cameraController.removeGridOverlay()
|
|
414
473
|
if let previewView = self.previewView {
|
|
415
474
|
previewView.removeFromSuperview()
|
|
416
475
|
self.previewView = nil
|
|
@@ -420,7 +479,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
420
479
|
self.isInitialized = false
|
|
421
480
|
self.isInitializing = false
|
|
422
481
|
self.cameraController.cleanup()
|
|
423
|
-
|
|
482
|
+
|
|
483
|
+
|
|
424
484
|
// Remove notification observers
|
|
425
485
|
NotificationCenter.default.removeObserver(self)
|
|
426
486
|
|
|
@@ -465,7 +525,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
465
525
|
PHAssetChangeRequest.creationRequestForAsset(from: image!)
|
|
466
526
|
}, completionHandler: { (success, error) in
|
|
467
527
|
if !success {
|
|
468
|
-
|
|
528
|
+
print("CameraPreview: Error saving image to gallery: \(error?.localizedDescription ?? "Unknown error")")
|
|
469
529
|
}
|
|
470
530
|
})
|
|
471
531
|
}
|
|
@@ -474,10 +534,12 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
474
534
|
call.reject("Failed to get JPEG data from image")
|
|
475
535
|
return
|
|
476
536
|
}
|
|
477
|
-
|
|
537
|
+
|
|
538
|
+
|
|
478
539
|
let exifData = self.getExifData(from: imageData)
|
|
479
540
|
let base64Image = imageData.base64EncodedString()
|
|
480
|
-
|
|
541
|
+
|
|
542
|
+
|
|
481
543
|
var result = JSObject()
|
|
482
544
|
result["value"] = base64Image
|
|
483
545
|
result["exif"] = exifData
|
|
@@ -492,12 +554,28 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
492
554
|
let exifDict = imageProperties[kCGImagePropertyExifDictionary as String] as? [String: Any] else {
|
|
493
555
|
return [:]
|
|
494
556
|
}
|
|
495
|
-
|
|
557
|
+
|
|
558
|
+
|
|
496
559
|
var exifData = JSObject()
|
|
497
560
|
for (key, value) in exifDict {
|
|
498
|
-
|
|
561
|
+
// Convert value to JSValue-compatible type
|
|
562
|
+
if let stringValue = value as? String {
|
|
563
|
+
exifData[key] = stringValue
|
|
564
|
+
} else if let numberValue = value as? NSNumber {
|
|
565
|
+
exifData[key] = numberValue
|
|
566
|
+
} else if let boolValue = value as? Bool {
|
|
567
|
+
exifData[key] = boolValue
|
|
568
|
+
} else if let arrayValue = value as? [Any] {
|
|
569
|
+
exifData[key] = arrayValue
|
|
570
|
+
} else if let dictValue = value as? [String: Any] {
|
|
571
|
+
exifData[key] = JSObject(_immutableCocoaDictionary: NSMutableDictionary(dictionary: dictValue))
|
|
572
|
+
} else {
|
|
573
|
+
// Convert other types to string as fallback
|
|
574
|
+
exifData[key] = String(describing: value)
|
|
575
|
+
}
|
|
499
576
|
}
|
|
500
|
-
|
|
577
|
+
|
|
578
|
+
|
|
501
579
|
return exifData
|
|
502
580
|
}
|
|
503
581
|
|
|
@@ -640,9 +718,11 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
640
718
|
// Collect all devices by position
|
|
641
719
|
for device in session.devices {
|
|
642
720
|
var lenses: [[String: Any]] = []
|
|
643
|
-
|
|
721
|
+
|
|
722
|
+
|
|
644
723
|
let constituentDevices = device.isVirtualDevice ? device.constituentDevices : [device]
|
|
645
|
-
|
|
724
|
+
|
|
725
|
+
|
|
646
726
|
for lensDevice in constituentDevices {
|
|
647
727
|
var deviceType: String
|
|
648
728
|
switch lensDevice.deviceType {
|
|
@@ -662,7 +742,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
662
742
|
} else if lensDevice.deviceType == .builtInTelephotoCamera {
|
|
663
743
|
baseZoomRatio = 2.0 // A common value for telephoto lenses
|
|
664
744
|
}
|
|
665
|
-
|
|
745
|
+
|
|
746
|
+
|
|
666
747
|
let lensInfo: [String: Any] = [
|
|
667
748
|
"label": lensDevice.localizedName,
|
|
668
749
|
"deviceType": deviceType,
|
|
@@ -673,7 +754,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
673
754
|
]
|
|
674
755
|
lenses.append(lensInfo)
|
|
675
756
|
}
|
|
676
|
-
|
|
757
|
+
|
|
758
|
+
|
|
677
759
|
let deviceData: [String: Any] = [
|
|
678
760
|
"deviceId": device.uniqueID,
|
|
679
761
|
"label": device.localizedName,
|
|
@@ -683,7 +765,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
683
765
|
"maxZoom": Float(device.maxAvailableVideoZoomFactor),
|
|
684
766
|
"isLogical": device.isVirtualDevice
|
|
685
767
|
]
|
|
686
|
-
|
|
768
|
+
|
|
769
|
+
|
|
687
770
|
devices.append(deviceData)
|
|
688
771
|
}
|
|
689
772
|
|
|
@@ -699,7 +782,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
699
782
|
do {
|
|
700
783
|
let zoomInfo = try self.cameraController.getZoom()
|
|
701
784
|
let lensInfo = try self.cameraController.getCurrentLensInfo()
|
|
702
|
-
|
|
785
|
+
|
|
786
|
+
|
|
703
787
|
var minZoom = zoomInfo.min
|
|
704
788
|
var maxZoom = zoomInfo.max
|
|
705
789
|
var currentZoom = zoomInfo.current
|
|
@@ -795,10 +879,12 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
795
879
|
self.cameraController.previewLayer?.frame = self.previewView.bounds
|
|
796
880
|
self.cameraController.previewLayer?.videoGravity = .resizeAspectFill
|
|
797
881
|
self.previewView.isUserInteractionEnabled = true
|
|
798
|
-
|
|
882
|
+
|
|
883
|
+
|
|
799
884
|
// Ensure webview remains transparent after device switch
|
|
800
885
|
self.makeWebViewTransparent()
|
|
801
|
-
|
|
886
|
+
|
|
887
|
+
|
|
802
888
|
call.resolve()
|
|
803
889
|
}
|
|
804
890
|
} catch {
|
|
@@ -825,14 +911,46 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
|
|
|
825
911
|
}
|
|
826
912
|
}
|
|
827
913
|
|
|
828
|
-
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
|
914
|
+
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
|
829
915
|
self.currentLocation = locations.last
|
|
830
916
|
self.locationManager?.stopUpdatingLocation()
|
|
831
917
|
}
|
|
832
918
|
|
|
833
|
-
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
|
834
|
-
|
|
919
|
+
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
|
920
|
+
print("CameraPreview: Failed to get location: \(error.localizedDescription)")
|
|
835
921
|
}
|
|
836
922
|
|
|
923
|
+
private func updateCameraFrame() {
|
|
924
|
+
guard let width = self.width, var height = self.height, let posX = self.posX, let posY = self.posY else { return }
|
|
925
|
+
let paddingBottom = self.paddingBottom ?? 0
|
|
926
|
+
height -= paddingBottom
|
|
927
|
+
|
|
928
|
+
var frame = CGRect(x: posX, y: posY, width: width, height: height)
|
|
837
929
|
|
|
930
|
+
if let aspectRatio = self.aspectRatio, aspectRatio != "fill" {
|
|
931
|
+
let ratioParts = aspectRatio.split(separator: ":").map { Double($0) ?? 1.0 }
|
|
932
|
+
let ratio = ratioParts[0] / ratioParts[1]
|
|
933
|
+
let viewWidth = Double(width)
|
|
934
|
+
let viewHeight = Double(height)
|
|
935
|
+
|
|
936
|
+
if viewWidth / ratio > viewHeight {
|
|
937
|
+
let newWidth = viewHeight * ratio
|
|
938
|
+
frame.origin.x += (viewWidth - newWidth) / 2
|
|
939
|
+
frame.size.width = newWidth
|
|
940
|
+
} else {
|
|
941
|
+
let newHeight = viewWidth / ratio
|
|
942
|
+
frame.origin.y += (viewHeight - newHeight) / 2
|
|
943
|
+
frame.size.height = newHeight
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
if self.previewView == nil {
|
|
948
|
+
self.previewView = UIView(frame: frame)
|
|
949
|
+
} else {
|
|
950
|
+
self.previewView.frame = frame
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// Update the preview layer frame to match the preview view
|
|
954
|
+
self.cameraController.previewLayer?.frame = frame
|
|
955
|
+
}
|
|
838
956
|
}
|