mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 01:20:12 +00:00
Move call presentation from the chats flow into the user session flow. (#4459)
This commit is contained in:
@@ -6,7 +6,6 @@
|
||||
//
|
||||
|
||||
import AnalyticsEvents
|
||||
import AVKit
|
||||
import Combine
|
||||
import MatrixRustSDK
|
||||
import SwiftUI
|
||||
@@ -15,6 +14,8 @@ enum ChatsFlowCoordinatorAction {
|
||||
case showSettings
|
||||
case showChatBackupSettings
|
||||
case sessionVerification(SessionVerificationScreenFlow)
|
||||
case showCallScreen(roomProxy: JoinedRoomProxyProtocol)
|
||||
case hideCallScreenOverlay
|
||||
case logout
|
||||
}
|
||||
|
||||
@@ -143,10 +144,6 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
case .userProfile(let userID):
|
||||
stateMachine.processEvent(.showUserProfileScreen(userID: userID), userInfo: .init(animated: animated))
|
||||
case .call(let roomID):
|
||||
Task { await presentCallScreen(roomID: roomID) }
|
||||
case .genericCallLink(let url):
|
||||
presentCallScreen(genericCallLink: url)
|
||||
case .share(let payload):
|
||||
if let roomID = payload.roomID {
|
||||
stateMachine.processEvent(.selectRoom(roomID: roomID,
|
||||
@@ -162,7 +159,7 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
} else {
|
||||
stateMachine.processEvent(.selectRoom(roomID: roomID, via: [], entryPoint: .transferOwnership))
|
||||
}
|
||||
case .accountProvisioningLink, .settings, .chatBackupSettings:
|
||||
case .accountProvisioningLink, .settings, .chatBackupSettings, .call, .genericCallLink:
|
||||
break // These routes cannot be handled.
|
||||
}
|
||||
}
|
||||
@@ -200,7 +197,7 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
} else {
|
||||
startRoomFlow(roomID: roomID, via: via, entryPoint: entryPoint, animated: animated)
|
||||
}
|
||||
hideCallScreenOverlay() // Turn any active call into a PiP so that navigation from a notification is visible to the user.
|
||||
actionsSubject.send(.hideCallScreenOverlay) // Turn any active call into a PiP so that navigation from a notification is visible to the user.
|
||||
case(.roomList, .deselectRoom, .roomList):
|
||||
dismissRoomFlow(animated: animated)
|
||||
|
||||
@@ -302,18 +299,6 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
flowParameters.elementCallService.actions
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] action in
|
||||
switch action {
|
||||
case .endCall:
|
||||
self?.dismissCallScreenIfNeeded()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
private func processDecryptionError(_ info: UnableToDecryptInfo) {
|
||||
@@ -465,7 +450,7 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
switch action {
|
||||
case .presentCallScreen(let roomProxy):
|
||||
presentCallScreen(roomProxy: roomProxy)
|
||||
actionsSubject.send(.showCallScreen(roomProxy: roomProxy))
|
||||
case .verifyUser(let userID):
|
||||
actionsSubject.send(.sessionVerification(.userInitiator(userID: userID)))
|
||||
case .finished:
|
||||
@@ -543,89 +528,6 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
self?.stateMachine.processEvent(.dismissedStartChatScreen)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Calls
|
||||
|
||||
private func presentCallScreen(genericCallLink url: URL) {
|
||||
presentCallScreen(configuration: .init(genericCallLink: url))
|
||||
}
|
||||
|
||||
private func presentCallScreen(roomID: String) async {
|
||||
guard case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(roomID) else {
|
||||
return
|
||||
}
|
||||
|
||||
presentCallScreen(roomProxy: roomProxy)
|
||||
}
|
||||
|
||||
private func presentCallScreen(roomProxy: JoinedRoomProxyProtocol) {
|
||||
let colorScheme: ColorScheme = flowParameters.windowManager.mainWindow.traitCollection.userInterfaceStyle == .light ? .light : .dark
|
||||
presentCallScreen(configuration: .init(roomProxy: roomProxy,
|
||||
clientProxy: userSession.clientProxy,
|
||||
clientID: InfoPlistReader.main.bundleIdentifier,
|
||||
elementCallBaseURL: flowParameters.appSettings.elementCallBaseURL,
|
||||
elementCallBaseURLOverride: flowParameters.appSettings.elementCallBaseURLOverride,
|
||||
colorScheme: colorScheme))
|
||||
}
|
||||
|
||||
private var callScreenPictureInPictureController: AVPictureInPictureController?
|
||||
private func presentCallScreen(configuration: ElementCallConfiguration) {
|
||||
guard flowParameters.ongoingCallRoomIDPublisher.value != configuration.callRoomID else {
|
||||
MXLog.info("Returning to existing call.")
|
||||
callScreenPictureInPictureController?.stopPictureInPicture()
|
||||
return
|
||||
}
|
||||
|
||||
let callScreenCoordinator = CallScreenCoordinator(parameters: .init(elementCallService: flowParameters.elementCallService,
|
||||
configuration: configuration,
|
||||
allowPictureInPicture: true,
|
||||
appSettings: flowParameters.appSettings,
|
||||
appHooks: flowParameters.appHooks,
|
||||
analytics: flowParameters.analytics))
|
||||
|
||||
callScreenCoordinator.actions
|
||||
.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .pictureInPictureIsAvailable(let controller):
|
||||
callScreenPictureInPictureController = controller
|
||||
case .pictureInPictureStarted:
|
||||
MXLog.info("Hiding call for PiP presentation.")
|
||||
navigationSplitCoordinator.setOverlayPresentationMode(.minimized)
|
||||
case .pictureInPictureStopped:
|
||||
MXLog.info("Restoring call after PiP presentation.")
|
||||
navigationSplitCoordinator.setOverlayPresentationMode(.fullScreen)
|
||||
case .dismiss:
|
||||
callScreenPictureInPictureController = nil
|
||||
navigationSplitCoordinator.setOverlayCoordinator(nil)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
navigationSplitCoordinator.setOverlayCoordinator(callScreenCoordinator, animated: true)
|
||||
|
||||
flowParameters.analytics.track(screen: .RoomCall)
|
||||
}
|
||||
|
||||
private func hideCallScreenOverlay() {
|
||||
guard let callScreenPictureInPictureController else {
|
||||
MXLog.warning("Picture in picture isn't available, dismissing the call screen.")
|
||||
dismissCallScreenIfNeeded()
|
||||
return
|
||||
}
|
||||
|
||||
MXLog.info("Starting picture in picture to hide the call screen overlay.")
|
||||
callScreenPictureInPictureController.startPictureInPicture()
|
||||
navigationSplitCoordinator.setOverlayPresentationMode(.minimized)
|
||||
}
|
||||
|
||||
private func dismissCallScreenIfNeeded() {
|
||||
guard navigationSplitCoordinator.overlayCoordinator is CallScreenCoordinator else {
|
||||
return
|
||||
}
|
||||
|
||||
navigationSplitCoordinator.setOverlayCoordinator(nil)
|
||||
}
|
||||
|
||||
// MARK: Secure backup
|
||||
|
||||
@@ -768,8 +670,8 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
case .openDirectChat(let roomID):
|
||||
navigationSplitCoordinator.setSheetCoordinator(nil)
|
||||
stateMachine.processEvent(.selectRoom(roomID: roomID, via: [], entryPoint: .room))
|
||||
case .startCall(let roomID):
|
||||
Task { await self.presentCallScreen(roomID: roomID) }
|
||||
case .startCall(let roomProxy):
|
||||
actionsSubject.send(.showCallScreen(roomProxy: roomProxy))
|
||||
case .dismiss:
|
||||
navigationSplitCoordinator.setSheetCoordinator(nil)
|
||||
}
|
||||
|
||||
@@ -215,14 +215,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
navigationStackCoordinator.setSheetCoordinator(stackCoordinator, animated: true)
|
||||
}
|
||||
|
||||
private func presentCallScreen(roomID: String) async {
|
||||
guard case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(roomID) else {
|
||||
return
|
||||
}
|
||||
|
||||
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
|
||||
}
|
||||
|
||||
private func handleRoomRoute(roomID: String, via: [String], presentationAction: PresentationAction? = nil, animated: Bool) async {
|
||||
guard roomID == self.roomID else { fatalError("Navigation route doesn't belong to this room flow.") }
|
||||
|
||||
@@ -1128,8 +1120,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
stateMachine.tryEvent(.presentUserProfile(userID: userID))
|
||||
case .openDirectChat(let roomID):
|
||||
stateMachine.tryEvent(.startChildFlow(roomID: roomID, via: [], entryPoint: .room))
|
||||
case .startCall(let roomID):
|
||||
Task { await self.presentCallScreen(roomID: roomID) }
|
||||
case .startCall(let roomProxy):
|
||||
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
|
||||
case .verifyUser(let userID):
|
||||
actionsSubject.send(.verifyUser(userID: userID))
|
||||
}
|
||||
@@ -1154,8 +1146,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
switch action {
|
||||
case .openDirectChat(let roomID):
|
||||
stateMachine.tryEvent(.startChildFlow(roomID: roomID, via: [], entryPoint: .room))
|
||||
case .startCall(let roomID):
|
||||
Task { await self.presentCallScreen(roomID: roomID) }
|
||||
case .startCall(let roomProxy):
|
||||
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
|
||||
case .dismiss:
|
||||
break // Not supported when pushed.
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import AVKit
|
||||
import Combine
|
||||
import Compound
|
||||
import MatrixRustSDK
|
||||
@@ -127,10 +128,14 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
stateMachine.tryEvent(.showSettingsScreen)
|
||||
}
|
||||
settingsFlowCoordinator?.handleAppRoute(appRoute, animated: animated)
|
||||
case .call(let roomID):
|
||||
Task { await presentCallScreen(roomID: roomID) }
|
||||
case .genericCallLink(let url):
|
||||
presentCallScreen(genericCallLink: url)
|
||||
case .roomList, .room, .roomAlias, .childRoom, .childRoomAlias,
|
||||
.roomDetails, .roomMemberDetails, .userProfile,
|
||||
.event, .eventOnRoomAlias, .childEvent, .childEventOnRoomAlias,
|
||||
.call, .genericCallLink, .share, .transferOwnership:
|
||||
.share, .transferOwnership:
|
||||
clearPresentedSheets(animated: animated) // Make sure the presented route is visible.
|
||||
chatsFlowCoordinator.handleAppRoute(appRoute, animated: animated)
|
||||
if navigationTabCoordinator.selectedTab != .chats {
|
||||
@@ -198,6 +203,10 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
handleAppRoute(.chatBackupSettings, animated: true)
|
||||
case .sessionVerification(let flow):
|
||||
presentSessionVerificationScreen(flow: flow)
|
||||
case .showCallScreen(let roomProxy):
|
||||
presentCallScreen(roomProxy: roomProxy)
|
||||
case .hideCallScreenOverlay:
|
||||
hideCallScreenOverlay()
|
||||
case .logout:
|
||||
Task { await self.runLogoutFlow() }
|
||||
}
|
||||
@@ -241,6 +250,18 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
flowParameters.elementCallService.actions
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] action in
|
||||
switch action {
|
||||
case .endCall:
|
||||
self?.dismissCallScreenIfNeeded()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
flowParameters.appSettings.$spacesEnabled
|
||||
.combineLatest(userSession.clientProxy.spaceService.joinedSpacesPublisher)
|
||||
.map { $0 && !$1.isEmpty ? nil : .hidden }
|
||||
@@ -346,6 +367,89 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
navigationTabCoordinator.setSheetCoordinator(navigationStackCoordinator)
|
||||
}
|
||||
|
||||
// MARK: - Calls
|
||||
|
||||
private func presentCallScreen(genericCallLink url: URL) {
|
||||
presentCallScreen(configuration: .init(genericCallLink: url))
|
||||
}
|
||||
|
||||
private func presentCallScreen(roomID: String) async {
|
||||
guard case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(roomID) else {
|
||||
return
|
||||
}
|
||||
|
||||
presentCallScreen(roomProxy: roomProxy)
|
||||
}
|
||||
|
||||
private func presentCallScreen(roomProxy: JoinedRoomProxyProtocol) {
|
||||
let colorScheme: ColorScheme = flowParameters.windowManager.mainWindow.traitCollection.userInterfaceStyle == .light ? .light : .dark
|
||||
presentCallScreen(configuration: .init(roomProxy: roomProxy,
|
||||
clientProxy: userSession.clientProxy,
|
||||
clientID: InfoPlistReader.main.bundleIdentifier,
|
||||
elementCallBaseURL: flowParameters.appSettings.elementCallBaseURL,
|
||||
elementCallBaseURLOverride: flowParameters.appSettings.elementCallBaseURLOverride,
|
||||
colorScheme: colorScheme))
|
||||
}
|
||||
|
||||
private var callScreenPictureInPictureController: AVPictureInPictureController?
|
||||
private func presentCallScreen(configuration: ElementCallConfiguration) {
|
||||
guard flowParameters.ongoingCallRoomIDPublisher.value != configuration.callRoomID else {
|
||||
MXLog.info("Returning to existing call.")
|
||||
callScreenPictureInPictureController?.stopPictureInPicture()
|
||||
return
|
||||
}
|
||||
|
||||
let callScreenCoordinator = CallScreenCoordinator(parameters: .init(elementCallService: flowParameters.elementCallService,
|
||||
configuration: configuration,
|
||||
allowPictureInPicture: true,
|
||||
appSettings: flowParameters.appSettings,
|
||||
appHooks: flowParameters.appHooks,
|
||||
analytics: flowParameters.analytics))
|
||||
|
||||
callScreenCoordinator.actions
|
||||
.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .pictureInPictureIsAvailable(let controller):
|
||||
callScreenPictureInPictureController = controller
|
||||
case .pictureInPictureStarted:
|
||||
MXLog.info("Hiding call for PiP presentation.")
|
||||
navigationTabCoordinator.setOverlayPresentationMode(.minimized)
|
||||
case .pictureInPictureStopped:
|
||||
MXLog.info("Restoring call after PiP presentation.")
|
||||
navigationTabCoordinator.setOverlayPresentationMode(.fullScreen)
|
||||
case .dismiss:
|
||||
callScreenPictureInPictureController = nil
|
||||
navigationTabCoordinator.setOverlayCoordinator(nil)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
navigationTabCoordinator.setOverlayCoordinator(callScreenCoordinator, animated: true)
|
||||
|
||||
flowParameters.analytics.track(screen: .RoomCall)
|
||||
}
|
||||
|
||||
private func hideCallScreenOverlay() {
|
||||
guard let callScreenPictureInPictureController else {
|
||||
MXLog.warning("Picture in picture isn't available, dismissing the call screen.")
|
||||
dismissCallScreenIfNeeded()
|
||||
return
|
||||
}
|
||||
|
||||
MXLog.info("Starting picture in picture to hide the call screen overlay.")
|
||||
callScreenPictureInPictureController.startPictureInPicture()
|
||||
navigationTabCoordinator.setOverlayPresentationMode(.minimized)
|
||||
}
|
||||
|
||||
private func dismissCallScreenIfNeeded() {
|
||||
guard navigationTabCoordinator.overlayCoordinator is CallScreenCoordinator else {
|
||||
return
|
||||
}
|
||||
|
||||
navigationTabCoordinator.setOverlayCoordinator(nil)
|
||||
}
|
||||
|
||||
// MARK: - Logout
|
||||
|
||||
private func runLogoutFlow() async {
|
||||
|
||||
Reference in New Issue
Block a user