You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
diplomatic-quarter/packages/vital_sign_camera/ios/Classes/FlutterVitalSignCamera.swift

290 lines
9.4 KiB
Swift

//
// FlutterVitalSignCamera.swift
// vital_sign_camera
//
// Created by Eddie Lau on 24/1/2023.
//
import Foundation
import Flutter
import UIKit
import PromiseKit
import VitalSignEngineCore
class FlutterVitalSignCamera: NSObject, FlutterPlatformView {
private var camera: VitalSignCamera
private var methodChannel: FlutterMethodChannel
private var eventSink: FlutterEventSink?
func view() -> UIView {
return camera
}
init(
frame: CGRect,
viewIdentifier viewId: Int64,
arguments args: Any?,
binaryMessenger messenger: FlutterBinaryMessenger
) {
methodChannel = FlutterMethodChannel(name: "ai.panoptic/channel/flutter_vital_sign_camera_\(viewId)", binaryMessenger: messenger)
camera = VitalSignCamera(frame: frame)
super.init()
camera.faceDetector = MLKitFaceDetector()
camera.configure(args: args, onVideoFrameProcessed: onVideoFrameProcessed)
methodChannel.setMethodCallHandler(onMethodCall)
let channel = FlutterEventChannel(name: "ai.panoptic/channel/flutter_vital_sign_camera_processed_frame_event_\(viewId)", binaryMessenger: messenger)
channel.setStreamHandler(self)
}
func onMethodCall(call: FlutterMethodCall, result: @escaping FlutterResult) {
switch(call.method){
case "startScanning":
NSLog("FlutterVitalSignCamera: startScanning")
camera.startScanning().done {
result(nil)
}.catch { error in
result(FlutterError(code: "\(error.code)", message: error.message, details: nil))
}
case "stopScanning":
NSLog("FlutterVitalSignCamera: stopScanning")
camera.stopScanning().done {
result(nil)
}.catch { error in
result(FlutterError(code: "\(error.code)", message: error.message, details: nil))
}
case "configure":
NSLog("FlutterVitalSignCamera: configure \(call.arguments ?? "nil")")
camera.configure(args: call.arguments, onVideoFrameProcessed: onVideoFrameProcessed)
default:
result(FlutterMethodNotImplemented)
}
}
func onVideoFrameProcessed(result: ProcessResult) {
let flutterMap = result.dictionary.flutterMap
guard let eventSink = eventSink else {
return
}
eventSink(flutterMap)
}
}
extension FlutterVitalSignCamera: FlutterStreamHandler {
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
eventSink = events
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
eventSink = nil
return nil
}
}
extension VitalSignCamera {
func configure(args:Any?, onVideoFrameProcessed:@escaping (ProcessResult)->Void) {
if let args = args as? [String:Any] {
configure(dict: args, onVideoFrameProcessed: onVideoFrameProcessed)
} else {
NSLog("Error: Invalid props: \(args ?? "nil")")
}
}
func configure(dict:Dictionary<String,Any>, onVideoFrameProcessed:@escaping (ProcessResult)->Void) {
var device:CameraDevice?
if let id = dict["id"] as? String {
device = VitalSignCamera.availableCameraDevices().first { $0.id == id }
}
var isActive = false
if let isActiveNumber = dict["isActive"] as? NSNumber {
isActive = isActiveNumber.boolValue
}
guard let userInfoDict = dict["userInfo"] as? Dictionary<String,Any>,
let userInfo = UserInfo(flutterMap: userInfoDict) else {
NSLog("Error: Invalid userInfo: \(dict["userInfo"] ?? "nil")")
return
}
guard let configDict = dict["config"] as? NSDictionary,
let config = VitalSignEngineConfig(flutterMap: configDict) else {
NSLog("Error: Invalid config: \(dict["config"] ?? "nil")")
return
}
let previewLayerGravity:PreviewLayerGravity = {
if let previewLayerGravityNum = dict["previewLayerGravity"] as? NSNumber,
let previewLayerGravity = PreviewLayerGravity(number: previewLayerGravityNum) {
return previewLayerGravity
}
return PreviewLayerGravity.resizeAspectFill
}()
configure { camera in
camera.device = device
camera.isActive = isActive
camera.userInfo = userInfo
camera.config = config
camera.previewLayerGravity = previewLayerGravity
camera.onVideoFrameProcessed = onVideoFrameProcessed
}
}
}
extension UserInfo {
init?(flutterMap:Dictionary<String,Any>) {
guard let genderObj = flutterMap["gender"] as? NSNumber else {
NSLog("Error: Invalid gender: \(flutterMap["gender"] ?? "nil")")
return nil
}
guard let gender = Gender(rawValue: genderObj.intValue) else {
NSLog("Error: Invalid gender: \(flutterMap["gender"] ?? "nil")")
return nil
}
guard let ageObj = flutterMap["age"] as? NSNumber else {
NSLog("Error: Invalid age: \(flutterMap["age"] ?? "nil")")
return nil
}
let age = ageObj.doubleValue
guard let userIdObj = flutterMap["userId"] as? NSString else {
NSLog("Error: Invalid userId: \(flutterMap["userId"] ?? "nil")")
return nil
}
self.init(
gender: gender,
age: age,
weight: (flutterMap["weight"] as? NSNumber)?.doubleValue,
height: (flutterMap["height"] as? NSNumber)?.doubleValue,
smoking: (flutterMap["smoker"] as? NSNumber)?.boolValue,
hypertension: Hypertension(hasHypertension: (flutterMap["hypertension"] as? NSNumber)?.boolValue),
bpMedication: (flutterMap["bpMedication"] as? NSNumber)?.boolValue,
diabetic: Diabetic(hasDiabetic: (flutterMap["diabetic"] as? NSNumber)?.boolValue),
waistCircumference: (flutterMap["waistCircumference"] as? NSNumber)?.doubleValue,
appUserId: flutterMap["appUserId"] as? String,
userId: userIdObj as String
)
}
}
extension Diabetic {
init?(hasDiabetic:Bool?) {
guard let hasDiabetic = hasDiabetic else {
return nil
}
if (hasDiabetic) {
self = .type1
} else {
self = .no
}
}
}
extension Hypertension {
init?(hasHypertension:Bool?) {
guard let hasHypertension = hasHypertension else {
return nil
}
if (hasHypertension) {
self = .stage1
} else {
self = .no
}
}
}
extension VitalSignEngineConfig {
init?(flutterMap: NSDictionary) {
/*
public var errorExpirationTime: TimeInterval?
*/
guard let map = flutterMap as? [String:Any] else {
NSLog("Error: Invalid config: \(flutterMap)")
return nil
}
let serverIdObj = map["serverId"] as? NSNumber
let serverId = serverIdObj != nil ? ServerId(rawValue: serverIdObj!.intValue) : nil
let dataCollectionDuration = map["dataCollectionDuration"] as? NSNumber
let dataSendingDuration = map["dataSendingDuration"] as? NSNumber
let dataSendingRate = map["dataSendingRate"] as? NSNumber
let dataWindowDuration = map["dataWindowDuration"] as? NSNumber
let apiKey = map["apiKey"] as? NSString
let faceLostTimeout = map["faceLostTimeout"] as? NSNumber
let errorExpirationTime = map["errorExpirationTime"] as? NSNumber
self.init(
serverId: serverId,
dataCollectionDuration: dataCollectionDuration?.doubleValue,
dataSendingDuration: dataSendingDuration?.doubleValue,
dataSendingRate: dataSendingRate?.doubleValue,
dataWindowDuration: dataWindowDuration?.doubleValue,
apiKey: apiKey as String?,
faceLostTimeout: faceLostTimeout?.doubleValue,
errorExpirationTime: errorExpirationTime?.doubleValue
)
}
}
extension PreviewLayerGravity {
init?(number: NSNumber) {
if (number.intValue == 0) {
self = .resizeAspectFill
} else if (number.intValue == 1) {
self = .resizeAspect
} else {
return nil
}
}
}
func toFlutterValue(value:Any) -> Any {
if let boolValue = value as? Bool {
return NSNumber(value: boolValue)
} else if let doubleValue = value as? Double {
return NSNumber(value: doubleValue)
} else if let intValue = value as? Int {
return NSNumber(value: intValue)
} else if let int32Value = value as? Int32 {
return NSNumber(value: int32Value)
} else if let stringValue = value as? String {
return stringValue
} else if let map = value as? [String:Any] {
return map.flutterMap
} else if let array = value as? [Any] {
return array.map { toFlutterValue(value: $0) }
} else {
return value
}
}
extension Dictionary<String,Any> {
var flutterMap:[String:Any] {
var map = [String:Any]()
keys.forEach { key in
map[key] = toFlutterValue(value: self[key]!)
}
return map
}
}