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:
@@ -1245,6 +1245,7 @@
|
|||||||
D34E328E9E65904358248FDD /* GlobalSearchScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 436A0D98D372B17EAE9AA999 /* GlobalSearchScreenModels.swift */; };
|
D34E328E9E65904358248FDD /* GlobalSearchScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 436A0D98D372B17EAE9AA999 /* GlobalSearchScreenModels.swift */; };
|
||||||
D38E59C48BE5499A48D12031 /* CreateRoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AC8FCE224D4185F28636FF /* CreateRoomScreenCoordinator.swift */; };
|
D38E59C48BE5499A48D12031 /* CreateRoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AC8FCE224D4185F28636FF /* CreateRoomScreenCoordinator.swift */; };
|
||||||
D3FD96913D2B1AAA3149DAC7 /* CreateRoomViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69D42EE0102D2857933625DD /* CreateRoomViewModelTests.swift */; };
|
D3FD96913D2B1AAA3149DAC7 /* CreateRoomViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69D42EE0102D2857933625DD /* CreateRoomViewModelTests.swift */; };
|
||||||
|
D433A58BFF77B3E563FB547E /* RoomCallControlsToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48A2FA6814F824ABB4C07F3 /* RoomCallControlsToolbar.swift */; };
|
||||||
D46C33F8B61B55F0C8C2D15F /* LocationRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B2AC540DE619B36832A5DB5 /* LocationRoomTimelineItem.swift */; };
|
D46C33F8B61B55F0C8C2D15F /* LocationRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B2AC540DE619B36832A5DB5 /* LocationRoomTimelineItem.swift */; };
|
||||||
D4CB979EB4FE26AAD9F9A72B /* UserProfileScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 604A69C081B935D6A38DE6D8 /* UserProfileScreenModels.swift */; };
|
D4CB979EB4FE26AAD9F9A72B /* UserProfileScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 604A69C081B935D6A38DE6D8 /* UserProfileScreenModels.swift */; };
|
||||||
D4D7CCECC6C0AAFC42E165BB /* NotificationPermissionsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE9BBB18FB27F09032AD8769 /* NotificationPermissionsScreenViewModel.swift */; };
|
D4D7CCECC6C0AAFC42E165BB /* NotificationPermissionsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE9BBB18FB27F09032AD8769 /* NotificationPermissionsScreenViewModel.swift */; };
|
||||||
@@ -2954,6 +2955,7 @@
|
|||||||
F4469F6AE311BDC439B3A5EC /* UserSessionMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionMock.swift; sourceTree = "<group>"; };
|
F4469F6AE311BDC439B3A5EC /* UserSessionMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionMock.swift; sourceTree = "<group>"; };
|
||||||
F4548A9BDE5CB3AB864BCA9F /* EffectsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EffectsView.swift; sourceTree = "<group>"; };
|
F4548A9BDE5CB3AB864BCA9F /* EffectsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EffectsView.swift; sourceTree = "<group>"; };
|
||||||
F46E441BA50705E6CEC89FE0 /* RoomSummaryProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProviderTests.swift; sourceTree = "<group>"; };
|
F46E441BA50705E6CEC89FE0 /* RoomSummaryProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProviderTests.swift; sourceTree = "<group>"; };
|
||||||
|
F48A2FA6814F824ABB4C07F3 /* RoomCallControlsToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomCallControlsToolbar.swift; sourceTree = "<group>"; };
|
||||||
F4CEB4590CCF70F0E3C0B171 /* GeneratedAccessibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneratedAccessibilityTests.swift; sourceTree = "<group>"; };
|
F4CEB4590CCF70F0E3C0B171 /* GeneratedAccessibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneratedAccessibilityTests.swift; sourceTree = "<group>"; };
|
||||||
F506C6ADB1E1DA6638078E11 /* UITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
F506C6ADB1E1DA6638078E11 /* UITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
F51D674A5B5F1FE1B878E20F /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = "<group>"; };
|
F51D674A5B5F1FE1B878E20F /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
@@ -5058,6 +5060,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
47F441A78A5CAA9E2937E463 /* KnockRequestsBannerView.swift */,
|
47F441A78A5CAA9E2937E463 /* KnockRequestsBannerView.swift */,
|
||||||
|
F48A2FA6814F824ABB4C07F3 /* RoomCallControlsToolbar.swift */,
|
||||||
5221DFDF809142A2D6AC82B9 /* RoomScreen.swift */,
|
5221DFDF809142A2D6AC82B9 /* RoomScreen.swift */,
|
||||||
4137900E28201C314C835C11 /* RoomScreenFooterView.swift */,
|
4137900E28201C314C835C11 /* RoomScreenFooterView.swift */,
|
||||||
4552D3466B1453F287223ADA /* SwipeRightAction.swift */,
|
4552D3466B1453F287223ADA /* SwipeRightAction.swift */,
|
||||||
@@ -8595,6 +8598,7 @@
|
|||||||
C7CEFC1FB0547CFC8F5C84EF /* Room.swift in Sources */,
|
C7CEFC1FB0547CFC8F5C84EF /* Room.swift in Sources */,
|
||||||
6E391F7F628D984AF44385D9 /* RoomAttachmentPicker.swift in Sources */,
|
6E391F7F628D984AF44385D9 /* RoomAttachmentPicker.swift in Sources */,
|
||||||
8587A53DE8EF94FD796DC375 /* RoomAvatarImage.swift in Sources */,
|
8587A53DE8EF94FD796DC375 /* RoomAvatarImage.swift in Sources */,
|
||||||
|
D433A58BFF77B3E563FB547E /* RoomCallControlsToolbar.swift in Sources */,
|
||||||
F8C87130FD999F7F1076208C /* RoomChangePermissionsScreen.swift in Sources */,
|
F8C87130FD999F7F1076208C /* RoomChangePermissionsScreen.swift in Sources */,
|
||||||
86F9D3028A1F4AE819D75560 /* RoomChangePermissionsScreenCoordinator.swift in Sources */,
|
86F9D3028A1F4AE819D75560 /* RoomChangePermissionsScreenCoordinator.swift in Sources */,
|
||||||
4D4D236F0BBCDC4D2CBCCBB5 /* RoomChangePermissionsScreenModels.swift in Sources */,
|
4D4D236F0BBCDC4D2CBCCBB5 /* RoomChangePermissionsScreenModels.swift in Sources */,
|
||||||
|
|||||||
@@ -1576,6 +1576,7 @@
|
|||||||
"screen_leave_space_choose_owners_action" = "Choose owners";
|
"screen_leave_space_choose_owners_action" = "Choose owners";
|
||||||
"screen_create_room_room_access_section_private_option_title" = "Private";
|
"screen_create_room_room_access_section_private_option_title" = "Private";
|
||||||
"screen_security_and_privacy_room_access_space_members_option_title" = "Space members";
|
"screen_security_and_privacy_room_access_space_members_option_title" = "Space members";
|
||||||
|
"a11y_start_video_call" = "Start a video call";
|
||||||
"screen_chat_backup_recovery_action_setup" = "Get recovery key";
|
"screen_chat_backup_recovery_action_setup" = "Get recovery key";
|
||||||
"screen_signout_confirmation_dialog_submit" = "Remove this device";
|
"screen_signout_confirmation_dialog_submit" = "Remove this device";
|
||||||
"screen_signout_confirmation_dialog_title" = "Remove this device";
|
"screen_signout_confirmation_dialog_title" = "Remove this device";
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ enum ChatsTabFlowCoordinatorAction {
|
|||||||
case showSettings
|
case showSettings
|
||||||
case showChatBackupSettings
|
case showChatBackupSettings
|
||||||
case sessionVerification(SessionVerificationScreenFlow)
|
case sessionVerification(SessionVerificationScreenFlow)
|
||||||
case showCallScreen(roomProxy: JoinedRoomProxyProtocol)
|
case showCallScreen(roomProxy: JoinedRoomProxyProtocol, isVoiceCall: Bool)
|
||||||
case hideCallScreenOverlay
|
case hideCallScreenOverlay
|
||||||
case logout
|
case logout
|
||||||
}
|
}
|
||||||
@@ -535,8 +535,8 @@ class ChatsTabFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case .presentCallScreen(let roomProxy):
|
case .presentCallScreen(let roomProxy, let isVoiceCall):
|
||||||
actionsSubject.send(.showCallScreen(roomProxy: roomProxy))
|
actionsSubject.send(.showCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
|
||||||
case .verifyUser(let userID):
|
case .verifyUser(let userID):
|
||||||
actionsSubject.send(.sessionVerification(.userInitiator(userID: userID)))
|
actionsSubject.send(.sessionVerification(.userInitiator(userID: userID)))
|
||||||
case .continueWithSpaceFlow(let spaceRoomListProxy):
|
case .continueWithSpaceFlow(let spaceRoomListProxy):
|
||||||
@@ -597,8 +597,8 @@ class ChatsTabFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
.sink { [weak self] action in
|
.sink { [weak self] action in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
switch action {
|
switch action {
|
||||||
case .presentCallScreen(let roomProxy):
|
case .presentCallScreen(let roomProxy, let isVoiceCall):
|
||||||
actionsSubject.send(.showCallScreen(roomProxy: roomProxy))
|
actionsSubject.send(.showCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
|
||||||
case .verifyUser(let userID):
|
case .verifyUser(let userID):
|
||||||
actionsSubject.send(.sessionVerification(.userInitiator(userID: userID)))
|
actionsSubject.send(.sessionVerification(.userInitiator(userID: userID)))
|
||||||
case .finished:
|
case .finished:
|
||||||
@@ -800,8 +800,8 @@ class ChatsTabFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case .openDirectChat(let roomID):
|
case .openDirectChat(let roomID):
|
||||||
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 roomProxy):
|
case .startCall(let roomProxy, let isVoiceCall):
|
||||||
actionsSubject.send(.showCallScreen(roomProxy: roomProxy))
|
actionsSubject.send(.showCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
navigationSplitCoordinator.setSheetCoordinator(nil)
|
navigationSplitCoordinator.setSheetCoordinator(nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import SwiftUI
|
|||||||
import UserNotifications
|
import UserNotifications
|
||||||
|
|
||||||
enum RoomFlowCoordinatorAction: Equatable {
|
enum RoomFlowCoordinatorAction: Equatable {
|
||||||
case presentCallScreen(roomProxy: JoinedRoomProxyProtocol)
|
case presentCallScreen(roomProxy: JoinedRoomProxyProtocol, isVoiceCall: Bool)
|
||||||
case verifyUser(userID: String)
|
case verifyUser(userID: String)
|
||||||
/// The requested room was actually a space. The room flow has been dismissed
|
/// The requested room was actually a space. The room flow has been dismissed
|
||||||
/// and a space flow should be started to continue.
|
/// and a space flow should be started to continue.
|
||||||
@@ -21,8 +21,8 @@ enum RoomFlowCoordinatorAction: Equatable {
|
|||||||
|
|
||||||
static func == (lhs: RoomFlowCoordinatorAction, rhs: RoomFlowCoordinatorAction) -> Bool {
|
static func == (lhs: RoomFlowCoordinatorAction, rhs: RoomFlowCoordinatorAction) -> Bool {
|
||||||
switch (lhs, rhs) {
|
switch (lhs, rhs) {
|
||||||
case (.presentCallScreen(let lhsRoomProxy), .presentCallScreen(let rhsRoomProxy)):
|
case (.presentCallScreen(let lhsRoomProxy, let lhsIsVoiceCall), .presentCallScreen(let rhsRoomProxy, let rhsIsVoiceCall)):
|
||||||
lhsRoomProxy.id == rhsRoomProxy.id
|
lhsRoomProxy.id == rhsRoomProxy.id && lhsIsVoiceCall == rhsIsVoiceCall
|
||||||
case (.finished, .finished):
|
case (.finished, .finished):
|
||||||
true
|
true
|
||||||
default:
|
default:
|
||||||
@@ -717,7 +717,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case .presentMessageForwarding(let forwardingItem):
|
case .presentMessageForwarding(let forwardingItem):
|
||||||
stateMachine.tryEvent(.presentMessageForwarding(forwardingItem: forwardingItem))
|
stateMachine.tryEvent(.presentMessageForwarding(forwardingItem: forwardingItem))
|
||||||
case .presentCallScreen:
|
case .presentCallScreen:
|
||||||
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
|
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: false))
|
||||||
case .presentPinnedEventsTimeline:
|
case .presentPinnedEventsTimeline:
|
||||||
stateMachine.tryEvent(.presentPinnedEventsTimeline)
|
stateMachine.tryEvent(.presentPinnedEventsTimeline)
|
||||||
case .presentResolveSendFailure(failure: let failure, sendHandle: let sendHandle):
|
case .presentResolveSendFailure(failure: let failure, sendHandle: let sendHandle):
|
||||||
@@ -952,8 +952,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
stateMachine.tryEvent(.presentPollsHistory)
|
stateMachine.tryEvent(.presentPollsHistory)
|
||||||
case .presentRolesAndPermissionsScreen:
|
case .presentRolesAndPermissionsScreen:
|
||||||
stateMachine.tryEvent(.presentRolesAndPermissionsScreen)
|
stateMachine.tryEvent(.presentRolesAndPermissionsScreen)
|
||||||
case .presentCall:
|
case .presentCall(isVoiceCall: let isVoiceCall):
|
||||||
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
|
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
|
||||||
case .presentPinnedEventsTimeline:
|
case .presentPinnedEventsTimeline:
|
||||||
stateMachine.tryEvent(.presentPinnedEventsTimeline)
|
stateMachine.tryEvent(.presentPinnedEventsTimeline)
|
||||||
case .presentKnockingRequestsListScreen:
|
case .presentKnockingRequestsListScreen:
|
||||||
@@ -1505,8 +1505,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case .presentCallScreen(let roomProxy):
|
case .presentCallScreen(let roomProxy, let isVoiceCall):
|
||||||
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
|
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
|
||||||
case .verifyUser(let userID):
|
case .verifyUser(let userID):
|
||||||
actionsSubject.send(.verifyUser(userID: userID))
|
actionsSubject.send(.verifyUser(userID: userID))
|
||||||
case .continueWithSpaceFlow(let spaceRoomListProxy):
|
case .continueWithSpaceFlow(let spaceRoomListProxy):
|
||||||
@@ -1615,8 +1615,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
.sink { [weak self] action in
|
.sink { [weak self] action in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
switch action {
|
switch action {
|
||||||
case .presentCallScreen(let roomProxy):
|
case .presentCallScreen(let roomProxy, let isVoiceCall):
|
||||||
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
|
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
|
||||||
case .verifyUser(let userID):
|
case .verifyUser(let userID):
|
||||||
actionsSubject.send(.verifyUser(userID: userID))
|
actionsSubject.send(.verifyUser(userID: userID))
|
||||||
case .finished:
|
case .finished:
|
||||||
@@ -1641,8 +1641,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
switch action {
|
switch action {
|
||||||
case .finished:
|
case .finished:
|
||||||
stateMachine.tryEvent(.stopMembersFlow)
|
stateMachine.tryEvent(.stopMembersFlow)
|
||||||
case .presentCallScreen(let roomProxy):
|
case .presentCallScreen(let roomProxy, let isVoiceCall):
|
||||||
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
|
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
|
||||||
case .verifyUser(let userID):
|
case .verifyUser(let userID):
|
||||||
actionsSubject.send(.verifyUser(userID: userID))
|
actionsSubject.send(.verifyUser(userID: userID))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import SwiftUI
|
|||||||
|
|
||||||
enum RoomMembersFlowCoordinatorAction {
|
enum RoomMembersFlowCoordinatorAction {
|
||||||
case finished
|
case finished
|
||||||
case presentCallScreen(roomProxy: JoinedRoomProxyProtocol)
|
case presentCallScreen(roomProxy: JoinedRoomProxyProtocol, isVoiceCall: Bool)
|
||||||
case verifyUser(userID: String)
|
case verifyUser(userID: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,8 +235,8 @@ final class RoomMembersFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
stateMachine.tryEvent(.presentUserProfile(userID: userID))
|
stateMachine.tryEvent(.presentUserProfile(userID: userID))
|
||||||
case .openDirectChat(let roomID):
|
case .openDirectChat(let roomID):
|
||||||
stateMachine.tryEvent(.startRoomFlow(roomID: roomID, via: [], eventID: nil))
|
stateMachine.tryEvent(.startRoomFlow(roomID: roomID, via: [], eventID: nil))
|
||||||
case .startCall(let roomProxy):
|
case .startCall(let roomProxy, let isVoiceCall):
|
||||||
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
|
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
|
||||||
case .verifyUser(let userID):
|
case .verifyUser(let userID):
|
||||||
actionsSubject.send(.verifyUser(userID: userID))
|
actionsSubject.send(.verifyUser(userID: userID))
|
||||||
}
|
}
|
||||||
@@ -294,8 +294,8 @@ final class RoomMembersFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
switch action {
|
switch action {
|
||||||
case .openDirectChat(let roomID):
|
case .openDirectChat(let roomID):
|
||||||
stateMachine.tryEvent(.startRoomFlow(roomID: roomID, via: [], eventID: nil))
|
stateMachine.tryEvent(.startRoomFlow(roomID: roomID, via: [], eventID: nil))
|
||||||
case .startCall(let roomProxy):
|
case .startCall(let roomProxy, let isVoiceCall):
|
||||||
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
|
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
break // Not supported when pushed.
|
break // Not supported when pushed.
|
||||||
}
|
}
|
||||||
@@ -322,8 +322,8 @@ final class RoomMembersFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case .presentCallScreen(let roomProxy):
|
case .presentCallScreen(let roomProxy, let isVoiceCall):
|
||||||
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
|
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
|
||||||
case .verifyUser(let userID):
|
case .verifyUser(let userID):
|
||||||
actionsSubject.send(.verifyUser(userID: userID))
|
actionsSubject.send(.verifyUser(userID: userID))
|
||||||
case .continueWithSpaceFlow:
|
case .continueWithSpaceFlow:
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import Foundation
|
|||||||
import SwiftState
|
import SwiftState
|
||||||
|
|
||||||
enum SpaceFlowCoordinatorAction {
|
enum SpaceFlowCoordinatorAction {
|
||||||
case presentCallScreen(roomProxy: JoinedRoomProxyProtocol)
|
case presentCallScreen(roomProxy: JoinedRoomProxyProtocol, isVoiceCall: Bool)
|
||||||
case verifyUser(userID: String)
|
case verifyUser(userID: String)
|
||||||
case finished
|
case finished
|
||||||
}
|
}
|
||||||
@@ -504,8 +504,8 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case .presentCallScreen(let roomProxy):
|
case .presentCallScreen(let roomProxy, let isVoiceCall):
|
||||||
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
|
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
|
||||||
case .verifyUser(let userID):
|
case .verifyUser(let userID):
|
||||||
actionsSubject.send(.verifyUser(userID: userID))
|
actionsSubject.send(.verifyUser(userID: userID))
|
||||||
case .finished:
|
case .finished:
|
||||||
@@ -529,8 +529,8 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case .presentCallScreen(let roomProxy):
|
case .presentCallScreen(let roomProxy, let isVoiceCall):
|
||||||
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
|
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: false))
|
||||||
case .verifyUser(let userID):
|
case .verifyUser(let userID):
|
||||||
actionsSubject.send(.verifyUser(userID: userID))
|
actionsSubject.send(.verifyUser(userID: userID))
|
||||||
case .continueWithSpaceFlow(let spaceRoomListProxy):
|
case .continueWithSpaceFlow(let spaceRoomListProxy):
|
||||||
@@ -557,8 +557,8 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
switch actions {
|
switch actions {
|
||||||
case .finished:
|
case .finished:
|
||||||
stateMachine.tryEvent(.stopMembersFlow)
|
stateMachine.tryEvent(.stopMembersFlow)
|
||||||
case .presentCallScreen(let roomProxy):
|
case .presentCallScreen(let roomProxy, let isVoiceCall):
|
||||||
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
|
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: false))
|
||||||
case .verifyUser(let userID):
|
case .verifyUser(let userID):
|
||||||
actionsSubject.send(.verifyUser(userID: userID))
|
actionsSubject.send(.verifyUser(userID: userID))
|
||||||
}
|
}
|
||||||
@@ -582,7 +582,7 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
stateMachine.tryEvent(.leftSpace)
|
stateMachine.tryEvent(.leftSpace)
|
||||||
}
|
}
|
||||||
case .presentCallScreen(let roomProxy):
|
case .presentCallScreen(let roomProxy):
|
||||||
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
|
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: false))
|
||||||
case .verifyUser(userID: let userID):
|
case .verifyUser(userID: let userID):
|
||||||
actionsSubject.send(.verifyUser(userID: userID))
|
actionsSubject.send(.verifyUser(userID: userID))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -395,7 +395,7 @@ final class SpaceSettingsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
switch action {
|
switch action {
|
||||||
case .finished:
|
case .finished:
|
||||||
stateMachine.tryEvent(.stopMembersListFlow)
|
stateMachine.tryEvent(.stopMembersListFlow)
|
||||||
case .presentCallScreen(let roomProxy):
|
case .presentCallScreen(let roomProxy, let isVoiceCall):
|
||||||
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
|
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
|
||||||
case .verifyUser(let userID):
|
case .verifyUser(let userID):
|
||||||
actionsSubject.send(.verifyUser(userID: userID))
|
actionsSubject.send(.verifyUser(userID: userID))
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import SwiftState
|
|||||||
|
|
||||||
enum SpacesTabFlowCoordinatorAction {
|
enum SpacesTabFlowCoordinatorAction {
|
||||||
case showSettings
|
case showSettings
|
||||||
case presentCallScreen(roomProxy: JoinedRoomProxyProtocol)
|
case presentCallScreen(roomProxy: JoinedRoomProxyProtocol, isVoiceCall: Bool)
|
||||||
case verifyUser(userID: String)
|
case verifyUser(userID: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,8 +178,8 @@ class SpacesTabFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case .presentCallScreen(let roomProxy):
|
case .presentCallScreen(let roomProxy, let isVoiceCall):
|
||||||
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
|
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
|
||||||
case .verifyUser(let userID):
|
case .verifyUser(let userID):
|
||||||
actionsSubject.send(.verifyUser(userID: userID))
|
actionsSubject.send(.verifyUser(userID: userID))
|
||||||
case .finished:
|
case .finished:
|
||||||
|
|||||||
@@ -207,8 +207,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
handleAppRoute(.chatBackupSettings, animated: true)
|
handleAppRoute(.chatBackupSettings, animated: true)
|
||||||
case .sessionVerification(let flow):
|
case .sessionVerification(let flow):
|
||||||
presentSessionVerificationScreen(flow: flow)
|
presentSessionVerificationScreen(flow: flow)
|
||||||
case .showCallScreen(let roomProxy):
|
case .showCallScreen(let roomProxy, let isVoiceCall):
|
||||||
presentCallScreen(roomProxy: roomProxy, voiceOnly: false)
|
presentCallScreen(roomProxy: roomProxy, voiceOnly: isVoiceCall)
|
||||||
case .hideCallScreenOverlay:
|
case .hideCallScreenOverlay:
|
||||||
hideCallScreenOverlay()
|
hideCallScreenOverlay()
|
||||||
case .logout:
|
case .logout:
|
||||||
@@ -221,8 +221,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
.sink { [weak self] action in
|
.sink { [weak self] action in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
switch action {
|
switch action {
|
||||||
case .presentCallScreen(let roomProxy):
|
case .presentCallScreen(let roomProxy, let isVoiceCall):
|
||||||
presentCallScreen(roomProxy: roomProxy, voiceOnly: false)
|
presentCallScreen(roomProxy: roomProxy, voiceOnly: isVoiceCall)
|
||||||
case .verifyUser(let userID):
|
case .verifyUser(let userID):
|
||||||
presentSessionVerificationScreen(flow: .userInitiator(userID: userID))
|
presentSessionVerificationScreen(flow: .userInitiator(userID: userID))
|
||||||
case .showSettings:
|
case .showSettings:
|
||||||
|
|||||||
@@ -120,6 +120,8 @@ internal enum L10n {
|
|||||||
internal static var a11yShowPassword: String { return L10n.tr("Localizable", "a11y_show_password") }
|
internal static var a11yShowPassword: String { return L10n.tr("Localizable", "a11y_show_password") }
|
||||||
/// Start a call
|
/// Start a call
|
||||||
internal static var a11yStartCall: String { return L10n.tr("Localizable", "a11y_start_call") }
|
internal static var a11yStartCall: String { return L10n.tr("Localizable", "a11y_start_call") }
|
||||||
|
/// Start a video call
|
||||||
|
internal static var a11yStartVideoCall: String { return L10n.tr("Localizable", "a11y_start_video_call") }
|
||||||
/// Start a voice call
|
/// Start a voice call
|
||||||
internal static var a11yStartVoiceCall: String { return L10n.tr("Localizable", "a11y_start_voice_call") }
|
internal static var a11yStartVoiceCall: String { return L10n.tr("Localizable", "a11y_start_voice_call") }
|
||||||
/// Tombstoned room
|
/// Tombstoned room
|
||||||
|
|||||||
@@ -157,6 +157,8 @@ enum A11yIdentifiers {
|
|||||||
let attachmentPickerTextFormatting = "room-attachment_picker_text_formatting"
|
let attachmentPickerTextFormatting = "room-attachment_picker_text_formatting"
|
||||||
let timelineItemActionMenu = "room-timeline_item_action_menu"
|
let timelineItemActionMenu = "room-timeline_item_action_menu"
|
||||||
let joinCall = "room-join_call"
|
let joinCall = "room-join_call"
|
||||||
|
let startVoiceCall = "room-start_voice_call"
|
||||||
|
let startVideoCall = "room-start_video_call"
|
||||||
let scrollToBottom = "room-scroll_to_bottom"
|
let scrollToBottom = "room-scroll_to_bottom"
|
||||||
|
|
||||||
let messageComposer = "room-message_composer"
|
let messageComposer = "room-message_composer"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ enum RoomDetailsScreenCoordinatorAction {
|
|||||||
case presentInviteUsersScreen
|
case presentInviteUsersScreen
|
||||||
case presentPollsHistory
|
case presentPollsHistory
|
||||||
case presentRolesAndPermissionsScreen
|
case presentRolesAndPermissionsScreen
|
||||||
case presentCall
|
case presentCall(isVoiceCall: Bool)
|
||||||
case presentPinnedEventsTimeline
|
case presentPinnedEventsTimeline
|
||||||
case presentMediaEventsTimeline
|
case presentMediaEventsTimeline
|
||||||
case presentKnockingRequestsListScreen
|
case presentKnockingRequestsListScreen
|
||||||
@@ -81,8 +81,8 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
|
|||||||
actionsSubject.send(.presentPollsHistory)
|
actionsSubject.send(.presentPollsHistory)
|
||||||
case .requestRolesAndPermissionsPresentation:
|
case .requestRolesAndPermissionsPresentation:
|
||||||
actionsSubject.send(.presentRolesAndPermissionsScreen)
|
actionsSubject.send(.presentRolesAndPermissionsScreen)
|
||||||
case .startCall:
|
case .startCall(let isVoiceCall):
|
||||||
actionsSubject.send(.presentCall)
|
actionsSubject.send(.presentCall(isVoiceCall: isVoiceCall))
|
||||||
case .displayPinnedEventsTimeline:
|
case .displayPinnedEventsTimeline:
|
||||||
actionsSubject.send(.presentPinnedEventsTimeline)
|
actionsSubject.send(.presentPinnedEventsTimeline)
|
||||||
case .displayMediaEventsTimeline:
|
case .displayMediaEventsTimeline:
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ enum RoomDetailsScreenViewModelAction: Equatable {
|
|||||||
case requestEditDetailsPresentation
|
case requestEditDetailsPresentation
|
||||||
case requestPollsHistoryPresentation
|
case requestPollsHistoryPresentation
|
||||||
case requestRolesAndPermissionsPresentation
|
case requestRolesAndPermissionsPresentation
|
||||||
case startCall
|
case startCall(isVoiceCall: Bool)
|
||||||
case displayPinnedEventsTimeline
|
case displayPinnedEventsTimeline
|
||||||
case displayMediaEventsTimeline
|
case displayMediaEventsTimeline
|
||||||
case displayKnockingRequests
|
case displayKnockingRequests
|
||||||
@@ -94,7 +94,10 @@ struct RoomDetailsScreenViewState: BindableState {
|
|||||||
var shortcuts: [RoomDetailsScreenViewShortcut] {
|
var shortcuts: [RoomDetailsScreenViewShortcut] {
|
||||||
var shortcuts: [RoomDetailsScreenViewShortcut] = [.mute]
|
var shortcuts: [RoomDetailsScreenViewShortcut] = [.mute]
|
||||||
if !ProcessInfo.processInfo.isiOSAppOnMac, canJoinCall {
|
if !ProcessInfo.processInfo.isiOSAppOnMac, canJoinCall {
|
||||||
shortcuts.append(.call)
|
if isDirect {
|
||||||
|
shortcuts.append(.voiceCall)
|
||||||
|
}
|
||||||
|
shortcuts.append(.videoCall)
|
||||||
}
|
}
|
||||||
if dmRecipientInfo == nil, canInviteUsers {
|
if dmRecipientInfo == nil, canInviteUsers {
|
||||||
shortcuts.append(.invite)
|
shortcuts.append(.invite)
|
||||||
@@ -223,7 +226,7 @@ enum RoomDetailsScreenViewAction {
|
|||||||
case toggleFavourite(isFavourite: Bool)
|
case toggleFavourite(isFavourite: Bool)
|
||||||
case processTapRolesAndPermissions
|
case processTapRolesAndPermissions
|
||||||
case processTapSecurityAndPrivacy
|
case processTapSecurityAndPrivacy
|
||||||
case processTapCall
|
case processTapCall(isVoiceCall: Bool)
|
||||||
case processTapPinnedEvents
|
case processTapPinnedEvents
|
||||||
case processTapMediaEvents
|
case processTapMediaEvents
|
||||||
case processTapRequestsToJoin
|
case processTapRequestsToJoin
|
||||||
@@ -233,7 +236,8 @@ enum RoomDetailsScreenViewAction {
|
|||||||
enum RoomDetailsScreenViewShortcut {
|
enum RoomDetailsScreenViewShortcut {
|
||||||
case share(link: URL)
|
case share(link: URL)
|
||||||
case mute
|
case mute
|
||||||
case call
|
case videoCall
|
||||||
|
case voiceCall
|
||||||
case invite
|
case invite
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -151,8 +151,8 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
|||||||
Task { await toggleFavourite(isFavourite) }
|
Task { await toggleFavourite(isFavourite) }
|
||||||
case .processTapRolesAndPermissions:
|
case .processTapRolesAndPermissions:
|
||||||
actionsSubject.send(.requestRolesAndPermissionsPresentation)
|
actionsSubject.send(.requestRolesAndPermissionsPresentation)
|
||||||
case .processTapCall:
|
case .processTapCall(let isVoiceCall):
|
||||||
actionsSubject.send(.startCall)
|
actionsSubject.send(.startCall(isVoiceCall: isVoiceCall))
|
||||||
case .processTapPinnedEvents:
|
case .processTapPinnedEvents:
|
||||||
analyticsService.trackInteraction(name: .PinnedMessageRoomInfoButton)
|
analyticsService.trackInteraction(name: .PinnedMessageRoomInfoButton)
|
||||||
actionsSubject.send(.displayPinnedEventsTimeline)
|
actionsSubject.send(.displayPinnedEventsTimeline)
|
||||||
|
|||||||
@@ -85,13 +85,22 @@ struct RoomDetailsScreen: View {
|
|||||||
CompoundIcon(\.shareIos)
|
CompoundIcon(\.shareIos)
|
||||||
}
|
}
|
||||||
.buttonStyle(FormActionButtonStyle(title: L10n.actionShare))
|
.buttonStyle(FormActionButtonStyle(title: L10n.actionShare))
|
||||||
case .call:
|
case .voiceCall:
|
||||||
Button {
|
Button {
|
||||||
context.send(viewAction: .processTapCall)
|
context.send(viewAction: .processTapCall(isVoiceCall: true))
|
||||||
|
} label: {
|
||||||
|
CompoundIcon(\.voiceCall)
|
||||||
|
}
|
||||||
|
.accessibilityLabel(L10n.a11yStartVoiceCall)
|
||||||
|
.buttonStyle(FormActionButtonStyle(title: L10n.actionCall))
|
||||||
|
case .videoCall:
|
||||||
|
Button {
|
||||||
|
context.send(viewAction: .processTapCall(isVoiceCall: false))
|
||||||
} label: {
|
} label: {
|
||||||
CompoundIcon(\.videoCall)
|
CompoundIcon(\.videoCall)
|
||||||
}
|
}
|
||||||
.buttonStyle(FormActionButtonStyle(title: L10n.actionCall))
|
.accessibilityLabel(L10n.a11yStartVideoCall)
|
||||||
|
.buttonStyle(FormActionButtonStyle(title: L10n.commonVideo))
|
||||||
case .invite:
|
case .invite:
|
||||||
Button {
|
Button {
|
||||||
context.send(viewAction: .processTapInvite)
|
context.send(viewAction: .processTapInvite)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ struct RoomMemberDetailsScreenCoordinatorParameters {
|
|||||||
enum RoomMemberDetailsScreenCoordinatorAction {
|
enum RoomMemberDetailsScreenCoordinatorAction {
|
||||||
case openUserProfile
|
case openUserProfile
|
||||||
case openDirectChat(roomID: String)
|
case openDirectChat(roomID: String)
|
||||||
case startCall(roomProxy: JoinedRoomProxyProtocol)
|
case startCall(roomProxy: JoinedRoomProxyProtocol, isVoiceCall: Bool)
|
||||||
case verifyUser(userID: String)
|
case verifyUser(userID: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,8 +51,8 @@ final class RoomMemberDetailsScreenCoordinator: CoordinatorProtocol {
|
|||||||
actionsSubject.send(.openUserProfile)
|
actionsSubject.send(.openUserProfile)
|
||||||
case .openDirectChat(let roomID):
|
case .openDirectChat(let roomID):
|
||||||
actionsSubject.send(.openDirectChat(roomID: roomID))
|
actionsSubject.send(.openDirectChat(roomID: roomID))
|
||||||
case .startCall(let roomProxy):
|
case .startCall(let roomProxy, let isVoiceCall):
|
||||||
actionsSubject.send(.startCall(roomProxy: roomProxy))
|
actionsSubject.send(.startCall(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
|
||||||
case .verifyUser(let userID):
|
case .verifyUser(let userID):
|
||||||
actionsSubject.send(.verifyUser(userID: userID))
|
actionsSubject.send(.verifyUser(userID: userID))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import Foundation
|
|||||||
enum RoomMemberDetailsScreenViewModelAction {
|
enum RoomMemberDetailsScreenViewModelAction {
|
||||||
case openUserProfile
|
case openUserProfile
|
||||||
case openDirectChat(roomID: String)
|
case openDirectChat(roomID: String)
|
||||||
case startCall(roomProxy: JoinedRoomProxyProtocol)
|
case startCall(roomProxy: JoinedRoomProxyProtocol, isVoiceCall: Bool)
|
||||||
case verifyUser(userID: String)
|
case verifyUser(userID: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ enum RoomMemberDetailsScreenViewAction {
|
|||||||
case displayAvatar(URL)
|
case displayAvatar(URL)
|
||||||
case openDirectChat
|
case openDirectChat
|
||||||
case createDirectChat
|
case createDirectChat
|
||||||
case startCall(roomID: String)
|
case startCall(roomID: String, isVoiceCall: Bool)
|
||||||
case verifyUser
|
case verifyUser
|
||||||
case withdrawVerification
|
case withdrawVerification
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,8 +81,8 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
|
|||||||
openDirectChat()
|
openDirectChat()
|
||||||
case .createDirectChat:
|
case .createDirectChat:
|
||||||
Task { await createDirectChat() }
|
Task { await createDirectChat() }
|
||||||
case .startCall(let roomID):
|
case .startCall(let roomID, let isVoiceCall):
|
||||||
Task { await startCall(roomID: roomID) }
|
Task { await startCall(roomID: roomID, isVoiceCall: isVoiceCall) }
|
||||||
case .verifyUser:
|
case .verifyUser:
|
||||||
actionsSubject.send(.verifyUser(userID: state.userID))
|
actionsSubject.send(.verifyUser(userID: state.userID))
|
||||||
case .withdrawVerification:
|
case .withdrawVerification:
|
||||||
@@ -225,12 +225,12 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func startCall(roomID: String) async {
|
private func startCall(roomID: String, isVoiceCall: Bool) async {
|
||||||
guard case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(roomID) else {
|
guard case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(roomID) else {
|
||||||
showErrorIndicator()
|
showErrorIndicator()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
actionsSubject.send(.startCall(roomProxy: roomProxy))
|
actionsSubject.send(.startCall(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: User Indicators
|
// MARK: User Indicators
|
||||||
|
|||||||
@@ -103,11 +103,20 @@ struct RoomMemberDetailsScreen: View {
|
|||||||
|
|
||||||
if let roomID = context.viewState.dmRoomID {
|
if let roomID = context.viewState.dmRoomID {
|
||||||
Button {
|
Button {
|
||||||
context.send(viewAction: .startCall(roomID: roomID))
|
context.send(viewAction: .startCall(roomID: roomID, isVoiceCall: true))
|
||||||
|
} label: {
|
||||||
|
CompoundIcon(\.voiceCall)
|
||||||
|
}
|
||||||
|
.accessibilityLabel(L10n.a11yStartVoiceCall)
|
||||||
|
.buttonStyle(FormActionButtonStyle(title: L10n.actionCall))
|
||||||
|
|
||||||
|
Button {
|
||||||
|
context.send(viewAction: .startCall(roomID: roomID, isVoiceCall: false))
|
||||||
} label: {
|
} label: {
|
||||||
CompoundIcon(\.videoCall)
|
CompoundIcon(\.videoCall)
|
||||||
}
|
}
|
||||||
.buttonStyle(FormActionButtonStyle(title: L10n.actionCall))
|
.accessibilityLabel(L10n.a11yStartVideoCall)
|
||||||
|
.buttonStyle(FormActionButtonStyle(title: L10n.commonVideo))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let permalink = context.viewState.memberDetails?.permalink {
|
if let permalink = context.viewState.memberDetails?.permalink {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ enum RoomScreenCoordinatorAction {
|
|||||||
case presentEmojiPicker(itemID: TimelineItemIdentifier, selectedEmojis: Set<String>)
|
case presentEmojiPicker(itemID: TimelineItemIdentifier, selectedEmojis: Set<String>)
|
||||||
case presentRoomMemberDetails(userID: String)
|
case presentRoomMemberDetails(userID: String)
|
||||||
case presentMessageForwarding(forwardingItem: MessageForwardingItem)
|
case presentMessageForwarding(forwardingItem: MessageForwardingItem)
|
||||||
case presentCallScreen
|
case presentCallScreen(isVoiceCall: Bool)
|
||||||
case presentPinnedEventsTimeline
|
case presentPinnedEventsTimeline
|
||||||
case presentResolveSendFailure(failure: TimelineItemSendFailure.VerifiedUser, sendHandle: SendHandleProxy)
|
case presentResolveSendFailure(failure: TimelineItemSendFailure.VerifiedUser, sendHandle: SendHandleProxy)
|
||||||
case presentKnockRequestsList
|
case presentKnockRequestsList
|
||||||
@@ -178,8 +178,8 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
|||||||
actionsSubject.send(.presentPinnedEventsTimeline)
|
actionsSubject.send(.presentPinnedEventsTimeline)
|
||||||
case .displayRoomDetails:
|
case .displayRoomDetails:
|
||||||
actionsSubject.send(.presentRoomDetails)
|
actionsSubject.send(.presentRoomDetails)
|
||||||
case .displayCall:
|
case .displayCall(let isVoiceCall):
|
||||||
actionsSubject.send(.presentCallScreen)
|
actionsSubject.send(.presentCallScreen(isVoiceCall: isVoiceCall))
|
||||||
case .removeComposerFocus:
|
case .removeComposerFocus:
|
||||||
composerViewModel.process(timelineAction: .removeFocus)
|
composerViewModel.process(timelineAction: .removeFocus)
|
||||||
case .displayKnockRequests:
|
case .displayKnockRequests:
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ enum RoomScreenViewModelAction: Equatable {
|
|||||||
case displayThread(threadRootEventID: String, focussedEventID: String)
|
case displayThread(threadRootEventID: String, focussedEventID: String)
|
||||||
case displayPinnedEventsTimeline
|
case displayPinnedEventsTimeline
|
||||||
case displayRoomDetails
|
case displayRoomDetails
|
||||||
case displayCall
|
case displayCall(isVoiceCall: Bool)
|
||||||
case removeComposerFocus
|
case removeComposerFocus
|
||||||
case displayKnockRequests
|
case displayKnockRequests
|
||||||
case displayRoom(roomID: String, via: [String])
|
case displayRoom(roomID: String, via: [String])
|
||||||
@@ -27,7 +27,7 @@ enum RoomScreenViewAction {
|
|||||||
case tappedPinnedEventsBanner
|
case tappedPinnedEventsBanner
|
||||||
case viewAllPins
|
case viewAllPins
|
||||||
case displayRoomDetails
|
case displayRoomDetails
|
||||||
case displayCall
|
case displayCall(isVoiceCall: Bool)
|
||||||
case footerViewAction(RoomScreenFooterViewAction)
|
case footerViewAction(RoomScreenFooterViewAction)
|
||||||
case acceptKnock(eventID: String)
|
case acceptKnock(eventID: String)
|
||||||
case dismissKnockRequests
|
case dismissKnockRequests
|
||||||
@@ -62,6 +62,9 @@ struct RoomScreenViewState: BindableState {
|
|||||||
isCallingEnabled && !isParticipatingInOngoingCall // Hide the join call button when already in the call
|
isCallingEnabled && !isParticipatingInOngoingCall // Hide the join call button when already in the call
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the current room is a DM
|
||||||
|
var isDirectOneToOneRoom: Bool
|
||||||
|
|
||||||
var roomThreadListEnabled = false
|
var roomThreadListEnabled = false
|
||||||
var isKnockingEnabled = false
|
var isKnockingEnabled = false
|
||||||
var isKnockableRoom = false
|
var isKnockableRoom = false
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
let viewState = RoomScreenViewState(roomTitle: roomProxy.infoPublisher.value.displayName ?? roomProxy.id,
|
let viewState = RoomScreenViewState(roomTitle: roomProxy.infoPublisher.value.displayName ?? roomProxy.id,
|
||||||
roomAvatar: roomProxy.infoPublisher.value.avatar,
|
roomAvatar: roomProxy.infoPublisher.value.avatar,
|
||||||
hasOngoingCall: roomProxy.infoPublisher.value.hasRoomCall,
|
hasOngoingCall: roomProxy.infoPublisher.value.hasRoomCall,
|
||||||
|
isDirectOneToOneRoom: roomProxy.isDirectOneToOneRoom,
|
||||||
hasSuccessor: roomProxy.infoPublisher.value.successor != nil,
|
hasSuccessor: roomProxy.infoPublisher.value.successor != nil,
|
||||||
roomHistorySharingState: roomHistorySharingState)
|
roomHistorySharingState: roomHistorySharingState)
|
||||||
super.init(initialViewState: appHooks.roomScreenHook.update(viewState),
|
super.init(initialViewState: appHooks.roomScreenHook.update(viewState),
|
||||||
@@ -98,8 +99,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
actionsSubject.send(.displayPinnedEventsTimeline)
|
actionsSubject.send(.displayPinnedEventsTimeline)
|
||||||
case .displayRoomDetails:
|
case .displayRoomDetails:
|
||||||
actionsSubject.send(.displayRoomDetails)
|
actionsSubject.send(.displayRoomDetails)
|
||||||
case .displayCall:
|
case .displayCall(let isVoiceCall):
|
||||||
actionsSubject.send(.displayCall)
|
actionsSubject.send(.displayCall(isVoiceCall: isVoiceCall))
|
||||||
actionsSubject.send(.removeComposerFocus)
|
actionsSubject.send(.removeComposerFocus)
|
||||||
analyticsService.trackInteraction(name: .MobileRoomCallButton)
|
analyticsService.trackInteraction(name: .MobileRoomCallButton)
|
||||||
case .footerViewAction(let action):
|
case .footerViewAction(let action):
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2026 Element Creations Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
// Please see LICENSE files in the repository root for full details.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Compound
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct RoomCallControlsToolbar: ToolbarContent {
|
||||||
|
let viewState: RoomScreenViewState
|
||||||
|
let onCallTap: (_ isVoiceCall: Bool) -> Void
|
||||||
|
|
||||||
|
var body: some ToolbarContent {
|
||||||
|
if viewState.hasOngoingCall {
|
||||||
|
ToolbarItem(placement: .primaryAction) {
|
||||||
|
JoinCallButton {
|
||||||
|
onCallTap(false)
|
||||||
|
}
|
||||||
|
.accessibilityIdentifier(A11yIdentifiers.roomScreen.joinCall)
|
||||||
|
.disabled(!viewState.canJoinCall)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if viewState.isDirectOneToOneRoom {
|
||||||
|
ToolbarItem(placement: .primaryAction) {
|
||||||
|
Button { onCallTap(true) } label: {
|
||||||
|
CompoundIcon(\.voiceCallSolid)
|
||||||
|
}
|
||||||
|
.accessibilityLabel(L10n.a11yStartVoiceCall)
|
||||||
|
.accessibilityIdentifier(A11yIdentifiers.roomScreen.startVoiceCall)
|
||||||
|
.disabled(!viewState.canJoinCall)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ToolbarItem(placement: .primaryAction) {
|
||||||
|
Button { onCallTap(false) } label: {
|
||||||
|
CompoundIcon(\.videoCallSolid)
|
||||||
|
}
|
||||||
|
.accessibilityLabel(L10n.a11yStartVideoCall)
|
||||||
|
.accessibilityIdentifier(A11yIdentifiers.roomScreen.startVideoCall)
|
||||||
|
.disabled(!viewState.canJoinCall)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Previews
|
||||||
|
|
||||||
|
struct RoomCallControlsToolbar_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
ElementNavigationStack {
|
||||||
|
Color.clear.toolbar { RoomCallControlsToolbar(viewState: .mock(hasOngoingCall: true)) { _ in } }
|
||||||
|
}
|
||||||
|
ElementNavigationStack {
|
||||||
|
Color.clear.toolbar { RoomCallControlsToolbar(viewState: .mock(hasOngoingCall: false, isDirectOneToOneRoom: true)) { _ in } }
|
||||||
|
}
|
||||||
|
ElementNavigationStack {
|
||||||
|
Color.clear.toolbar { RoomCallControlsToolbar(viewState: .mock(hasOngoingCall: false)) { _ in } }
|
||||||
|
}
|
||||||
|
ElementNavigationStack {
|
||||||
|
Color.clear.toolbar { RoomCallControlsToolbar(viewState: .mock(hasOngoingCall: false, canJoinCall: false)) { _ in } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.previewDisplayName("All states")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension RoomScreenViewState {
|
||||||
|
static func mock(hasOngoingCall: Bool, isDirectOneToOneRoom: Bool = false, canJoinCall: Bool = true) -> RoomScreenViewState {
|
||||||
|
RoomScreenViewState(roomAvatar: .room(id: "mock", name: "Mock Room", avatarURL: nil),
|
||||||
|
canJoinCall: canJoinCall,
|
||||||
|
hasOngoingCall: hasOngoingCall, isDirectOneToOneRoom: isDirectOneToOneRoom,
|
||||||
|
hasSuccessor: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
// Please see LICENSE files in the repository root for full details.
|
// Please see LICENSE files in the repository root for full details.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
import Compound
|
import Compound
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import WysiwygComposer
|
import WysiwygComposer
|
||||||
@@ -172,10 +173,9 @@ struct RoomScreen: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !ProcessInfo.processInfo.isiOSAppOnMac {
|
if !ProcessInfo.processInfo.isiOSAppOnMac {
|
||||||
ToolbarItem(placement: .primaryAction) {
|
if context.viewState.shouldShowCallButton {
|
||||||
if context.viewState.shouldShowCallButton {
|
RoomCallControlsToolbar(viewState: context.viewState) { isVoiceCall in
|
||||||
callButton
|
context.send(viewAction: .displayCall(isVoiceCall: isVoiceCall))
|
||||||
.disabled(!context.viewState.canJoinCall)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,24 +194,6 @@ struct RoomScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private var callButton: some View {
|
|
||||||
if context.viewState.hasOngoingCall {
|
|
||||||
JoinCallButton {
|
|
||||||
context.send(viewAction: .displayCall)
|
|
||||||
}
|
|
||||||
.accessibilityIdentifier(A11yIdentifiers.roomScreen.joinCall)
|
|
||||||
} else {
|
|
||||||
Button {
|
|
||||||
context.send(viewAction: .displayCall)
|
|
||||||
} label: {
|
|
||||||
CompoundIcon(\.videoCallSolid)
|
|
||||||
}
|
|
||||||
.accessibilityLabel(L10n.a11yStartCall)
|
|
||||||
.accessibilityIdentifier(A11yIdentifiers.roomScreen.joinCall)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ struct UserProfileScreenCoordinatorParameters {
|
|||||||
|
|
||||||
enum UserProfileScreenCoordinatorAction {
|
enum UserProfileScreenCoordinatorAction {
|
||||||
case openDirectChat(roomID: String)
|
case openDirectChat(roomID: String)
|
||||||
case startCall(roomProxy: JoinedRoomProxyProtocol)
|
case startCall(roomProxy: JoinedRoomProxyProtocol, isVoiceCall: Bool)
|
||||||
case dismiss
|
case dismiss
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,8 +48,8 @@ final class UserProfileScreenCoordinator: CoordinatorProtocol {
|
|||||||
switch action {
|
switch action {
|
||||||
case .openDirectChat(let roomID):
|
case .openDirectChat(let roomID):
|
||||||
actionsSubject.send(.openDirectChat(roomID: roomID))
|
actionsSubject.send(.openDirectChat(roomID: roomID))
|
||||||
case .startCall(let roomProxy):
|
case .startCall(let roomProxy, let isVoiceCall):
|
||||||
actionsSubject.send(.startCall(roomProxy: roomProxy))
|
actionsSubject.send(.startCall(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
actionsSubject.send(.dismiss)
|
actionsSubject.send(.dismiss)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import Foundation
|
|||||||
|
|
||||||
enum UserProfileScreenViewModelAction {
|
enum UserProfileScreenViewModelAction {
|
||||||
case openDirectChat(roomID: String)
|
case openDirectChat(roomID: String)
|
||||||
case startCall(roomProxy: JoinedRoomProxyProtocol)
|
case startCall(roomProxy: JoinedRoomProxyProtocol, isVoiceCall: Bool)
|
||||||
case dismiss
|
case dismiss
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ enum UserProfileScreenViewAction {
|
|||||||
case displayAvatar(URL)
|
case displayAvatar(URL)
|
||||||
case openDirectChat
|
case openDirectChat
|
||||||
case createDirectChat
|
case createDirectChat
|
||||||
case startCall(roomID: String)
|
case startCall(roomID: String, isVoiceCall: Bool)
|
||||||
case dismiss
|
case dismiss
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,8 +62,8 @@ class UserProfileScreenViewModel: UserProfileScreenViewModelType, UserProfileScr
|
|||||||
openDirectChat()
|
openDirectChat()
|
||||||
case .createDirectChat:
|
case .createDirectChat:
|
||||||
Task { await createDirectChat() }
|
Task { await createDirectChat() }
|
||||||
case .startCall(let roomID):
|
case .startCall(let roomID, let isVoiceCall):
|
||||||
Task { await startCall(roomID: roomID) }
|
Task { await startCall(roomID: roomID, isVoiceCall: isVoiceCall) }
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
actionsSubject.send(.dismiss)
|
actionsSubject.send(.dismiss)
|
||||||
}
|
}
|
||||||
@@ -144,12 +144,12 @@ class UserProfileScreenViewModel: UserProfileScreenViewModelType, UserProfileScr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func startCall(roomID: String) async {
|
private func startCall(roomID: String, isVoiceCall: Bool) async {
|
||||||
guard case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(roomID) else {
|
guard case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(roomID) else {
|
||||||
showErrorIndicator()
|
showErrorIndicator()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
actionsSubject.send(.startCall(roomProxy: roomProxy))
|
actionsSubject.send(.startCall(roomProxy: roomProxy, isVoiceCall: isVoiceCall))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: User Indicators
|
// MARK: User Indicators
|
||||||
|
|||||||
@@ -66,11 +66,20 @@ struct UserProfileScreen: View {
|
|||||||
|
|
||||||
if let roomID = context.viewState.dmRoomID {
|
if let roomID = context.viewState.dmRoomID {
|
||||||
Button {
|
Button {
|
||||||
context.send(viewAction: .startCall(roomID: roomID))
|
context.send(viewAction: .startCall(roomID: roomID, isVoiceCall: true))
|
||||||
|
} label: {
|
||||||
|
CompoundIcon(\.voiceCall)
|
||||||
|
}
|
||||||
|
.accessibilityLabel(L10n.a11yStartVoiceCall)
|
||||||
|
.buttonStyle(FormActionButtonStyle(title: L10n.actionCall))
|
||||||
|
|
||||||
|
Button {
|
||||||
|
context.send(viewAction: .startCall(roomID: roomID, isVoiceCall: false))
|
||||||
} label: {
|
} label: {
|
||||||
CompoundIcon(\.videoCall)
|
CompoundIcon(\.videoCall)
|
||||||
}
|
}
|
||||||
.buttonStyle(FormActionButtonStyle(title: L10n.actionCall))
|
.accessibilityLabel(L10n.a11yStartVideoCall)
|
||||||
|
.buttonStyle(FormActionButtonStyle(title: L10n.commonVideo))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let permalink = context.viewState.permalink {
|
if let permalink = context.viewState.permalink {
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user