|
|
|
|
@ -2,10 +2,12 @@ import UIKit
|
|
|
|
|
import Flutter
|
|
|
|
|
import Firebase
|
|
|
|
|
import PushKit
|
|
|
|
|
import CallKit
|
|
|
|
|
import AVFAudio
|
|
|
|
|
import flutter_callkit_incoming
|
|
|
|
|
|
|
|
|
|
@main
|
|
|
|
|
@objc class AppDelegate: FlutterAppDelegate, PKPushRegistryDelegate {
|
|
|
|
|
@objc class AppDelegate: FlutterAppDelegate, PKPushRegistryDelegate, CallkitIncomingAppDelegate {
|
|
|
|
|
|
|
|
|
|
// Expose the Flutter engine for SceneDelegate to use
|
|
|
|
|
lazy var flutterEngine = FlutterEngine(name: "my flutter engine")
|
|
|
|
|
@ -13,6 +15,9 @@ import flutter_callkit_incoming
|
|
|
|
|
// Must be retained for the lifetime of the app, otherwise VoIP pushes stop.
|
|
|
|
|
private var voipRegistry: PKPushRegistry?
|
|
|
|
|
|
|
|
|
|
// Keeps the app alive briefly after a decline so Dart can notify the caller.
|
|
|
|
|
private var declineTask: UIBackgroundTaskIdentifier = .invalid
|
|
|
|
|
|
|
|
|
|
override func application(
|
|
|
|
|
_ application: UIApplication,
|
|
|
|
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
|
|
|
|
@ -26,6 +31,7 @@ import flutter_callkit_incoming
|
|
|
|
|
// Register plugins with the engine
|
|
|
|
|
GeneratedPluginRegistrant.register(with: self.flutterEngine)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Register for VoIP pushes AFTER plugins, so flutter_callkit_incoming is ready.
|
|
|
|
|
let registry = PKPushRegistry(queue: .main)
|
|
|
|
|
registry.delegate = self
|
|
|
|
|
@ -46,6 +52,51 @@ import flutter_callkit_incoming
|
|
|
|
|
return sceneConfiguration
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - CallkitIncomingAppDelegate
|
|
|
|
|
// The plugin hands these actions to the app delegate; every one must be fulfilled
|
|
|
|
|
// (that is what the plugin does itself when the delegate does not conform).
|
|
|
|
|
|
|
|
|
|
func onAccept(_ call: Call, _ action: CXAnswerCallAction) {
|
|
|
|
|
action.fulfill()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func onDecline(_ call: Call, _ action: CXEndCallAction) {
|
|
|
|
|
action.fulfill()
|
|
|
|
|
// App launched in the background by a VoIP push (killed state): once the call is declined
|
|
|
|
|
// iOS may suspend the app almost immediately. Keep it alive briefly so the Dart decline
|
|
|
|
|
// handler (CallKitEvents in firebase_notification_manger.dart) can notify the caller.
|
|
|
|
|
beginDeclineBackgroundTask()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func onEnd(_ call: Call, _ action: CXEndCallAction) {
|
|
|
|
|
action.fulfill()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func onTimeOut(_ call: Call) {}
|
|
|
|
|
|
|
|
|
|
func didActivateAudioSession(_ audioSession: AVAudioSession) {}
|
|
|
|
|
|
|
|
|
|
func didDeactivateAudioSession(_ audioSession: AVAudioSession) {}
|
|
|
|
|
|
|
|
|
|
func providerDidReset() {}
|
|
|
|
|
|
|
|
|
|
private func beginDeclineBackgroundTask() {
|
|
|
|
|
endDeclineBackgroundTask()
|
|
|
|
|
declineTask = UIApplication.shared.beginBackgroundTask(withName: "CallDecline") { [weak self] in
|
|
|
|
|
self?.endDeclineBackgroundTask()
|
|
|
|
|
}
|
|
|
|
|
// Enough for the token request + SignalR connect + CallDeclinedAsync.
|
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 20) { [weak self] in
|
|
|
|
|
self?.endDeclineBackgroundTask()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func endDeclineBackgroundTask() {
|
|
|
|
|
guard declineTask != .invalid else { return }
|
|
|
|
|
UIApplication.shared.endBackgroundTask(declineTask)
|
|
|
|
|
declineTask = .invalid
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - PushKit (VoIP)
|
|
|
|
|
|
|
|
|
|
func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
|
|
|
|
|
@ -157,6 +208,11 @@ import flutter_callkit_incoming
|
|
|
|
|
"supportsVideo": true,
|
|
|
|
|
"maximumCallGroups": 1,
|
|
|
|
|
"maximumCallsPerCallGroup": 1,
|
|
|
|
|
// Don't let the plugin activate a playAndRecord audio session while ringing: while the app
|
|
|
|
|
// holds an active session, iOS routes the volume/side buttons to it instead of the CallKit
|
|
|
|
|
// call (no silence on press, no decline on double-press). CallKit activates audio on
|
|
|
|
|
// answer and flutter_webrtc configures it for the call.
|
|
|
|
|
"configureAudioSession": false,
|
|
|
|
|
"audioSessionMode": "default",
|
|
|
|
|
"audioSessionActive": true,
|
|
|
|
|
"audioSessionPreferredSampleRate": 44100.0,
|
|
|
|
|
|