Stop delaying ElementCall until the next sync loop and only notify other participants when presumed to already be up to date. (#3559)

This commit is contained in:
Stefan Ceriu
2024-11-27 09:57:18 +02:00
committed by GitHub
parent a292c41ca5
commit be8c1477c0
4 changed files with 52 additions and 56 deletions
@@ -201,7 +201,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
case .userProfile(let userID): case .userProfile(let userID):
stateMachine.processEvent(.showUserProfileScreen(userID: userID), userInfo: .init(animated: animated)) stateMachine.processEvent(.showUserProfileScreen(userID: userID), userInfo: .init(animated: animated))
case .call(let roomID): case .call(let roomID):
Task { await presentCallScreen(roomID: roomID) } Task { await presentCallScreen(roomID: roomID, notifyOtherParticipants: false) }
case .genericCallLink(let url): case .genericCallLink(let url):
presentCallScreen(genericCallLink: url) presentCallScreen(genericCallLink: url)
case .settings, .chatBackupSettings: case .settings, .chatBackupSettings:
@@ -579,7 +579,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
switch action { switch action {
case .presentCallScreen(let roomProxy): case .presentCallScreen(let roomProxy):
presentCallScreen(roomProxy: roomProxy) // Here we assume that the app is running and the call state is already up to date
presentCallScreen(roomProxy: roomProxy, notifyOtherParticipants: !roomProxy.infoPublisher.value.hasRoomCall)
case .finished: case .finished:
stateMachine.processEvent(.deselectRoom) stateMachine.processEvent(.deselectRoom)
} }
@@ -657,22 +658,23 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
presentCallScreen(configuration: .init(genericCallLink: url)) presentCallScreen(configuration: .init(genericCallLink: url))
} }
private func presentCallScreen(roomID: String) async { private func presentCallScreen(roomID: String, notifyOtherParticipants: Bool) async {
guard case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(roomID) else { guard case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(roomID) else {
return return
} }
presentCallScreen(roomProxy: roomProxy) presentCallScreen(roomProxy: roomProxy, notifyOtherParticipants: notifyOtherParticipants)
} }
private func presentCallScreen(roomProxy: JoinedRoomProxyProtocol) { private func presentCallScreen(roomProxy: JoinedRoomProxyProtocol, notifyOtherParticipants: Bool) {
let colorScheme: ColorScheme = appMediator.windowManager.mainWindow.traitCollection.userInterfaceStyle == .light ? .light : .dark let colorScheme: ColorScheme = appMediator.windowManager.mainWindow.traitCollection.userInterfaceStyle == .light ? .light : .dark
presentCallScreen(configuration: .init(roomProxy: roomProxy, presentCallScreen(configuration: .init(roomProxy: roomProxy,
clientProxy: userSession.clientProxy, clientProxy: userSession.clientProxy,
clientID: InfoPlistReader.main.bundleIdentifier, clientID: InfoPlistReader.main.bundleIdentifier,
elementCallBaseURL: appSettings.elementCallBaseURL, elementCallBaseURL: appSettings.elementCallBaseURL,
elementCallBaseURLOverride: appSettings.elementCallBaseURLOverride, elementCallBaseURLOverride: appSettings.elementCallBaseURLOverride,
colorScheme: colorScheme)) colorScheme: colorScheme,
notifyOtherParticipants: notifyOtherParticipants))
} }
private var callScreenPictureInPictureController: AVPictureInPictureController? private var callScreenPictureInPictureController: AVPictureInPictureController?
@@ -899,7 +901,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
navigationSplitCoordinator.setSheetCoordinator(nil) navigationSplitCoordinator.setSheetCoordinator(nil)
stateMachine.processEvent(.selectRoom(roomID: roomID, via: [], entryPoint: .room)) stateMachine.processEvent(.selectRoom(roomID: roomID, via: [], entryPoint: .room))
case .startCall(let roomID): case .startCall(let roomID):
Task { await self.presentCallScreen(roomID: roomID) } Task { await self.presentCallScreen(roomID: roomID, notifyOtherParticipants: false) }
case .dismiss: case .dismiss:
navigationSplitCoordinator.setSheetCoordinator(nil) navigationSplitCoordinator.setSheetCoordinator(nil)
} }
@@ -24,8 +24,6 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
actionsSubject.eraseToAnyPublisher() actionsSubject.eraseToAnyPublisher()
} }
private var syncUpdateCancellable: AnyCancellable?
/// Designated initialiser /// Designated initialiser
/// - Parameters: /// - Parameters:
/// - elementCallService: service responsible for setting up CallKit /// - elementCallService: service responsible for setting up CallKit
@@ -43,7 +41,7 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
switch configuration.kind { switch configuration.kind {
case .genericCallLink(let url): case .genericCallLink(let url):
widgetDriver = GenericCallLinkWidgetDriver(url: url) widgetDriver = GenericCallLinkWidgetDriver(url: url)
case .roomCall(let roomProxy, let clientProxy, _, _, _, _): case .roomCall(let roomProxy, let clientProxy, _, _, _, _, _):
guard let deviceID = clientProxy.deviceID else { fatalError("Missing device ID for the call.") } guard let deviceID = clientProxy.deviceID else { fatalError("Missing device ID for the call.") }
widgetDriver = roomProxy.elementCallWidgetDriver(deviceID: deviceID) widgetDriver = roomProxy.elementCallWidgetDriver(deviceID: deviceID)
} }
@@ -137,47 +135,39 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
case .genericCallLink(let url): case .genericCallLink(let url):
state.url = url state.url = url
// We need widget messaging to work before enabling CallKit, otherwise mute, hangup etc do nothing. // We need widget messaging to work before enabling CallKit, otherwise mute, hangup etc do nothing.
case .roomCall(let roomProxy, let clientProxy, let clientID, let elementCallBaseURL, let elementCallBaseURLOverride, let colorScheme): case .roomCall(let roomProxy, let clientProxy, let clientID, let elementCallBaseURL, let elementCallBaseURLOverride, let colorScheme, let notifyOtherParticipants):
// Wait for room states to be up to date before starting the call and notifying others Task { [weak self] in
syncUpdateCancellable = clientProxy.actionsPublisher guard let self else { return }
.filter(\.isSyncUpdate)
.timeout(.seconds(5), scheduler: DispatchQueue.main) let baseURL = if let elementCallBaseURLOverride {
.first() // Timeout will make the publisher complete, use first to handle both branches in the same place elementCallBaseURLOverride
.sink(receiveCompletion: { [weak self] _ in } else if case .success(let wellKnown) = await clientProxy.getElementWellKnown(), let wellKnownCall = wellKnown?.call {
Task { [weak self] in wellKnownCall.widgetURL
guard let self else { return } } else {
elementCallBaseURL
let baseURL = if let elementCallBaseURLOverride { }
elementCallBaseURLOverride
} else if case .success(let wellKnown) = await clientProxy.getElementWellKnown(), let wellKnownCall = wellKnown?.call { switch await widgetDriver.start(baseURL: baseURL, clientID: clientID, colorScheme: colorScheme) {
wellKnownCall.widgetURL case .success(let url):
} else { state.url = url
elementCallBaseURL case .failure(let error):
} MXLog.error("Failed starting ElementCall Widget Driver with error: \(error)")
state.bindings.alertInfo = .init(id: UUID(),
switch await widgetDriver.start(baseURL: baseURL, clientID: clientID, colorScheme: colorScheme) { title: L10n.errorUnknown,
case .success(let url): primaryButton: .init(title: L10n.actionOk) {
state.url = url self.actionsSubject.send(.dismiss)
case .failure(let error): })
MXLog.error("Failed starting ElementCall Widget Driver with error: \(error)") return
state.bindings.alertInfo = .init(id: UUID(), }
title: L10n.errorUnknown,
primaryButton: .init(title: L10n.actionOk) { await elementCallService.setupCallSession(roomID: roomProxy.id,
self.actionsSubject.send(.dismiss) roomDisplayName: roomProxy.infoPublisher.value.displayName ?? roomProxy.id)
})
if notifyOtherParticipants {
return _ = await roomProxy.sendCallNotificationIfNeeded()
} }
}
await elementCallService.setupCallSession(roomID: roomProxy.id,
roomDisplayName: roomProxy.infoPublisher.value.displayName ?? roomProxy.id)
_ = await roomProxy.sendCallNotificationIfNeeded()
syncUpdateCancellable = nil
}
}, receiveValue: { _ in })
} }
} }
@@ -303,7 +303,8 @@ struct CallScreen_Previews: PreviewProvider {
clientID: "io.element.elementx", clientID: "io.element.elementx",
elementCallBaseURL: "https://call.element.io", elementCallBaseURL: "https://call.element.io",
elementCallBaseURLOverride: nil, elementCallBaseURLOverride: nil,
colorScheme: .light), colorScheme: .light,
notifyOtherParticipants: false),
allowPictureInPicture: false, allowPictureInPicture: false,
appHooks: AppHooks()) appHooks: AppHooks())
}() }()
@@ -21,7 +21,8 @@ struct ElementCallConfiguration {
clientID: String, clientID: String,
elementCallBaseURL: URL, elementCallBaseURL: URL,
elementCallBaseURLOverride: URL?, elementCallBaseURLOverride: URL?,
colorScheme: ColorScheme) colorScheme: ColorScheme,
notifyOtherParticipants: Bool)
} }
/// The type of call being configured i.e. whether it's an external URL or an internal room call. /// The type of call being configured i.e. whether it's an external URL or an internal room call.
@@ -58,13 +59,15 @@ struct ElementCallConfiguration {
clientID: String, clientID: String,
elementCallBaseURL: URL, elementCallBaseURL: URL,
elementCallBaseURLOverride: URL?, elementCallBaseURLOverride: URL?,
colorScheme: ColorScheme) { colorScheme: ColorScheme,
notifyOtherParticipants: Bool) {
kind = .roomCall(roomProxy: roomProxy, kind = .roomCall(roomProxy: roomProxy,
clientProxy: clientProxy, clientProxy: clientProxy,
clientID: clientID, clientID: clientID,
elementCallBaseURL: elementCallBaseURL, elementCallBaseURL: elementCallBaseURL,
elementCallBaseURLOverride: elementCallBaseURLOverride, elementCallBaseURLOverride: elementCallBaseURLOverride,
colorScheme: colorScheme) colorScheme: colorScheme,
notifyOtherParticipants: notifyOtherParticipants)
} }
/// A string representing the call being configured. /// A string representing the call being configured.
@@ -72,7 +75,7 @@ struct ElementCallConfiguration {
switch kind { switch kind {
case .genericCallLink(let url): case .genericCallLink(let url):
url.absoluteString url.absoluteString
case .roomCall(let roomProxy, _, _, _, _, _): case .roomCall(let roomProxy, _, _, _, _, _, _):
roomProxy.id roomProxy.id
} }
} }