Add support for starting voice calls from a DM (#5305)

* feat: Start voice call from DM

* rename voiceCall:bool to isVoiceCall

* review: Fix a typo

* review: use one displayCall(bool) instead of 2 actions

* review: Add a new specific preview for DM calls

* combine startCall and startVoiceCall in single enum with isVoiceCall

* review: add isVoiceCall to presentCallScreen action

* review: Use proper a11y for voice vs  video

* add voice/video options to UserProfile Screen

* fixup: move config params to the roomInfo object

* review: Revert changes on preview as the toolbar cannot be snapshot'd

* review: Extract call controls in specific file

* oups: Add voice call option in room details screen

* Update room details screenshots

* Update user profile screenshots

* Update room member details screenshots

* fixup: remove dead code
This commit is contained in:
Valere Fedronic
2026-04-09 17:22:31 +02:00
committed by GitHub
parent 7434d531b2
commit b05ab3ac0b
76 changed files with 303 additions and 201 deletions
@@ -16,7 +16,7 @@ enum ChatsTabFlowCoordinatorAction {
case showSettings
case showChatBackupSettings
case sessionVerification(SessionVerificationScreenFlow)
case showCallScreen(roomProxy: JoinedRoomProxyProtocol)
case showCallScreen(roomProxy: JoinedRoomProxyProtocol, isVoiceCall: Bool)
case hideCallScreenOverlay
case logout
}
@@ -535,8 +535,8 @@ class ChatsTabFlowCoordinator: FlowCoordinatorProtocol {
guard let self else { return }
switch action {
case .presentCallScreen(let roomProxy):
actionsSubject.send(.showCallScreen(roomProxy: roomProxy))
case .presentCallScreen(let roomProxy, let isVoiceCall):
actionsSubject.send(.showCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
case .verifyUser(let userID):
actionsSubject.send(.sessionVerification(.userInitiator(userID: userID)))
case .continueWithSpaceFlow(let spaceRoomListProxy):
@@ -597,8 +597,8 @@ class ChatsTabFlowCoordinator: FlowCoordinatorProtocol {
.sink { [weak self] action in
guard let self else { return }
switch action {
case .presentCallScreen(let roomProxy):
actionsSubject.send(.showCallScreen(roomProxy: roomProxy))
case .presentCallScreen(let roomProxy, let isVoiceCall):
actionsSubject.send(.showCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
case .verifyUser(let userID):
actionsSubject.send(.sessionVerification(.userInitiator(userID: userID)))
case .finished:
@@ -800,8 +800,8 @@ class ChatsTabFlowCoordinator: FlowCoordinatorProtocol {
case .openDirectChat(let roomID):
navigationSplitCoordinator.setSheetCoordinator(nil)
stateMachine.processEvent(.selectRoom(roomID: roomID, via: [], entryPoint: .room))
case .startCall(let roomProxy):
actionsSubject.send(.showCallScreen(roomProxy: roomProxy))
case .startCall(let roomProxy, let isVoiceCall):
actionsSubject.send(.showCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
case .dismiss:
navigationSplitCoordinator.setSheetCoordinator(nil)
}
@@ -12,7 +12,7 @@ import SwiftUI
import UserNotifications
enum RoomFlowCoordinatorAction: Equatable {
case presentCallScreen(roomProxy: JoinedRoomProxyProtocol)
case presentCallScreen(roomProxy: JoinedRoomProxyProtocol, isVoiceCall: Bool)
case verifyUser(userID: String)
/// The requested room was actually a space. The room flow has been dismissed
/// and a space flow should be started to continue.
@@ -21,8 +21,8 @@ enum RoomFlowCoordinatorAction: Equatable {
static func == (lhs: RoomFlowCoordinatorAction, rhs: RoomFlowCoordinatorAction) -> Bool {
switch (lhs, rhs) {
case (.presentCallScreen(let lhsRoomProxy), .presentCallScreen(let rhsRoomProxy)):
lhsRoomProxy.id == rhsRoomProxy.id
case (.presentCallScreen(let lhsRoomProxy, let lhsIsVoiceCall), .presentCallScreen(let rhsRoomProxy, let rhsIsVoiceCall)):
lhsRoomProxy.id == rhsRoomProxy.id && lhsIsVoiceCall == rhsIsVoiceCall
case (.finished, .finished):
true
default:
@@ -717,7 +717,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
case .presentMessageForwarding(let forwardingItem):
stateMachine.tryEvent(.presentMessageForwarding(forwardingItem: forwardingItem))
case .presentCallScreen:
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: false))
case .presentPinnedEventsTimeline:
stateMachine.tryEvent(.presentPinnedEventsTimeline)
case .presentResolveSendFailure(failure: let failure, sendHandle: let sendHandle):
@@ -952,8 +952,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
stateMachine.tryEvent(.presentPollsHistory)
case .presentRolesAndPermissionsScreen:
stateMachine.tryEvent(.presentRolesAndPermissionsScreen)
case .presentCall:
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
case .presentCall(isVoiceCall: let isVoiceCall):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
case .presentPinnedEventsTimeline:
stateMachine.tryEvent(.presentPinnedEventsTimeline)
case .presentKnockingRequestsListScreen:
@@ -1505,8 +1505,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
guard let self else { return }
switch action {
case .presentCallScreen(let roomProxy):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
case .presentCallScreen(let roomProxy, let isVoiceCall):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
case .verifyUser(let userID):
actionsSubject.send(.verifyUser(userID: userID))
case .continueWithSpaceFlow(let spaceRoomListProxy):
@@ -1615,8 +1615,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
.sink { [weak self] action in
guard let self else { return }
switch action {
case .presentCallScreen(let roomProxy):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
case .presentCallScreen(let roomProxy, let isVoiceCall):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
case .verifyUser(let userID):
actionsSubject.send(.verifyUser(userID: userID))
case .finished:
@@ -1641,8 +1641,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
switch action {
case .finished:
stateMachine.tryEvent(.stopMembersFlow)
case .presentCallScreen(let roomProxy):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
case .presentCallScreen(let roomProxy, let isVoiceCall):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
case .verifyUser(let userID):
actionsSubject.send(.verifyUser(userID: userID))
}
@@ -11,7 +11,7 @@ import SwiftUI
enum RoomMembersFlowCoordinatorAction {
case finished
case presentCallScreen(roomProxy: JoinedRoomProxyProtocol)
case presentCallScreen(roomProxy: JoinedRoomProxyProtocol, isVoiceCall: Bool)
case verifyUser(userID: String)
}
@@ -235,8 +235,8 @@ final class RoomMembersFlowCoordinator: FlowCoordinatorProtocol {
stateMachine.tryEvent(.presentUserProfile(userID: userID))
case .openDirectChat(let roomID):
stateMachine.tryEvent(.startRoomFlow(roomID: roomID, via: [], eventID: nil))
case .startCall(let roomProxy):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
case .startCall(let roomProxy, let isVoiceCall):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
case .verifyUser(let userID):
actionsSubject.send(.verifyUser(userID: userID))
}
@@ -294,8 +294,8 @@ final class RoomMembersFlowCoordinator: FlowCoordinatorProtocol {
switch action {
case .openDirectChat(let roomID):
stateMachine.tryEvent(.startRoomFlow(roomID: roomID, via: [], eventID: nil))
case .startCall(let roomProxy):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
case .startCall(let roomProxy, let isVoiceCall):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
case .dismiss:
break // Not supported when pushed.
}
@@ -322,8 +322,8 @@ final class RoomMembersFlowCoordinator: FlowCoordinatorProtocol {
guard let self else { return }
switch action {
case .presentCallScreen(let roomProxy):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
case .presentCallScreen(let roomProxy, let isVoiceCall):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
case .verifyUser(let userID):
actionsSubject.send(.verifyUser(userID: userID))
case .continueWithSpaceFlow:
@@ -11,7 +11,7 @@ import Foundation
import SwiftState
enum SpaceFlowCoordinatorAction {
case presentCallScreen(roomProxy: JoinedRoomProxyProtocol)
case presentCallScreen(roomProxy: JoinedRoomProxyProtocol, isVoiceCall: Bool)
case verifyUser(userID: String)
case finished
}
@@ -504,8 +504,8 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
guard let self else { return }
switch action {
case .presentCallScreen(let roomProxy):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
case .presentCallScreen(let roomProxy, let isVoiceCall):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
case .verifyUser(let userID):
actionsSubject.send(.verifyUser(userID: userID))
case .finished:
@@ -529,8 +529,8 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
guard let self else { return }
switch action {
case .presentCallScreen(let roomProxy):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
case .presentCallScreen(let roomProxy, let isVoiceCall):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: false))
case .verifyUser(let userID):
actionsSubject.send(.verifyUser(userID: userID))
case .continueWithSpaceFlow(let spaceRoomListProxy):
@@ -557,8 +557,8 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
switch actions {
case .finished:
stateMachine.tryEvent(.stopMembersFlow)
case .presentCallScreen(let roomProxy):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
case .presentCallScreen(let roomProxy, let isVoiceCall):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: false))
case .verifyUser(let userID):
actionsSubject.send(.verifyUser(userID: userID))
}
@@ -582,7 +582,7 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
stateMachine.tryEvent(.leftSpace)
}
case .presentCallScreen(let roomProxy):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: false))
case .verifyUser(userID: let userID):
actionsSubject.send(.verifyUser(userID: userID))
}
@@ -395,7 +395,7 @@ final class SpaceSettingsFlowCoordinator: FlowCoordinatorProtocol {
switch action {
case .finished:
stateMachine.tryEvent(.stopMembersListFlow)
case .presentCallScreen(let roomProxy):
case .presentCallScreen(let roomProxy, let isVoiceCall):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
case .verifyUser(let userID):
actionsSubject.send(.verifyUser(userID: userID))
@@ -12,7 +12,7 @@ import SwiftState
enum SpacesTabFlowCoordinatorAction {
case showSettings
case presentCallScreen(roomProxy: JoinedRoomProxyProtocol)
case presentCallScreen(roomProxy: JoinedRoomProxyProtocol, isVoiceCall: Bool)
case verifyUser(userID: String)
}
@@ -178,8 +178,8 @@ class SpacesTabFlowCoordinator: FlowCoordinatorProtocol {
guard let self else { return }
switch action {
case .presentCallScreen(let roomProxy):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
case .presentCallScreen(let roomProxy, let isVoiceCall):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
case .verifyUser(let userID):
actionsSubject.send(.verifyUser(userID: userID))
case .finished:
@@ -207,8 +207,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
handleAppRoute(.chatBackupSettings, animated: true)
case .sessionVerification(let flow):
presentSessionVerificationScreen(flow: flow)
case .showCallScreen(let roomProxy):
presentCallScreen(roomProxy: roomProxy, voiceOnly: false)
case .showCallScreen(let roomProxy, let isVoiceCall):
presentCallScreen(roomProxy: roomProxy, voiceOnly: isVoiceCall)
case .hideCallScreenOverlay:
hideCallScreenOverlay()
case .logout:
@@ -221,8 +221,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
.sink { [weak self] action in
guard let self else { return }
switch action {
case .presentCallScreen(let roomProxy):
presentCallScreen(roomProxy: roomProxy, voiceOnly: false)
case .presentCallScreen(let roomProxy, let isVoiceCall):
presentCallScreen(roomProxy: roomProxy, voiceOnly: isVoiceCall)
case .verifyUser(let userID):
presentSessionVerificationScreen(flow: .userInitiator(userID: userID))
case .showSettings: