diff --git a/android/app/src/main/kotlin/com/mohem_flutter_app/VoIPCenter.swift b/android/app/src/main/kotlin/com/mohem_flutter_app/VoIPCenter.swift new file mode 100644 index 0000000..801b687 --- /dev/null +++ b/android/app/src/main/kotlin/com/mohem_flutter_app/VoIPCenter.swift @@ -0,0 +1,420 @@ +// VoIPCenter.swift +// +// +// // +// // VoIPCenter.swift +// // flutter_ios_voip_kit +// // +// // Created by 須藤将史 on 2020/07/02. +// // +// +// import Foundation +// import Flutter +// import PushKit +// import CallKit +// import AVFoundation +// +// extension String { +// internal init(deviceToken: Data) { +// self = deviceToken.map { String(format: "%.2hhx", $0) }.joined() +// } +// } +// +// class VoIPCenter: NSObject, URLSessionDelegate { +// +// +// +// +// // MARK: - event channel +// +// private let eventChannel: FlutterEventChannel +// private var eventSink: FlutterEventSink? +// +// private enum EventChannel: String { +// case onDidReceiveIncomingPush +// case onDidAcceptIncomingCall +// case onDidRejectIncomingCall +// +// case onDidUpdatePushToken +// case onDidActivateAudioSession +// case onDidDeactivateAudioSession +// } +// +// // MARK: - PushKit +// +// private let didUpdateTokenKey = "Did_Update_VoIP_Device_Token" +// private let pushRegistry: PKPushRegistry +// +// var token: String? { +// if let didUpdateDeviceToken = UserDefaults.standard.data(forKey: didUpdateTokenKey) { +// let token = String(deviceToken: didUpdateDeviceToken) +// print("🎈 VoIP didUpdateDeviceToken: \(token)") +// return token +// } +// +// guard let cacheDeviceToken = self.pushRegistry.pushToken(for: .voIP) else { +// return nil +// } +// +// let token = String(deviceToken: cacheDeviceToken) +// print("🎈 VoIP cacheDeviceToken: \(token)") +// return token +// } +// +// // MARK: - CallKit +// +// let callKitCenter: CallKitCenter +// +// fileprivate var audioSessionMode: AVAudioSession.Mode +// fileprivate let ioBufferDuration: TimeInterval +// fileprivate let audioSampleRate: Double +// +// init(eventChannel: FlutterEventChannel) { +// self.eventChannel = eventChannel +// self.pushRegistry = PKPushRegistry(queue: .main) +// self.pushRegistry.desiredPushTypes = [.voIP] +// self.callKitCenter = CallKitCenter() +// +// if let path = Bundle.main.path(forResource: "Info", ofType: "plist"), let plist = NSDictionary(contentsOfFile: path) { +// self.audioSessionMode = ((plist["FIVKAudioSessionMode"] as? String) ?? "audio") == "video" ? .videoChat : .voiceChat +// self.ioBufferDuration = plist["FIVKIOBufferDuration"] as? TimeInterval ?? 0.005 +// self.audioSampleRate = plist["FIVKAudioSampleRate"] as? Double ?? 44100.0 +// } else { +// self.audioSessionMode = .voiceChat +// self.ioBufferDuration = TimeInterval(0.005) +// self.audioSampleRate = 44100.0 +// } +// +// super.init() +// self.eventChannel.setStreamHandler(self) +// self.pushRegistry.delegate = self +// self.callKitCenter.setup(delegate: self) +// } +// } +// +// extension VoIPCenter: PKPushRegistryDelegate { +// +// // MARK: - PKPushRegistryDelegate +// +// public func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) { +// print("🎈 VoIP didUpdate pushCredentials") +// UserDefaults.standard.set(pushCredentials.token, forKey: didUpdateTokenKey) +// +// self.eventSink?(["event": EventChannel.onDidUpdatePushToken.rawValue, +// "token": pushCredentials.token.hexString]) +// } +// +// // NOTE: iOS11 or more support +// +// public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) { +// print("🎈 VoIP didRecyeiveIncomingPushWith completion: \(payload.dictionaryPayload)") +// +// let info = self.parse(payload: payload) +// let callerName = info?["incoming_caller_name"] as! String +// self.callKitCenter.incomingCall(uuidString: info?["uuid"] as! String, +// callerId: info?["incoming_caller_id"] as! String, +// callerName: callerName) { error in +// if let error = error { +// print("❌ reportNewIncomingCall error: \(error.localizedDescription)") +// return +// } +// self.eventSink?(["event": EventChannel.onDidReceiveIncomingPush.rawValue, +// "payload": info as Any, +// "incoming_caller_name": callerName]) +// completion() +// } +// } +// +// // NOTE: iOS10 support +// +// public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) { +// print("🎈 VoIP didReceiveIncomingPushWith: \(payload.dictionaryPayload)") +// +// +// let info = self.parse(payload: payload) +// let callerName = info?["incoming_caller_name"] as! String +// self.callKitCenter.incomingCall(uuidString: info?["uuid"] as! String, +// callerId: info?["incoming_caller_id"] as! String, +// callerName: callerName) { error in +// if let error = error { +// print("❌ reportNewIncomingCall error: \(error.localizedDescription)") +// return +// } +// self.eventSink?(["event": EventChannel.onDidReceiveIncomingPush.rawValue, +// "payload": info as Any, +// "incoming_caller_name": callerName]) +// } +// } +// +// private func parse(payload: PKPushPayload) -> [String: Any]? { +// do { +// let data = try JSONSerialization.data(withJSONObject: payload.dictionaryPayload, options: .prettyPrinted) +// let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] +// +// if let aps = json?["aps"] as? [String: Any] { +// if let alertString = aps["alert"] as? String { +// let alertData = alertString.data(using: .utf8) +// let alertJson = try JSONSerialization.jsonObject(with: alertData!, options: []) as? [String: Any] +// return alertJson +// } else if let alertDictionary = aps["alert"] as? [String: Any] { +// return alertDictionary +// } +// } +// +// return nil +// } catch let error as NSError { +// print("❌ VoIP parsePayload: \(error.localizedDescription)") +// return nil +// } +// } +// +// +// +// // private func parse(payload: PKPushPayload) -> [String: Any]? { +// // do { +// // let data = try JSONSerialization.data(withJSONObject: payload.dictionaryPayload, options: .prettyPrinted) +// // let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] +// // let aps = json?["aps"] as? [String: Any] +// // return aps?["alert"] as? [String: Any] +// // } catch let error as NSError { +// // print("❌ VoIP parsePayload: \(error.localizedDescription)") +// // return nil +// // } +// // } +// +// +// } +// +// extension VoIPCenter: CXProviderDelegate , APIDelegate { +// func didReceiveAPIResponse(data: Data?) { +// print("didReceiveAPIResponse") +// } +// +// func didFailAPIRequest(error: Error) { +// print("didFailAPIRequest") +// } +// +// +// // MARK: - CXProviderDelegate +// +// public func providerDidReset(_ provider: CXProvider) { +// print("🚫 VoIP providerDidReset") +// } +// +// public func provider(_ provider: CXProvider, perform action: CXStartCallAction) { +// print("🤙 VoIP CXStartCallAction") +// self.callKitCenter.connectingOutgoingCall() +// action.fulfill() +// } +// +// public func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { +// print("✅ VoIP CXAnswerCallAction") +// self.callKitCenter.answerCallAction = action +// self.configureAudioSession() +// self.eventSink?(["event": EventChannel.onDidAcceptIncomingCall.rawValue, +// "uuid": self.callKitCenter.uuidString as Any, +// "incoming_caller_id": self.callKitCenter.incomingCallerId as Any]) +// } +// +// public func provider(_ provider: CXProvider, perform action: CXEndCallAction) { +// print("❎ VoIP CXEndCallAction") +// if (self.callKitCenter.isCalleeBeforeAcceptIncomingCall) { +// self.eventSink?(["event": EventChannel.onDidRejectIncomingCall.rawValue, +// "uuid": self.callKitCenter.uuidString as Any, +// "incoming_caller_id": self.callKitCenter.incomingCallerId as Any]) +// if let callerId = self.callKitCenter.incomingCallerId { +// let components = callerId.components(separatedBy: "-") +// if components.count == 3 { +// let targetUserID = components[0] +// let currentUserID = components[1] +// print("❎ VoIP CXEndCallBeforeApi") +// APIClient.shared.postRequest(delegate: self, currentUserID: currentUserID,targetUserID:targetUserID) +// self.callKitCenter.disconnected(reason: .remoteEnded) +// action.fulfill() +// print("❎ VoIP CXEndCallAfterApi") +// } else { +// print("Invalid input string format") +// } +// } else { +// print("incomingCallerId is not a String") +// } +// } +// +// } +// +// +// +// public func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { +// print("🔈 VoIP didActivate audioSession") +// self.eventSink?(["event": EventChannel.onDidActivateAudioSession.rawValue]) +// } +// +// public func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { +// print("🔇 VoIP didDeactivate audioSession") +// self.eventSink?(["event": EventChannel.onDidDeactivateAudioSession.rawValue]) +// } +// +// // This is a workaround for known issue, when audio doesn't start from lockscreen call +// // https://stackoverflow.com/questions/55391026/no-sound-after-connecting-to-webrtc-when-app-is-launched-in-background-using-pus +// private func configureAudioSession() { +// let sharedSession = AVAudioSession.sharedInstance() +// do { +// try sharedSession.setCategory(.playAndRecord, +// options: [AVAudioSession.CategoryOptions.allowBluetooth, +// AVAudioSession.CategoryOptions.defaultToSpeaker]) +// try sharedSession.setMode(audioSessionMode) +// try sharedSession.setPreferredIOBufferDuration(ioBufferDuration) +// try sharedSession.setPreferredSampleRate(audioSampleRate) +// } catch { +// print("❌ VoIP Failed to configure `AVAudioSession`") +// } +// } +// } +// +// // Aamir Work +// +// protocol APIDelegate: AnyObject { +// func didReceiveAPIResponse(data: Data?) +// func didFailAPIRequest(error: Error) +// } +// +// +// class APIClient { +// static let shared = APIClient() +// +// func postRequest(delegate: APIDelegate, currentUserID: String, targetUserID: String) { +// DispatchQueue.global(qos: .background).async { +// self.getApiToken(currentUserID: currentUserID) { data, error in +// if let error = error { +// print("Error: \(error.localizedDescription)") +// } else if let data = data { +// do{ +// let sem = DispatchSemaphore(value: 0) +// do { +// let json = try JSONSerialization.jsonObject(with: data, options: []) +// guard let dictionary = json as? [String: Any] else { +// print("Error parsing JSON") +// return +// } +// guard let responseDictionary = dictionary["response"] as? [String: Any] else { +// print("Error parsing response") +// return +// } +// +// guard let token = responseDictionary["token"] as? String else { +// print("Error getting token") +// return +// } +// print("OneSignal User Token After Login JSON: \(token)") +// +// self.makeEndCallRequest(currentUserID: currentUserID, targetUserID: targetUserID, token: token) { data, error in +// if let error = error { +// print("Error: \(error.localizedDescription)") +// } else if let data = data { +// sem.signal() +// _ = String(data: data, encoding: .utf8) +// print("Call Ended Successfully") +// } +// } +// } +// sem.wait() +// }catch{ +// print("Error parsing JSON: \(error)") +// } +// } +// +// } +// } +// +// } +// +// private func getApiToken(currentUserID: String, completion: @escaping (Data?, Error?) -> Void) { +// let parameters = """ +// { +// "employeeNumber": "\(currentUserID)", +// "password": "FxIu26rWIKoF8n6mpbOmAjDLphzFGmpG" +// } +// """ +// let postData = parameters.data(using: .utf8) +// var request = URLRequest(url: URL(string: "https://apiderichat.hmg.com/api/user/externaluserlogin")!, +// timeoutInterval: Double.infinity) +// request.addValue("application/json", forHTTPHeaderField: "Content-Type") +// request.httpMethod = "POST" +// request.httpBody = postData +// let task = URLSession.shared.dataTask(with: request) { data, response, error in +// if let error = error { +// completion(nil, error) +// return +// } +// guard let httpResponse = response as? HTTPURLResponse else { +// let error = NSError(domain: "InvalidResponse", code: 0, userInfo: nil) +// completion(nil, error) +// return +// } +// guard (200...299).contains(httpResponse.statusCode) else { +// let error = NSError(domain: "HTTPError", code: httpResponse.statusCode, userInfo: nil) +// completion(nil, error) +// return +// } +// completion(data, nil) +// } +// +// task.resume() +// } +// +// +// private func makeEndCallRequest(currentUserID: String, targetUserID: String, token: String, completion: @escaping (Data?, Error?) -> Void) { +// let parameters = """ +// { +// "currentUserId": \(currentUserID), +// "targetUserId": \(targetUserID), +// "secretKey": "derichatmobileuser", +// "targetUserToken": "\(token)" +// } +// """ +// print("OneSignal Params: \(parameters)") +// let postData = parameters.data(using: .utf8) +// var request = URLRequest(url: URL(string: "https://apiderichat.hmg.com/api/user/calldecline")!, +// timeoutInterval: Double.infinity) +// request.addValue("application/json", forHTTPHeaderField: "Content-Type") +// request.httpMethod = "POST" +// request.httpBody = postData +// let task = URLSession.shared.dataTask(with: request) { data, response, error in +// if let error = error { +// completion(nil, error) +// return +// } +// guard let httpResponse = response as? HTTPURLResponse else { +// let error = NSError(domain: "InvalidResponse", code: 0, userInfo: nil) +// completion(nil, error) +// return +// } +// guard (200...299).contains(httpResponse.statusCode) else { +// let error = NSError(domain: "HTTPError", code: httpResponse.statusCode, userInfo: nil) +// completion(nil, error) +// return +// } +// completion(data, nil) +// } +// +// task.resume() +// } +// +// } +// +// +// // End Aamir Work +// extension VoIPCenter: FlutterStreamHandler { +// +// // MARK: - FlutterStreamHandler(event channel) +// +// public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { +// self.eventSink = events +// return nil +// } +// +// public func onCancel(withArguments arguments: Any?) -> FlutterError? { +// self.eventSink = nil +// return nil +// } +// } diff --git a/lib/api/chat/chat_api_client.dart b/lib/api/chat/chat_api_client.dart index 7998284..fb50c85 100644 --- a/lib/api/chat/chat_api_client.dart +++ b/lib/api/chat/chat_api_client.dart @@ -338,7 +338,7 @@ class ChatApiClient { } // Call Decline On App Terminated State - Future callDecline({required int cUserID, required int tUserID, required String targetUsertoken}) async { + Future callDecline({required int cUserID, required int tUserID, String? targetUsertoken}) async { Response response = await ApiClient().postJsonForResponse( "${ApiConsts.chatLoginTokenUrl}calldecline", {"currentUserId": cUserID, "targetUserId": tUserID, "secretKey": "derichatmobileuser", "targetUserToken": targetUsertoken}, @@ -359,7 +359,7 @@ class ChatApiClient { "app_id": ApiConsts.oneSignalAppID, "identifier": value, "device_type": 0, - "test_type": !kReleaseMode ? 1 : 0 + "test_type": !kReleaseMode || kDebugMode ? 1 : 0 // 1 }, ); diff --git a/lib/classes/chat_call_kit.dart b/lib/classes/chat_call_kit.dart index e4721eb..039de31 100644 --- a/lib/classes/chat_call_kit.dart +++ b/lib/classes/chat_call_kit.dart @@ -154,7 +154,7 @@ class ChatVoipCall { IosCallPayload _iosCallPayload = IosCallPayload(incomingCallerId: iosCallPayload.incomingCallerId!.split("-")[0], incomingCallReciverId: iosCallPayload.incomingCallerId!.split("-")[1]); ALM.UserAutoLoginModel model = await ChatApiClient().getUserCallToken(userid: iosCallPayload.incomingCallerId!.split("-")[1]); await ChatApiClient() - .callDecline(cUserID: int.parse(_iosCallPayload.incomingCallerId!), tUserID: int.parse(_iosCallPayload.incomingCallReciverId.toString()), targetUsertoken: model.response!.token!); + .callDecline(cUserID: int.parse(_iosCallPayload.incomingCallerId!), tUserID: int.parse(_iosCallPayload.incomingCallReciverId.toString()), targetUsertoken: model.response!.token); } catch (err) { print(err); } diff --git a/lib/ui/login/login_screen.dart b/lib/ui/login/login_screen.dart index 91e6b9e..9ca08fc 100644 --- a/lib/ui/login/login_screen.dart +++ b/lib/ui/login/login_screen.dart @@ -119,6 +119,7 @@ class _LoginScreenState extends State with WidgetsBindingObserver { String uuid, String callerId, ) async { + timeOutTimer.cancel(); await ChatVoipCall().voipDeclineCall(_iosCallPayload); await voIPKit.endCall(); }; @@ -135,7 +136,7 @@ class _LoginScreenState extends State with WidgetsBindingObserver { } void _timeOut() async { - timeOutTimer = Timer(const Duration(seconds: 25), () async { + timeOutTimer = Timer(const Duration(seconds: 20), () async { String? incomingCallerName = await voIPKit.getIncomingCallerName(); voIPKit.unansweredIncomingCall( skipLocalNotification: false,