Show ElementCalls in the system Recents list and allow deep linking back into a call from there

This commit is contained in:
Stefan Ceriu 2024-05-27 12:49:24 +03:00 committed by Stefan Ceriu
parent 16fc7caa62
commit f6c476eeca
10 changed files with 43 additions and 18 deletions

View File

@ -17,6 +17,7 @@
import AnalyticsEvents import AnalyticsEvents
import BackgroundTasks import BackgroundTasks
import Combine import Combine
import Intents
import MatrixRustSDK import MatrixRustSDK
import SwiftUI import SwiftUI
import Version import Version
@ -236,6 +237,20 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
return false return false
} }
func handleUserActivity(_ userActivity: NSUserActivity) {
// `INStartVideoCallIntent` is to be replaced with `INStartCallIntent`
// but calls from Recents still send it ¯\_()_/¯
guard let intent = userActivity.interaction?.intent as? INStartVideoCallIntent,
let contact = intent.contacts?.first,
let roomIdentifier = contact.personHandle?.value else {
MXLog.error("Failed retrieving information from userActivity: \(userActivity)")
return
}
MXLog.info("Starting call in room: \(roomIdentifier)")
handleAppRoute(AppRoute.call(roomID: roomIdentifier))
}
// MARK: - AuthenticationFlowCoordinatorDelegate // MARK: - AuthenticationFlowCoordinatorDelegate
func authenticationFlowCoordinator(didLoginWithSession userSession: UserSessionProtocol) { func authenticationFlowCoordinator(didLoginWithSession userSession: UserSessionProtocol) {

View File

@ -18,5 +18,8 @@ import Foundation
protocol AppCoordinatorProtocol: CoordinatorProtocol { protocol AppCoordinatorProtocol: CoordinatorProtocol {
var windowManager: SecureWindowManagerProtocol { get } var windowManager: SecureWindowManagerProtocol { get }
@discardableResult func handleDeepLink(_ url: URL, isExternalURL: Bool) -> Bool @discardableResult func handleDeepLink(_ url: URL, isExternalURL: Bool) -> Bool
func handleUserActivity(_ userActivity: NSUserActivity)
} }

View File

@ -51,6 +51,11 @@ struct Application: App {
openURLInSystemBrowser($0) openURLInSystemBrowser($0)
} }
} }
.onContinueUserActivity("INStartVideoCallIntent", perform: { userActivity in
// `INStartVideoCallIntent` is to be replaced with `INStartCallIntent`
// but calls from Recents still send it ¯\_()_/¯
appCoordinator.handleUserActivity(userActivity)
})
.task { .task {
appCoordinator.start() appCoordinator.start()
} }

View File

@ -91,7 +91,7 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) { func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
guard let roomID = payload.dictionaryPayload[ElementCallServiceNotificationKey.roomID.rawValue] as? String else { guard let roomID = payload.dictionaryPayload[ElementCallServiceNotificationKey.roomID.rawValue] as? String else {
MXLog.error("Somethnig went wrong, missing room identifier for incoming voip call: \(payload)") MXLog.error("Something went wrong, missing room identifier for incoming voip call: \(payload)")
return return
} }
@ -102,20 +102,18 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
let configuration = CXProviderConfiguration() let configuration = CXProviderConfiguration()
configuration.supportsVideo = true configuration.supportsVideo = true
configuration.includesCallsInRecents = false configuration.includesCallsInRecents = true
// https://stackoverflow.com/a/46077628/730924
configuration.supportedHandleTypes = [.generic]
let update = CXCallUpdate() let update = CXCallUpdate()
update.hasVideo = true update.hasVideo = true
update.localizedCallerName = payload.dictionaryPayload[ElementCallServiceNotificationKey.roomDisplayName.rawValue] as? String update.localizedCallerName = payload.dictionaryPayload[ElementCallServiceNotificationKey.roomDisplayName.rawValue] as? String
if let senderDisplayName = payload.dictionaryPayload[ElementCallServiceNotificationKey.senderDisplayName.rawValue] as? String { // https://stackoverflow.com/a/41230020/730924
update.remoteHandle = .init(type: .generic, value: senderDisplayName) update.remoteHandle = .init(type: .generic, value: roomID)
} else if let senderID = payload.dictionaryPayload[ElementCallServiceNotificationKey.senderID.rawValue] as? String {
update.remoteHandle = .init(type: .generic, value: senderID)
} else {
MXLog.error("Something went wrong, both the user display name and ID are nil")
}
let callProvider = CXProvider(configuration: configuration) let callProvider = CXProvider(configuration: configuration)
callProvider.setDelegate(self, queue: nil) callProvider.setDelegate(self, queue: nil)

View File

@ -24,8 +24,6 @@ enum ElementCallServiceAction {
enum ElementCallServiceNotificationKey: String { enum ElementCallServiceNotificationKey: String {
case roomID case roomID
case roomDisplayName case roomDisplayName
case senderID
case senderDisplayName
} }
let ElementCallServiceNotificationDiscardDelta = 10.0 let ElementCallServiceNotificationDiscardDelta = 10.0

View File

@ -70,6 +70,10 @@ class UITestsAppCoordinator: AppCoordinatorProtocol, SecureWindowManagerDelegate
fatalError("Not implemented.") fatalError("Not implemented.")
} }
func handleUserActivity(_ activity: NSUserActivity) {
fatalError("Not implemented.")
}
func windowManagerDidConfigureWindows(_ windowManager: SecureWindowManagerProtocol) { func windowManagerDidConfigureWindows(_ windowManager: SecureWindowManagerProtocol) {
ServiceLocator.shared.userIndicatorController.window = windowManager.overlayWindow ServiceLocator.shared.userIndicatorController.window = windowManager.overlayWindow

View File

@ -41,4 +41,8 @@ class UnitTestsAppCoordinator: AppCoordinatorProtocol {
func handleDeepLink(_ url: URL, isExternalURL: Bool) -> Bool { func handleDeepLink(_ url: URL, isExternalURL: Bool) -> Bool {
fatalError("Not implemented.") fatalError("Not implemented.")
} }
func handleUserActivity(_ activity: NSUserActivity) {
fatalError("Not implemented.")
}
} }

View File

@ -77,6 +77,7 @@
<key>NSUserActivityTypes</key> <key>NSUserActivityTypes</key>
<array> <array>
<string>INSendMessageIntent</string> <string>INSendMessageIntent</string>
<string>INStartCallIntent</string>
</array> </array>
<key>UIBackgroundModes</key> <key>UIBackgroundModes</key>
<array> <array>

View File

@ -82,7 +82,8 @@ targets:
productionAppName: $(PRODUCTION_APP_NAME) productionAppName: $(PRODUCTION_APP_NAME)
ITSAppUsesNonExemptEncryption: false ITSAppUsesNonExemptEncryption: false
NSUserActivityTypes: [ NSUserActivityTypes: [
INSendMessageIntent INSendMessageIntent,
INStartCallIntent,
] ]
NSCameraUsageDescription: To take pictures or videos and send them as a message $(APP_DISPLAY_NAME) needs access to the camera. NSCameraUsageDescription: To take pictures or videos and send them as a message $(APP_DISPLAY_NAME) needs access to the camera.
NSMicrophoneUsageDescription: To record and send messages with audio, $(APP_DISPLAY_NAME) needs to access the microphone. NSMicrophoneUsageDescription: To record and send messages with audio, $(APP_DISPLAY_NAME) needs to access the microphone.

View File

@ -206,12 +206,8 @@ class NotificationServiceExtension: UNNotificationServiceExtension {
return true return true
} }
var payload = [ElementCallServiceNotificationKey.roomID.rawValue: itemProxy.roomID, let payload = [ElementCallServiceNotificationKey.roomID.rawValue: itemProxy.roomID,
ElementCallServiceNotificationKey.roomDisplayName.rawValue: itemProxy.roomDisplayName, ElementCallServiceNotificationKey.roomDisplayName.rawValue: itemProxy.roomDisplayName]
ElementCallServiceNotificationKey.senderID.rawValue: itemProxy.senderID]
if let senderDisplayName = itemProxy.senderDisplayName {
payload[ElementCallServiceNotificationKey.senderDisplayName.rawValue] = senderDisplayName
}
do { do {
try await CXProvider.reportNewIncomingVoIPPushPayload(payload) try await CXProvider.reportNewIncomingVoIPPushPayload(payload)