hiennguyen92 / flutter_callkit_incoming

Flutter Callkit Incoming
https://pub.dev/packages/flutter_callkit_incoming
MIT License
180 stars 312 forks source link

iOS Kill state don't showing call kit! #571

Closed Technozer closed 3 months ago

Technozer commented 3 months ago

i was using view call kit firebase FCM to send notification to send notificaion using FCM -> FirebaseMessaging.onMessage.listen -> flutter_call_incoming

And also when app was forgroung state view call kit on after app was kill auto cut call only this two issue face on iOS kill state.

Call kit is not showing even though notification is received when in kill state only iOS. otherwise andorid all stack view call kit and iOS Only background and forgrond working fine.

iOS AppDelegte code :

import UIKit import FirebaseCore import Flutter import Firebase import CallKit import AVFAudio import PushKit import flutter_callkit_incoming

@UIApplicationMain @objc class AppDelegate: FlutterAppDelegate, PKPushRegistryDelegate, CallkitIncomingAppDelegate { // var window : UIWindow?

override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { FirebaseApp.configure()

//Setup VOIP
 let mainQueue = DispatchQueue.main
 let voipRegistry: PKPushRegistry = PKPushRegistry(queue: mainQueue)
 voipRegistry.delegate = self
 voipRegistry.desiredPushTypes = [PKPushType.voIP]

GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)

}

// Call back from Recent history
override func application(_ application: UIApplication,
    continue userActivity: NSUserActivity,
    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {

    guard let handleObj = userActivity.handle else {
        return false
    }

    guard let isVideo = userActivity.isVideo else {
        return false
    }
    let objData = handleObj.getDecryptHandle()
    let nameCaller = objData["nameCaller"] as? String ?? ""
    let handle = objData["handle"] as? String ?? ""
    let data = flutter_callkit_incoming.Data(id: UUID().uuidString, nameCaller: nameCaller, handle: handle, type: isVideo ? 1 : 0)
    //set more data...
    //data.nameCaller = nameCaller
    SwiftFlutterCallkitIncomingPlugin.sharedInstance?.startCall(data, fromPushKit: true)

    return super.application(application, continue: userActivity, restorationHandler: restorationHandler)
}

// Handle updated push credentials
func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
    print(credentials.token)
    let deviceToken = credentials.token.map { String(format: "%02x", $0) }.joined()
    print(deviceToken)
    //Save deviceToken to your server
    SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP(deviceToken)
}

func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
    print("didInvalidatePushTokenFor")
    SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP("")
}

// Handle incoming pushes
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
    print("didReceiveIncomingPushWith")
    guard type == .voIP else { return }

    let id = payload.dictionaryPayload["id"] as? String ?? ""
    let nameCaller = payload.dictionaryPayload["nameCaller"] as? String ?? ""
    let handle = payload.dictionaryPayload["handle"] as? String ?? ""
    let isVideo = payload.dictionaryPayload["isVideo"] as? Bool ?? false

    let data = flutter_callkit_incoming.Data(id: id, nameCaller: nameCaller, handle: handle, type: isVideo ? 1 : 0)
    //set more data
    data.extra = ["user": "abc@123", "platform": "ios"]
    //data.iconName = ...
    //data.....
    SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(data, fromPushKit: true)

    //Make sure call completion()
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        completion()
    }
}

// Func Call api for Accept
func onAccept(_ call: Call, _ action: CXAnswerCallAction) {
    let json = ["action": "ACCEPT", "data": call.data.toJSON()] as [String: Any]
    print("LOG: onAccept")
    self.performRequest(parameters: json) { result in
        switch result {
        case .success(let data):
            print("Received data: \(data)")
            //Make sure call action.fulfill() when you are done(connected WebRTC - Start counting seconds)
            action.fulfill()

        case .failure(let error):
            print("Error: \(error.localizedDescription)")
        }
    }
}

// Func Call API for Decline
func onDecline(_ call: Call, _ action: CXEndCallAction) {
    let json = ["action": "DECLINE", "data": call.data.toJSON()] as [String: Any]
    print("LOG: onDecline")
    self.performRequest(parameters: json) { result in
        switch result {
        case .success(let data):
            print("Received data: \(data)")
            //Make sure call action.fulfill() when you are done
            action.fulfill()

        case .failure(let error):
            print("Error: \(error.localizedDescription)")
        }
    }
}

// Func Call API for End
func onEnd(_ call: Call, _ action: CXEndCallAction) {
    let json = ["action": "END", "data": call.data.toJSON()] as [String: Any]
    print("LOG: onEnd")
    self.performRequest(parameters: json) { result in
        switch result {
        case .success(let data):
            print("Received data: \(data)")
            //Make sure call action.fulfill() when you are done
            action.fulfill()

        case .failure(let error):
            print("Error: \(error.localizedDescription)")
        }
    }
}

// Func Call API for TimeOut
func onTimeOut(_ call: Call) {
    let json = ["action": "TIMEOUT", "data": call.data.toJSON()] as [String: Any]
    print("LOG: onTimeOut")
    self.performRequest(parameters: json) { result in
        switch result {
        case .success(let data):
            print("Received data: \(data)")

        case .failure(let error):
            print("Error: \(error.localizedDescription)")
        }
    }
}

// Func Callback Toggle Audio Session
func didActivateAudioSession(_ audioSession: AVAudioSession) {
    //Use if using WebRTC
    //RTCAudioSession.sharedInstance().audioSessionDidActivate(audioSession)
    //RTCAudioSession.sharedInstance().isAudioEnabled = true
}

// Func Callback Toggle Audio Session
func didDeactivateAudioSession(_ audioSession: AVAudioSession) {
    //Use if using WebRTC
    //RTCAudioSession.sharedInstance().audioSessionDidDeactivate(audioSession)
    //RTCAudioSession.sharedInstance().isAudioEnabled = false
}

func performRequest(parameters: [String: Any], completion: @escaping (Result<Any, Error>) -> Void) {
    if let url = URL(string: "https://webhook.site/e32a591f-0d17-469d-a70d-33e9f9d60727") {
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        //Add header

        do {
            let jsonData = try JSONSerialization.data(withJSONObject: parameters, options: [])
            request.httpBody = jsonData
        } catch {
            completion(.failure(error))
            return
        }

        let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
            if let error = error {
                completion(.failure(error))
                return
            }

            guard let data = data else {
                completion(.failure(NSError(domain: "mobile.app", code: 0, userInfo: [NSLocalizedDescriptionKey: "Empty data"])))
                return
            }

            do {
                let jsonObject = try JSONSerialization.jsonObject(with: data, options: [])
                completion(.success(jsonObject))
            } catch {
                completion(.failure(error))
            }
        }
        task.resume()
    } else {
        completion(.failure(NSError(domain: "mobile.app", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])))
    }
}

}

hiennguyen92 commented 3 months ago

For iOS, you need to send voip push notification not just notification. voip push can wake up app when app is in terminated state

Technozer commented 3 months ago

Can you share a detailed demo? How to send voip notifications on the app side and also receive side view call kill and how to set up voip notification?

On Tue, Aug 6, 2024 at 4:58 PM Hien Nguyen @.***> wrote:

For iOS, you need to send voip push notification not just notification. voip push can wake up app when app is in terminated state

— Reply to this email directly, view it on GitHub https://github.com/hiennguyen92/flutter_callkit_incoming/issues/571#issuecomment-2271063623, or unsubscribe https://github.com/notifications/unsubscribe-auth/AV2LF2AQVRTHZ6ZRJ6EWBG3ZQCXOHAVCNFSM6AAAAABMBWCG76VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENZRGA3DGNRSGM . You are receiving this because you authored the thread.Message ID: @.***>

hiennguyen92 commented 3 months ago

Please check with https://github.com/hiennguyen92/flutter_callkit_incoming/blob/master/PUSHKIT.md

Technozer commented 3 months ago

Now facing new issue SwiftFlutterCallKitIncomigPlugin file Error: uuid is nil

@objc public func showCallkitIncoming(_ data: Data, fromPushKit: Bool) { self.isFromPushKit = fromPushKit if(fromPushKit){ self.data = data }

    var handle: CXHandle?
    handle = CXHandle(type: self.getHandleType(data.handleType), value: data.getEncryptHandle())

    let callUpdate = CXCallUpdate()
    callUpdate.remoteHandle = handle
    callUpdate.supportsDTMF = data.supportsDTMF
    callUpdate.supportsHolding = data.supportsHolding
    callUpdate.supportsGrouping = data.supportsGrouping
    callUpdate.supportsUngrouping = data.supportsUngrouping
    callUpdate.hasVideo = data.type > 0 ? true : false
    callUpdate.localizedCallerName = data.nameCaller

    initCallkitProvider(data)

    let uuid = UUID(uuidString: data.uuid)

    configurAudioSession()
    guard self.sharedProvider != nil else {
        print("Error: sharedProvider is nil")
        return
    }

    guard let uuid = uuid else {
        print("Error: uuid is nil")
        return
    }
    self.sharedProvider?.reportNewIncomingCall(with: uuid, update: callUpdate) { error in
        if(error == nil) {
            self.configurAudioSession()
            let call = Call(uuid: uuid, data: data)
            call.handle = data.handle
            self.callManager.addCall(call)
            self.sendEvent(SwiftFlutterCallkitIncomingPlugin.ACTION_CALL_INCOMING, data.toJSON())
            self.endCallNotExist(data)
        }
    }
}

Error: uuid is nil

also using showCallkitIncoming( uuid: const Uuid().v4(), title: message.data['doctorName'], body: message.data['body'], profile: message.data['doctorProfile'], callType: callType, customRoomId: customRoomId, videoCallId: message.data['videoCallId'], appointmentPatientName: message.data['appointmentPatientName'], isVideoCall: message.data['isVideoCall'], );

On Wed, Aug 7, 2024 at 9:52 AM Hien Nguyen @.***> wrote:

Please check with https://github.com/hiennguyen92/flutter_callkit_incoming/blob/master/PUSHKIT.md

— Reply to this email directly, view it on GitHub https://github.com/hiennguyen92/flutter_callkit_incoming/issues/571#issuecomment-2272589469, or unsubscribe https://github.com/notifications/unsubscribe-auth/AV2LF2E7HDW6VK4KDPQQBCDZQGOIDAVCNFSM6AAAAABMBWCG76VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENZSGU4DSNBWHE . You are receiving this because you authored the thread.Message ID: @.***>

hiennguyen92 commented 3 months ago

You have to make sure that the data you transmit has UUID.

himanshuflutter commented 3 months ago

Screenshot 2024-08-12 at 21 27 15

himanshuflutter commented 3 months ago

this code is working for me in terminate state