From be8c1477c025d9141f5938f9f3b673f0e105011c Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Wed, 27 Nov 2024 09:57:18 +0200 Subject: [PATCH] Stop delaying ElementCall until the next sync loop and only notify other participants when presumed to already be up to date. (#3559) --- .../UserSessionFlowCoordinator.swift | 16 ++-- .../CallScreen/CallScreenViewModel.swift | 78 ++++++++----------- .../Screens/CallScreen/View/CallScreen.swift | 3 +- .../ElementCallConfiguration.swift | 11 ++- 4 files changed, 52 insertions(+), 56 deletions(-) diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index 0c7af9484..a057d2a74 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -201,7 +201,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { case .userProfile(let userID): stateMachine.processEvent(.showUserProfileScreen(userID: userID), userInfo: .init(animated: animated)) case .call(let roomID): - Task { await presentCallScreen(roomID: roomID) } + Task { await presentCallScreen(roomID: roomID, notifyOtherParticipants: false) } case .genericCallLink(let url): presentCallScreen(genericCallLink: url) case .settings, .chatBackupSettings: @@ -579,7 +579,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { switch action { 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: stateMachine.processEvent(.deselectRoom) } @@ -657,22 +658,23 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { 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 { 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 presentCallScreen(configuration: .init(roomProxy: roomProxy, clientProxy: userSession.clientProxy, clientID: InfoPlistReader.main.bundleIdentifier, elementCallBaseURL: appSettings.elementCallBaseURL, elementCallBaseURLOverride: appSettings.elementCallBaseURLOverride, - colorScheme: colorScheme)) + colorScheme: colorScheme, + notifyOtherParticipants: notifyOtherParticipants)) } private var callScreenPictureInPictureController: AVPictureInPictureController? @@ -899,7 +901,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { navigationSplitCoordinator.setSheetCoordinator(nil) stateMachine.processEvent(.selectRoom(roomID: roomID, via: [], entryPoint: .room)) case .startCall(let roomID): - Task { await self.presentCallScreen(roomID: roomID) } + Task { await self.presentCallScreen(roomID: roomID, notifyOtherParticipants: false) } case .dismiss: navigationSplitCoordinator.setSheetCoordinator(nil) } diff --git a/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift b/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift index 4c41b4358..c00623c44 100644 --- a/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift +++ b/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift @@ -24,8 +24,6 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol actionsSubject.eraseToAnyPublisher() } - private var syncUpdateCancellable: AnyCancellable? - /// Designated initialiser /// - Parameters: /// - elementCallService: service responsible for setting up CallKit @@ -43,7 +41,7 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol switch configuration.kind { case .genericCallLink(let 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.") } widgetDriver = roomProxy.elementCallWidgetDriver(deviceID: deviceID) } @@ -137,47 +135,39 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol case .genericCallLink(let url): state.url = url // 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): - // Wait for room states to be up to date before starting the call and notifying others - syncUpdateCancellable = clientProxy.actionsPublisher - .filter(\.isSyncUpdate) - .timeout(.seconds(5), scheduler: DispatchQueue.main) - .first() // Timeout will make the publisher complete, use first to handle both branches in the same place - .sink(receiveCompletion: { [weak self] _ in - Task { [weak self] in - guard let self else { return } - - let baseURL = if let elementCallBaseURLOverride { - elementCallBaseURLOverride - } else if case .success(let wellKnown) = await clientProxy.getElementWellKnown(), let wellKnownCall = wellKnown?.call { - wellKnownCall.widgetURL - } else { - elementCallBaseURL - } - - switch await widgetDriver.start(baseURL: baseURL, clientID: clientID, colorScheme: colorScheme) { - case .success(let url): - state.url = url - case .failure(let error): - MXLog.error("Failed starting ElementCall Widget Driver with error: \(error)") - state.bindings.alertInfo = .init(id: UUID(), - title: L10n.errorUnknown, - primaryButton: .init(title: L10n.actionOk) { - self.actionsSubject.send(.dismiss) - }) - - return - } - - await elementCallService.setupCallSession(roomID: roomProxy.id, - roomDisplayName: roomProxy.infoPublisher.value.displayName ?? roomProxy.id) - - _ = await roomProxy.sendCallNotificationIfNeeded() - - syncUpdateCancellable = nil - } - }, receiveValue: { _ in }) + + case .roomCall(let roomProxy, let clientProxy, let clientID, let elementCallBaseURL, let elementCallBaseURLOverride, let colorScheme, let notifyOtherParticipants): + Task { [weak self] in + guard let self else { return } + + let baseURL = if let elementCallBaseURLOverride { + elementCallBaseURLOverride + } else if case .success(let wellKnown) = await clientProxy.getElementWellKnown(), let wellKnownCall = wellKnown?.call { + wellKnownCall.widgetURL + } else { + elementCallBaseURL + } + + switch await widgetDriver.start(baseURL: baseURL, clientID: clientID, colorScheme: colorScheme) { + case .success(let url): + state.url = url + case .failure(let error): + MXLog.error("Failed starting ElementCall Widget Driver with error: \(error)") + state.bindings.alertInfo = .init(id: UUID(), + title: L10n.errorUnknown, + primaryButton: .init(title: L10n.actionOk) { + self.actionsSubject.send(.dismiss) + }) + return + } + + await elementCallService.setupCallSession(roomID: roomProxy.id, + roomDisplayName: roomProxy.infoPublisher.value.displayName ?? roomProxy.id) + + if notifyOtherParticipants { + _ = await roomProxy.sendCallNotificationIfNeeded() + } + } } } diff --git a/ElementX/Sources/Screens/CallScreen/View/CallScreen.swift b/ElementX/Sources/Screens/CallScreen/View/CallScreen.swift index 97e224ed9..2cd0d5559 100644 --- a/ElementX/Sources/Screens/CallScreen/View/CallScreen.swift +++ b/ElementX/Sources/Screens/CallScreen/View/CallScreen.swift @@ -303,7 +303,8 @@ struct CallScreen_Previews: PreviewProvider { clientID: "io.element.elementx", elementCallBaseURL: "https://call.element.io", elementCallBaseURLOverride: nil, - colorScheme: .light), + colorScheme: .light, + notifyOtherParticipants: false), allowPictureInPicture: false, appHooks: AppHooks()) }() diff --git a/ElementX/Sources/Services/ElementCall/ElementCallConfiguration.swift b/ElementX/Sources/Services/ElementCall/ElementCallConfiguration.swift index 31da3bb16..29bbdf3cd 100644 --- a/ElementX/Sources/Services/ElementCall/ElementCallConfiguration.swift +++ b/ElementX/Sources/Services/ElementCall/ElementCallConfiguration.swift @@ -21,7 +21,8 @@ struct ElementCallConfiguration { clientID: String, elementCallBaseURL: 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. @@ -58,13 +59,15 @@ struct ElementCallConfiguration { clientID: String, elementCallBaseURL: URL, elementCallBaseURLOverride: URL?, - colorScheme: ColorScheme) { + colorScheme: ColorScheme, + notifyOtherParticipants: Bool) { kind = .roomCall(roomProxy: roomProxy, clientProxy: clientProxy, clientID: clientID, elementCallBaseURL: elementCallBaseURL, elementCallBaseURLOverride: elementCallBaseURLOverride, - colorScheme: colorScheme) + colorScheme: colorScheme, + notifyOtherParticipants: notifyOtherParticipants) } /// A string representing the call being configured. @@ -72,7 +75,7 @@ struct ElementCallConfiguration { switch kind { case .genericCallLink(let url): url.absoluteString - case .roomCall(let roomProxy, _, _, _, _, _): + case .roomCall(let roomProxy, _, _, _, _, _, _): roomProxy.id } }