diff --git a/CapacitorCamera.podspec b/CapacitorCamera.podspec index 83ea913..ce54ce8 100644 --- a/CapacitorCamera.podspec +++ b/CapacitorCamera.podspec @@ -13,5 +13,6 @@ Pod::Spec.new do |s| s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}', 'camera/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' s.ios.deployment_target = '15.0' s.dependency 'Capacitor' + s.dependency 'IONCameraLib', spec='~> 1.0.0' s.swift_version = '5.1' end diff --git a/Package.swift b/Package.swift index 9dd603a..a38d7fd 100644 --- a/Package.swift +++ b/Package.swift @@ -10,14 +10,16 @@ let package = Package( targets: ["CameraPlugin"]) ], dependencies: [ - .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0") + .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0"), + .package(url: "https://github.com/ionic-team/ion-ios-camera.git", branch: "main") // TODO: update to a stable release when available ], targets: [ .target( name: "CameraPlugin", dependencies: [ .product(name: "Capacitor", package: "capacitor-swift-pm"), - .product(name: "Cordova", package: "capacitor-swift-pm") + .product(name: "Cordova", package: "capacitor-swift-pm"), + .product(name: "IONCameraLib", package: "ion-ios-camera") ], path: "ios/Sources/CameraPlugin"), .testTarget( diff --git a/ios/Sources/CameraPlugin/CameraPlugin.swift b/ios/Sources/CameraPlugin/CameraPlugin.swift index 251b8dd..497ec30 100644 --- a/ios/Sources/CameraPlugin/CameraPlugin.swift +++ b/ios/Sources/CameraPlugin/CameraPlugin.swift @@ -1,4 +1,5 @@ import Foundation +import IONCameraLib import Capacitor import Photos import PhotosUI @@ -8,6 +9,12 @@ public class CameraPlugin: CAPPlugin, CAPBridgedPlugin { public let identifier = "CAPCameraPlugin" public let jsName = "Camera" public let pluginMethods: [CAPPluginMethod] = [ + CAPPluginMethod(name: "takePhoto", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "chooseFromGallery", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "editURIPhoto", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "editPhoto", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "recordVideo", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "playVideo", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "getPhoto", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "pickImages", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "checkPermissions", returnType: CAPPluginReturnPromise), @@ -20,8 +27,83 @@ public class CameraPlugin: CAPPlugin, CAPBridgedPlugin { private let defaultSource = CameraSource.prompt private let defaultDirection = CameraDirection.rear private var multiple = false + + private lazy var cameraManager = IONCAMRFactory.createCameraManagerWrapper(withDelegate: self, and: self.bridge?.viewController ?? UIViewController()) + private lazy var galleryManager = IONCAMRFactory.createGalleryManagerWrapper(withDelegate: self, and: self.bridge?.viewController ?? UIViewController()) + private lazy var editManager = IONCAMRFactory.createEditManagerWrapper(withDelegate: self, and: self.bridge?.viewController ?? UIViewController()) + private lazy var videoManager = IONCAMRFactory.createVideoManagerWrapper(withDelegate: self, and: self.bridge?.viewController ?? UIViewController()) private var imageCounter = 0 + + private func decodeParameters(from call: CAPPluginCall) -> T? { + guard let dict = call.options as? [String: Any], + let data = try? JSONSerialization.data(withJSONObject: dict) + else { return nil } + return try? JSONDecoder().decode(T.self, from: data) + } + + private func sendError(_ error: IONCAMRError) { + DispatchQueue.main.async { + self.call?.reject(error.localizedDescription, "OS-PLUG-CAMR-" + String(format: "%04d", error.errorCode)) + } + } + + private func handleCall(_ call: CAPPluginCall, error: IONCAMRError, action: @escaping (T) -> Void) { + self.call = call + guard let options: T = decodeParameters(from: call) else { + sendError(error) + return + } + DispatchQueue.main.async { + action(options) + } + } + + @objc func takePhoto(_ call: CAPPluginCall) { + handleCall(call, error: .takePictureArguments) { (options: IONCAMRTakePhotoOptions) in + self.cameraManager.takePhoto(with: options) + } + } + + @objc func chooseFromGallery(_ call: CAPPluginCall) { + handleCall(call, error: .chooseMultimediaIssue) { (options: IONCAMRGalleryOptions) in + self.galleryManager.chooseFromGallery(with: options) + } + } + + @objc func editURIPhoto(_ call: CAPPluginCall) { + handleCall(call, error: .editPictureIssue) { (options: IONCAMRPhotoEditOptions) in + self.editManager.editPhoto(with: options) + } + } + + @objc func editPhoto(_ call: CAPPluginCall) { + handleCall(call, error: .editPictureIssue) { (options: IONCAMRPhotoEditOptions) in + self.editManager.editPhoto(with: options) + } + } + + @objc func recordVideo(_ call: CAPPluginCall) { + handleCall(call, error: .captureVideoIssue) { (options: IONCAMRRecordVideoOptions) in + self.cameraManager.recordVideo(with: options) + } + } + + @objc func playVideo(_ call: CAPPluginCall) { + handleCall(call, error: .playVideoIssue) { (options: IONCAMRPlayVideoOptions) in + Task { + do { + try await self.videoManager.playVideo(options.url) + call.resolve() + } catch let error as IONCAMRError { + self.callback(error: error) + } catch { + self.callback(error: .playVideoIssue) + } + } + } + } + @objc override public func checkPermissions(_ call: CAPPluginCall) { var result: [String: Any] = [:] @@ -126,6 +208,7 @@ public class CameraPlugin: CAPPlugin, CAPBridgedPlugin { } } + @available(*, deprecated, message: "Use takePhoto or chooseFromGallery instead") @objc func getPhoto(_ call: CAPPluginCall) { self.multiple = false self.call = call @@ -150,15 +233,6 @@ public class CameraPlugin: CAPPlugin, CAPBridgedPlugin { } } - @objc func pickImages(_ call: CAPPluginCall) { - self.multiple = true - self.call = call - self.settings = cameraSettings(from: call) - DispatchQueue.main.async { - self.showPhotos() - } - } - private func checkUsageDescriptions() -> String? { if let dict = Bundle.main.infoDictionary { for key in CameraPropertyListKeys.allCases where dict[key.rawValue] == nil { @@ -168,6 +242,16 @@ public class CameraPlugin: CAPPlugin, CAPBridgedPlugin { return nil } + @available(*, deprecated, message: "Use chooseFromGallery instead") + @objc func pickImages(_ call: CAPPluginCall) { + self.multiple = true + self.call = call + self.settings = cameraSettings(from: call) + DispatchQueue.main.async { + self.showPhotos() + } + } + private func cameraSettings(from call: CAPPluginCall) -> CameraSettings { var settings = CameraSettings() settings.jpegQuality = min(abs(CGFloat(call.getFloat("quality") ?? 100.0)) / 100.0, 1.0) @@ -551,3 +635,64 @@ private extension CameraPlugin { return result } } + +extension CameraPlugin: IONCAMRCallbackDelegate { + + public func callback(error: IONCAMRError) { + sendError(error) + } + + public func callback(result: IONCAMRMediaResult) { + resolve(result) + } + + public func callback(result: [IONCAMRMediaResult]) { + resolve(["results": result]) + } + + private func resolve(_ value: T) { + do { + let data = try JSONEncoder().encode(value) + var json = try JSONSerialization.jsonObject(with: data) + + // Add webPath to results + if var dict = json as? [String: Any] { + // Handle single result + if let uri = dict["uri"] as? String, + let webPath = resolveWebPath(from: uri) { + dict["webPath"] = webPath + } + + // Handle array of results + if var results = dict["results"] as? [[String: Any]] { + results = results.map { item in + var newItem = item + if let uri = item["uri"] as? String, + let webPath = resolveWebPath(from: uri) { + newItem["webPath"] = webPath + } + return newItem + } + dict["results"] = results + } + + json = dict + } + + DispatchQueue.main.async { + self.call?.resolve(json as? [String: Any] ?? [:]) + } + } catch { + sendError(.invalidEncodeResultMedia) + } + } + + private func resolveWebPath(from uri: String) -> String? { + guard !uri.isEmpty, + let fileURL = URL(string: uri), + let webURL = bridge?.portablePath(fromLocalURL: fileURL) else { + return nil + } + return webURL.absoluteString + } +}