diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 4cf84fb8c..74a1369f5 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -744,6 +744,7 @@ 7C1A7B594B2F8143F0DD0005 /* ElementXAttributeScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = C024C151639C4E1B91FCC68B /* ElementXAttributeScope.swift */; }; 7C545FFEC9930F7247352593 /* SecurityAndPrivacyScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 978092B01BEAB39F2C4389AE /* SecurityAndPrivacyScreenViewModel.swift */; }; 7C6376192F578E0BA801BFEC /* AnalyticsSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42C64A14EE89928207E3B42B /* AnalyticsSettingsScreenModels.swift */; }; + 7C92C6B48879C733859B7A0D /* CallIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587698C8FC0076A2DC822EEA /* CallIntent.swift */; }; 7C9BDF1FC7BD46C4676536AB /* AuthenticationStartScreenBackgroundImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 682BC7BAF0EFEF512A8C5140 /* AuthenticationStartScreenBackgroundImage.swift */; }; 7CD16990BA843BE9ED639129 /* ImageRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DFE4453AB0B34C203447162 /* ImageRoomTimelineItem.swift */; }; 7D249465ED00988EEEC14E05 /* JoinedRoomProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 867DC9530C42F7B5176BE465 /* JoinedRoomProxyMock.swift */; }; @@ -1108,6 +1109,7 @@ BA43D782BE85C7F5F20C624A /* AttributedStringBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */; }; BA48D6AFF6421D199148C0A1 /* KnockRequestsListScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9AC2CC94FA06F728883B694 /* KnockRequestsListScreenViewModelTests.swift */; }; BA4C9049BC96DED3A2F3B82E /* RoomNotificationSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03DD998E523D4EC93C7ED703 /* RoomNotificationSettingsScreenViewModelProtocol.swift */; }; + BA6B8A60185FE238FBCE705E /* CallIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587698C8FC0076A2DC822EEA /* CallIntent.swift */; }; BB6BF528BC7F5B87E08C4F18 /* CameraPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8A3B7637DDBD6AA97AC2545 /* CameraPicker.swift */; }; BB784A02BADB03C820617A46 /* TextRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90A55430639712CFACA34F43 /* TextRoomTimelineItem.swift */; }; BB9B800C6094E34860E89DC5 /* AppLockSetupBiometricsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CCF9A924521DECA44778C4 /* AppLockSetupBiometricsScreen.swift */; }; @@ -1172,6 +1174,7 @@ C646CFE86CA46495D6BB7448 /* ManageAuthorizedSpacesScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74AE4F02A507882743973214 /* ManageAuthorizedSpacesScreenViewModelProtocol.swift */; }; C67156445600FAE6430DE41E /* LocationSharingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F56E6E41C6DFE8054787D57 /* LocationSharingScreen.swift */; }; C67FCC854F3A6FC7A2EC04D0 /* MediaUploadPreviewScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70C86696AC9521F8ED88FBEB /* MediaUploadPreviewScreen.swift */; }; + C6A4A679116E2FC0DFE665EF /* CallIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587698C8FC0076A2DC822EEA /* CallIntent.swift */; }; C6AC34B731F4F853E9D15146 /* AnalyticsConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA2397174D0DC3918A7A8A7B /* AnalyticsConfiguration.swift */; }; C6C06DDA8881260303FBA3A0 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2141693488CE5446BB391964 /* Date.swift */; }; C76892321558E75101E68ED6 /* ReadableFrameModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398817652FA8ABAE0A31AC6D /* ReadableFrameModifier.swift */; }; @@ -2111,6 +2114,7 @@ 580BDCD23DD02481AB5FFB47 /* LeaveSpaceHandleSDKMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaveSpaceHandleSDKMock.swift; sourceTree = ""; }; 584A61D9C459FAFEF038A7C0 /* Section.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = ""; }; 5875F7C0A2398E9F134B1284 /* EncryptionResetScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetScreenViewModel.swift; sourceTree = ""; }; + 587698C8FC0076A2DC822EEA /* CallIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallIntent.swift; sourceTree = ""; }; 58C2527813FDAE23E72A9063 /* AnalyticsSettingsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenViewModelTests.swift; sourceTree = ""; }; 58D295F0081084F38DB20893 /* RoomNotificationSettingsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenViewModelTests.swift; sourceTree = ""; }; 592A35163B0749C66BFD6186 /* MapLibreStaticMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLibreStaticMapView.swift; sourceTree = ""; }; @@ -3243,6 +3247,7 @@ 06501F0E978B2D5C92771DC7 /* Logging */ = { isa = PBXGroup; children = ( + 587698C8FC0076A2DC822EEA /* CallIntent.swift */, 41A8571A8A071FB41778C016 /* ExtensionLogger.swift */, 2711E5996016ABD6EAAEB58A /* LogLevel.swift */, 111B698739E3410E2CDB7144 /* MXLog.swift */, @@ -7749,6 +7754,7 @@ F255083E18CDBFDF7E640FB1 /* Avatars.swift in Sources */, A8E324E700E596E36B0A311B /* BootDetectionManager.swift in Sources */, 9A3B0CDF097E3838FB1B9595 /* Bundle.swift in Sources */, + 7C92C6B48879C733859B7A0D /* CallIntent.swift in Sources */, 9295F1F5E04484E10780BCE8 /* CharacterSet.swift in Sources */, 238D561CA231339C6D4D06F3 /* ClientBuilder.swift in Sources */, 0BAF83521871E69D222EE8E4 /* ClientBuilderHook.swift in Sources */, @@ -7986,6 +7992,7 @@ 2CC3F27CD76DB00A747BEA6C /* AppSettings.swift in Sources */, 56C18ACF91DD20463B242460 /* AudioPlaybackSpeed.swift in Sources */, 2F2906AE9BC3D0E79A6F98F8 /* Bundle.swift in Sources */, + BA6B8A60185FE238FBCE705E /* CallIntent.swift in Sources */, 88A87AA16CD93F57143623F8 /* ClientBuilderHook.swift in Sources */, D104B27C5DA0626B41CE78D3 /* CurrentValuePublisher.swift in Sources */, 66ED2E6F19B0C669EC71D4CA /* Dictionary.swift in Sources */, @@ -8177,6 +8184,7 @@ 1D2D713A5A269072D103AE37 /* CLLocationManagerProtocol.swift in Sources */, 5470E62F65AE1803BBF3D528 /* CXProviderMock.swift in Sources */, 3D0DAED550E967AB49F1758C /* CXProviderProtocol.swift in Sources */, + C6A4A679116E2FC0DFE665EF /* CallIntent.swift in Sources */, 01B63F1A04A276B39AC17014 /* CallInviteRoomTimelineItem.swift in Sources */, 3D72F5F9109AAA257542456B /* CallInviteRoomTimelineView.swift in Sources */, E5AB28123E2488F97E953AC0 /* CallNotificationRoomTimelineItem.swift in Sources */, diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index 8596f6677..b2a23eb8f 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -719,8 +719,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { stateMachine.tryEvent(.startMembersFlow(entryPoint: .roomMember(userID: userID))) case .presentMessageForwarding(let forwardingItem): stateMachine.tryEvent(.presentMessageForwarding(forwardingItem: forwardingItem)) - case .presentCallScreen: - actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: false)) + case .presentCallScreen(let isVoiceCall): + actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall)) case .presentPinnedEventsTimeline: stateMachine.tryEvent(.presentPinnedEventsTimeline) case .presentResolveSendFailure(failure: let failure, sendHandle: let sendHandle): diff --git a/ElementX/Sources/FlowCoordinators/SpaceFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/SpaceFlowCoordinator.swift index 3de52f74c..3610a4088 100644 --- a/ElementX/Sources/FlowCoordinators/SpaceFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/SpaceFlowCoordinator.swift @@ -529,8 +529,8 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol { guard let self else { return } switch action { - case .presentCallScreen(let roomProxy, _): - actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: false)) + 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): @@ -557,8 +557,8 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol { switch actions { case .finished: stateMachine.tryEvent(.stopMembersFlow) - case .presentCallScreen(let roomProxy, _): - actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: false)) + case .presentCallScreen(let roomProxy, let isVoiceCall): + actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall)) case .verifyUser(let userID): actionsSubject.send(.verifyUser(userID: userID)) } @@ -581,8 +581,8 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol { if leftRoom { stateMachine.tryEvent(.leftSpace) } - case .presentCallScreen(let roomProxy): - actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: false)) + case .presentCallScreen(let roomProxy, let isVoiceCall): + actionsSubject.send(.presentCallScreen(roomProxy: roomProxy, isVoiceCall: isVoiceCall)) case .verifyUser(userID: let userID): actionsSubject.send(.verifyUser(userID: userID)) } diff --git a/ElementX/Sources/FlowCoordinators/SpaceSettingsFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/SpaceSettingsFlowCoordinator.swift index 03697d709..d872fcff4 100644 --- a/ElementX/Sources/FlowCoordinators/SpaceSettingsFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/SpaceSettingsFlowCoordinator.swift @@ -11,7 +11,7 @@ import SwiftState enum SpaceSettingsFlowCoordinatorAction { case finished(leftRoom: Bool) - case presentCallScreen(roomProxy: JoinedRoomProxyProtocol) + case presentCallScreen(roomProxy: JoinedRoomProxyProtocol, isVoiceCall: Bool) case verifyUser(userID: String) } @@ -395,8 +395,8 @@ final class SpaceSettingsFlowCoordinator: FlowCoordinatorProtocol { switch action { case .finished: stateMachine.tryEvent(.stopMembersListFlow) - 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)) } diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 468cbd39e..d1dce52bb 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -15238,6 +15238,7 @@ class RoomInfoProxyMock: RoomInfoProxyProtocol, @unchecked Sendable { set(value) { underlyingHasRoomCall = value } } var underlyingHasRoomCall: Bool! + var activeRoomCallIntent: CallIntent? var activeRoomCallParticipants: [String] = [] var isMarkedUnread: Bool { get { return underlyingIsMarkedUnread } diff --git a/ElementX/Sources/Mocks/RoomSummaryProviderMock.swift b/ElementX/Sources/Mocks/RoomSummaryProviderMock.swift index f599f2658..969ed2de9 100644 --- a/ElementX/Sources/Mocks/RoomSummaryProviderMock.swift +++ b/ElementX/Sources/Mocks/RoomSummaryProviderMock.swift @@ -92,6 +92,7 @@ extension RoomSummary { canonicalAlias: canonicalAlias, alternativeAliases: [], hasOngoingCall: false, + activeCallIntent: nil, isMarkedUnread: false, isFavourite: false, isTombstoned: false) @@ -119,6 +120,7 @@ extension Array where Element == RoomSummary { canonicalAlias: nil, alternativeAliases: [], hasOngoingCall: false, + activeCallIntent: nil, isMarkedUnread: false, isFavourite: false, isTombstoned: false), @@ -141,6 +143,7 @@ extension Array where Element == RoomSummary { canonicalAlias: "#foundation-and-empire:matrix.org", alternativeAliases: [], hasOngoingCall: false, + activeCallIntent: nil, isMarkedUnread: false, isFavourite: false, isTombstoned: false), @@ -163,6 +166,7 @@ extension Array where Element == RoomSummary { canonicalAlias: nil, alternativeAliases: [], hasOngoingCall: false, + activeCallIntent: nil, isMarkedUnread: false, isFavourite: false, isTombstoned: false), @@ -185,6 +189,7 @@ extension Array where Element == RoomSummary { canonicalAlias: nil, alternativeAliases: [], hasOngoingCall: false, + activeCallIntent: nil, isMarkedUnread: false, isFavourite: false, isTombstoned: false), @@ -207,6 +212,7 @@ extension Array where Element == RoomSummary { canonicalAlias: nil, alternativeAliases: [], hasOngoingCall: true, + activeCallIntent: nil, isMarkedUnread: false, isFavourite: false, isTombstoned: false), @@ -229,6 +235,7 @@ extension Array where Element == RoomSummary { canonicalAlias: "#prelude-foundation:matrix.org", alternativeAliases: [], hasOngoingCall: true, + activeCallIntent: .audio, isMarkedUnread: false, isFavourite: false, isTombstoned: false), @@ -251,6 +258,7 @@ extension Array where Element == RoomSummary { canonicalAlias: nil, alternativeAliases: [], hasOngoingCall: false, + activeCallIntent: nil, isMarkedUnread: false, isFavourite: false, isTombstoned: true), @@ -273,6 +281,7 @@ extension Array where Element == RoomSummary { canonicalAlias: nil, alternativeAliases: [], hasOngoingCall: false, + activeCallIntent: nil, isMarkedUnread: false, isFavourite: false, isTombstoned: false) @@ -328,6 +337,7 @@ extension Array where Element == RoomSummary { canonicalAlias: "#footest:somewhere.org", alternativeAliases: [], hasOngoingCall: false, + activeCallIntent: nil, isMarkedUnread: false, isFavourite: false, isTombstoned: false), @@ -350,6 +360,7 @@ extension Array where Element == RoomSummary { canonicalAlias: nil, alternativeAliases: [], hasOngoingCall: false, + activeCallIntent: nil, isMarkedUnread: false, isFavourite: false, isTombstoned: false) @@ -375,6 +386,7 @@ extension Array where Element == RoomSummary { canonicalAlias: "#footest:somewhere.org", alternativeAliases: [], hasOngoingCall: false, + activeCallIntent: nil, isMarkedUnread: false, isFavourite: false, isTombstoned: false), @@ -397,6 +409,7 @@ extension Array where Element == RoomSummary { canonicalAlias: nil, alternativeAliases: [], hasOngoingCall: false, + activeCallIntent: nil, isMarkedUnread: false, isFavourite: false, isTombstoned: false) diff --git a/ElementX/Sources/Other/Logging/CallIntent.swift b/ElementX/Sources/Other/Logging/CallIntent.swift new file mode 100644 index 000000000..154c70394 --- /dev/null +++ b/ElementX/Sources/Other/Logging/CallIntent.swift @@ -0,0 +1,30 @@ +// +// 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 Foundation +import MatrixRustSDK + +enum CallIntent: Codable, CaseIterable { + case video, audio +} + +extension CallIntent { + // periphery:ignore - Unused, but added to detect new cases when updating the SDK. + init(rustCallIntent: MatrixRustSDK.RtcCallIntent) { + switch rustCallIntent { + case .audio: self = .audio + case .video: self = .video + } + } + + var rustCallIntent: MatrixRustSDK.RtcCallIntent { + switch self { + case .audio: .audio + case .video: .video + } + } +} diff --git a/ElementX/Sources/Other/SwiftUI/Views/JoinCallButton.swift b/ElementX/Sources/Other/SwiftUI/Views/JoinCallButton.swift index 819838852..ab63c6fb3 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/JoinCallButton.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/JoinCallButton.swift @@ -10,8 +10,9 @@ import Compound import SwiftUI struct JoinCallButton: View { + let isVoiceCall: Bool let action: () -> Void - + var body: some View { if #available(iOS 26, *) { glassButton @@ -25,7 +26,7 @@ struct JoinCallButton: View { // Use an HStack on iOS 26 as .labelStyle(.titleAndIcon) doesn't // seem to have any effect on a label in the navigation bar 🤷‍♂️ HStack(spacing: 6) { - CompoundIcon(\.videoCallSolid) + CompoundIcon(isVoiceCall ? \.voiceCallSolid : \.videoCallSolid) Text(L10n.actionJoin) .padding(.trailing, 4) } @@ -66,9 +67,20 @@ struct JoinCallButton_Previews: PreviewProvider { Color.clear .toolbar { ToolbarItem(placement: .confirmationAction) { - JoinCallButton { } + JoinCallButton(isVoiceCall: false) { } } } } + .previewDisplayName("Join Video Call") + + ElementNavigationStack { + Color.clear + .toolbar { + ToolbarItem(placement: .confirmationAction) { + JoinCallButton(isVoiceCall: true) { } + } + } + } + .previewDisplayName("Join Audio Call") } } diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift index 16d9d6f36..a8fa0754b 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift @@ -160,6 +160,10 @@ struct HomeScreenViewStateBindings { var spaceFiltersViewModel: ChatsSpaceFiltersScreenViewModel? } +enum CallBadgeType { + case voice, video, none +} + struct HomeScreenRoom: Identifiable, Equatable { enum RoomType: Equatable { case placeholder @@ -190,7 +194,7 @@ struct HomeScreenRoom: Identifiable, Equatable { let isDotShown: Bool let isMentionShown: Bool let isMuteShown: Bool - let isCallShown: Bool + let callBadgeType: CallBadgeType } let name: String @@ -228,7 +232,7 @@ struct HomeScreenRoom: Identifiable, Equatable { HomeScreenRoom(id: UUID().uuidString, roomID: nil, type: .placeholder, - badges: .init(isDotShown: false, isMentionShown: false, isMuteShown: false, isCallShown: false), + badges: .init(isDotShown: false, isMentionShown: false, isMuteShown: false, callBadgeType: .none), name: "Placeholder room name", isDirect: false, isHighlighted: false, @@ -252,9 +256,12 @@ extension HomeScreenRoom { let isDotShown = hasUnreadMessages || summary.hasUnreadMentions || summary.hasUnreadNotifications || summary.isMarkedUnread || isUnseenInvite let isMentionShown = summary.hasUnreadMentions && !summary.isMuted let isMuteShown = summary.isMuted - let isCallShown = summary.hasOngoingCall let isHighlighted = summary.isMarkedUnread || (!summary.isMuted && (summary.hasUnreadNotifications || summary.hasUnreadMentions)) || isUnseenInvite + let callBadge = if summary.hasOngoingCall { + summary.activeCallIntent == .audio ? CallBadgeType.voice : CallBadgeType.video + } else { CallBadgeType.none } + let type: HomeScreenRoom.RoomType = switch summary.joinRequestType { case .invite(let inviter): .invite(inviterDetails: inviter.map(RoomInviterDetails.init)) case .knock: .knock @@ -267,7 +274,7 @@ extension HomeScreenRoom { badges: .init(isDotShown: isDotShown, isMentionShown: isMentionShown, isMuteShown: isMuteShown, - isCallShown: isCallShown), + callBadgeType: callBadge), name: summary.name, isDirect: summary.isDirect, isHighlighted: isHighlighted, diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInviteCell.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInviteCell.swift index 05d6a47a2..83b1ba45a 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInviteCell.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInviteCell.swift @@ -235,6 +235,7 @@ private extension HomeScreenRoom { canonicalAlias: "#footest:somewhere.org", alternativeAliases: [], hasOngoingCall: false, + activeCallIntent: nil, isMarkedUnread: false, isFavourite: false, isTombstoned: false) @@ -270,6 +271,7 @@ private extension HomeScreenRoom { canonicalAlias: alias, alternativeAliases: [], hasOngoingCall: false, + activeCallIntent: nil, isMarkedUnread: false, isFavourite: false, isTombstoned: false) diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenKnockedCell.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenKnockedCell.swift index 71c04c50f..1814151e4 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenKnockedCell.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenKnockedCell.swift @@ -167,6 +167,7 @@ private extension HomeScreenRoom { canonicalAlias: "#footest:somewhere.org", alternativeAliases: [], hasOngoingCall: false, + activeCallIntent: nil, isMarkedUnread: false, isFavourite: false, isTombstoned: false) @@ -199,6 +200,7 @@ private extension HomeScreenRoom { canonicalAlias: alias, alternativeAliases: [], hasOngoingCall: false, + activeCallIntent: nil, isMarkedUnread: false, isFavourite: false, isTombstoned: false) diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift index 9292190cd..ce7f88264 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift @@ -119,11 +119,16 @@ struct HomeScreenRoomCell: View { Spacer() HStack(spacing: 8) { - if room.badges.isCallShown { - CompoundIcon(\.videoCallSolid, size: .xSmall, relativeTo: .compound.bodySM) + if room.badges.callBadgeType == .voice { + CompoundIcon(\.voiceCallSolid, size: .xSmall, relativeTo: .compound.bodySM) .accessibilityLabel(L10n.a11yNotificationsOngoingCall) } + if room.badges.callBadgeType == .video { + CompoundIcon(\.videoCallSolid, size: .xSmall, relativeTo: .compound.bodySM) + .accessibilityLabel(L10n.a11yNotificationsOngoingCall) + } + if room.badges.isMuteShown { CompoundIcon(\.notificationsOffSolid, size: .custom(15), relativeTo: .compound.bodyMD) .accessibilityLabel(L10n.a11yNotificationsMuted) @@ -253,6 +258,7 @@ struct HomeScreenRoomCell_Previews: PreviewProvider, TestablePreview { canonicalAlias: "#foundation-and-empire:matrix.org", alternativeAliases: [], hasOngoingCall: false, + activeCallIntent: nil, isMarkedUnread: false, isFavourite: false, isTombstoned: false) diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift index 318dfb311..10321cb0b 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift @@ -7,7 +7,6 @@ // import Foundation -import MatrixRustSDK import OrderedCollections enum RoomScreenViewModelAction: Equatable { @@ -60,6 +59,8 @@ struct RoomScreenViewState: BindableState { var canJoinCall = false /// Whether or not this room currently has a call in progress. var hasOngoingCall: Bool + /// The ongoing call nature (audio or video), null if not advertised by the participants + var activeRoomCallIntent: CallIntent? /// Whether or not the user is already part of a call in another room. var isParticipatingInOngoingCall = false var shouldShowCallButton: Bool { diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index eefa64aed..907084335 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -345,6 +345,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol state.roomTitle = roomInfo.displayName ?? roomProxy.id state.roomAvatar = roomInfo.avatar state.hasOngoingCall = roomInfo.hasRoomCall + state.activeRoomCallIntent = roomInfo.activeRoomCallIntent state.hasSuccessor = roomInfo.successor != nil let pinnedEventIDs = roomInfo.pinnedEventIDs diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomCallControlsToolbar.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomCallControlsToolbar.swift index e4c072132..6d6c5225a 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomCallControlsToolbar.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomCallControlsToolbar.swift @@ -15,8 +15,8 @@ struct RoomCallControlsToolbar: ToolbarContent { var body: some ToolbarContent { if viewState.hasOngoingCall { ToolbarItem(placement: .primaryAction) { - JoinCallButton { - onCallTap(false) + JoinCallButton(isVoiceCall: viewState.activeRoomCallIntent == .audio) { + onCallTap(viewState.activeRoomCallIntent == .audio) } .accessibilityIdentifier(A11yIdentifiers.roomScreen.joinCall) .disabled(!viewState.canJoinCall) @@ -61,16 +61,21 @@ struct RoomCallControlsToolbar_Previews: PreviewProvider { ElementNavigationStack { Color.clear.toolbar { RoomCallControlsToolbar(viewState: .mock(hasOngoingCall: false, canJoinCall: false)) { _ in } } } + ElementNavigationStack { + Color.clear.toolbar { RoomCallControlsToolbar(viewState: .mock(hasOngoingCall: true, activeRoomCallIntent: .audio)) { _ in } } + } } .previewDisplayName("All states") } } private extension RoomScreenViewState { - static func mock(hasOngoingCall: Bool, isDirectOneToOneRoom: Bool = false, canJoinCall: Bool = true) -> RoomScreenViewState { + static func mock(hasOngoingCall: Bool, isDirectOneToOneRoom: Bool = false, canJoinCall: Bool = true, activeRoomCallIntent: CallIntent? = nil) -> RoomScreenViewState { RoomScreenViewState(roomAvatar: .room(id: "mock", name: "Mock Room", avatarURL: nil), canJoinCall: canJoinCall, - hasOngoingCall: hasOngoingCall, isDirectOneToOneRoom: isDirectOneToOneRoom, + hasOngoingCall: hasOngoingCall, + activeRoomCallIntent: activeRoomCallIntent, + isDirectOneToOneRoom: isDirectOneToOneRoom, hasSuccessor: false) } } diff --git a/ElementX/Sources/Services/Room/RoomInfoProxy.swift b/ElementX/Sources/Services/Room/RoomInfoProxy.swift index 8d34ad0b6..f6ab6d825 100644 --- a/ElementX/Sources/Services/Room/RoomInfoProxy.swift +++ b/ElementX/Sources/Services/Room/RoomInfoProxy.swift @@ -110,6 +110,17 @@ struct RoomInfoProxy: RoomInfoProxyProtocol { roomInfo.hasRoomCall } + var activeRoomCallIntent: CallIntent? { + switch roomInfo.activeRoomCallConsensusIntent { + case .full(let intent): + return .init(rustCallIntent: intent) + case .partial(intent: let intent, _, _): + return .init(rustCallIntent: intent) + case .none: + return nil + } + } + var activeRoomCallParticipants: [String] { roomInfo.activeRoomCallParticipants } diff --git a/ElementX/Sources/Services/Room/RoomInfoProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomInfoProxyProtocol.swift index d4ab657d4..b208b3feb 100644 --- a/ElementX/Sources/Services/Room/RoomInfoProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomInfoProxyProtocol.swift @@ -51,6 +51,7 @@ protocol RoomInfoProxyProtocol: BaseRoomInfoProxyProtocol { var notificationCount: Int { get } var cachedUserDefinedNotificationMode: RoomNotificationMode? { get } var hasRoomCall: Bool { get } + var activeRoomCallIntent: CallIntent? { get } var activeRoomCallParticipants: [String] { get } var isMarkedUnread: Bool { get } var unreadMessagesCount: UInt { get } diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift index cbc06a381..508493e1b 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift @@ -57,6 +57,7 @@ struct RoomSummary { let alternativeAliases: Set let hasOngoingCall: Bool + let activeCallIntent: CallIntent? let isMarkedUnread: Bool let isFavourite: Bool @@ -140,6 +141,7 @@ extension RoomSummary { canonicalAlias = nil alternativeAliases = [] hasOngoingCall = false + activeCallIntent = nil joinRequestType = nil isMarkedUnread = false diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift index 9e53719f3..0a9491573 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift @@ -313,6 +313,15 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { default: nil } + let activeCallIntent: RtcCallIntent? = switch roomInfo.activeRoomCallConsensusIntent { + case .full(let intent): + intent + case .partial(intent: let intent, _, _): + intent + case .none: + nil + } + return RoomSummary(room: room, id: roomInfo.id, joinRequestType: joinRequestType, @@ -332,6 +341,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { canonicalAlias: roomInfo.canonicalAlias, alternativeAliases: .init(roomInfo.alternativeAliases), hasOngoingCall: roomInfo.hasRoomCall, + activeCallIntent: activeCallIntent.map { .init(rustCallIntent: $0) }, isMarkedUnread: roomInfo.isMarkedUnread, isFavourite: roomInfo.isFavourite, isTombstoned: roomInfo.successorRoom != nil) diff --git a/Enterprise b/Enterprise index 973f37d49..4ce8b9a0e 160000 --- a/Enterprise +++ b/Enterprise @@ -1 +1 @@ -Subproject commit 973f37d49ca8c9be5e62e08c1edfa9f777077be6 +Subproject commit 4ce8b9a0e28ac38ca2b8cf7fe73255a6afdbdc14 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreen.Loaded-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreen.Loaded-iPad-en-GB.png index 87f5e2e71..498ebe093 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreen.Loaded-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreen.Loaded-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f901edb0c39131e27370319469ac0d0dfce5719b174046b0f6673580593c07ed -size 303085 +oid sha256:149d25dc1e12dfe9c9bcbfebd1ac04d02793312e8911b54ef84dfc086222471a +size 303186 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreen.Loaded-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreen.Loaded-iPad-pseudo.png index 419223349..4da3866ce 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreen.Loaded-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreen.Loaded-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b6e8ba88bfb62326c026921098221dfcc5b1d70800a293af203c569f8b1101d -size 313396 +oid sha256:afab5e01fcb18108837e3bfeaddf6be1bb593e102662cd146ea462060a7d7505 +size 313484 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreen.Loaded-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreen.Loaded-iPhone-en-GB.png index c4774ce8a..dd86624be 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreen.Loaded-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreen.Loaded-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5a8760dab42849524bbac719d24b672df43c93eb206ccc612708103e1f797203 -size 210718 +oid sha256:7404926b789acd48873e81784298fab5ffd79ff0519af1b37337ca55f290f08b +size 210835 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreen.Loaded-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreen.Loaded-iPhone-pseudo.png index 68f3475ab..1103c4320 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreen.Loaded-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreen.Loaded-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed1f7cd07578279025a64adfe0841d146217b30faad542b82abd682b55e3f19f -size 216503 +oid sha256:c2cab7ce3717833e02f05086b011f9830ffad2f185b5679e6d761d21cf79ee4d +size 216624 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Generic-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Generic-iPad-en-GB.png index 40ea49da7..9efb3a9c3 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Generic-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Generic-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ffba894dea08125123ea68ed3de1f138ae1265a05f465a6c298348642933d7c8 -size 242706 +oid sha256:339294b60291c6e37eac218a284cc76abccf250d953e516f84f9c942bf02399d +size 242792 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Generic-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Generic-iPad-pseudo.png index 491460c3a..e2b544af3 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Generic-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Generic-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b2db126a886bb2c985bbe01a8369dae88b832ec10f485e2a518dfb664425a86e -size 248420 +oid sha256:ba13cf5b1fa1a3fb3c0878a75efed3f852aab3e276e9f67c4e7b8accbfe8624e +size 248504 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Generic-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Generic-iPhone-en-GB.png index 9ecd32cec..4b36754bd 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Generic-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Generic-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae2426b7516db93b4cead0e539dedf53b0c7ee000563851462cec9ec5e54dccd -size 180341 +oid sha256:75fd157f849dc5ef362b4379665a91e792010760b21d8c41f6b92960aec3c91c +size 180458 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Generic-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Generic-iPhone-pseudo.png index 0ab6ff2c3..aa969b535 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Generic-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Generic-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3cf9b24d0eaf11def62a6e55d3118202975ebc93bcea6bc364b8f999172fe973 -size 184712 +oid sha256:e6f4e20256bdb47489fdc7cb8893833e2ff5d5a61981a32d58beb615e31c15b6 +size 184812 diff --git a/UnitTests/Sources/HomeScreenRoomTests.swift b/UnitTests/Sources/HomeScreenRoomTests.swift index 03b435774..aaf8b2fa1 100644 --- a/UnitTests/Sources/HomeScreenRoomTests.swift +++ b/UnitTests/Sources/HomeScreenRoomTests.swift @@ -19,7 +19,8 @@ struct HomeScreenRoomTests { unreadMentionsCount: UInt, unreadNotificationsCount: UInt, notificationMode: RoomNotificationModeProxy, - hasOngoingCall: Bool) { + hasOngoingCall: Bool, + activeCallIntent: CallIntent? = nil) { roomSummary = RoomSummary(room: .init(noHandle: .init()), id: "Test room", joinRequestType: nil, @@ -39,6 +40,7 @@ struct HomeScreenRoomTests { canonicalAlias: nil, alternativeAliases: [], hasOngoingCall: hasOngoingCall, + activeCallIntent: activeCallIntent, isMarkedUnread: isMarkedUnread, isFavourite: false, isTombstoned: false) @@ -57,7 +59,7 @@ struct HomeScreenRoomTests { #expect(!room.isHighlighted) #expect(!room.badges.isDotShown) - #expect(!room.badges.isCallShown) + #expect(room.badges.callBadgeType == .none) #expect(!room.badges.isMuteShown) #expect(!room.badges.isMentionShown) } @@ -75,11 +77,26 @@ struct HomeScreenRoomTests { #expect(room.isHighlighted) #expect(room.badges.isDotShown) - #expect(room.badges.isCallShown) + #expect(room.badges.callBadgeType == .video) #expect(!room.badges.isMuteShown) #expect(room.badges.isMentionShown) } + @Test + mutating func voiceCallBadget() { + setupRoomSummary(isMarkedUnread: true, + unreadMessagesCount: 0, + unreadMentionsCount: 0, + unreadNotificationsCount: 0, + notificationMode: .allMessages, + hasOngoingCall: true, + activeCallIntent: .audio) + + let room = HomeScreenRoom(summary: roomSummary, hideUnreadMessagesBadge: false) + + #expect(room.badges.callBadgeType == .voice) + } + @Test mutating func unhighlightedDot() { setupRoomSummary(isMarkedUnread: false, @@ -93,7 +110,7 @@ struct HomeScreenRoomTests { #expect(!room.isHighlighted) #expect(room.badges.isDotShown) - #expect(!room.badges.isCallShown) + #expect(room.badges.callBadgeType == .none) #expect(!room.badges.isMuteShown) #expect(!room.badges.isMentionShown) } @@ -111,7 +128,7 @@ struct HomeScreenRoomTests { #expect(room.isHighlighted) #expect(room.badges.isDotShown) - #expect(!room.badges.isCallShown) + #expect(room.badges.callBadgeType == .none) #expect(!room.badges.isMuteShown) #expect(!room.badges.isMentionShown) } @@ -129,7 +146,7 @@ struct HomeScreenRoomTests { #expect(room.isHighlighted) #expect(room.badges.isDotShown) - #expect(!room.badges.isCallShown) + #expect(room.badges.callBadgeType == .none) #expect(!room.badges.isMuteShown) #expect(room.badges.isMentionShown) } @@ -147,7 +164,7 @@ struct HomeScreenRoomTests { #expect(!room.isHighlighted) #expect(!room.badges.isDotShown) - #expect(room.badges.isCallShown) + #expect(room.badges.callBadgeType == .video) #expect(!room.badges.isMuteShown) #expect(!room.badges.isMentionShown) } @@ -165,7 +182,7 @@ struct HomeScreenRoomTests { #expect(!room.isHighlighted) #expect(room.badges.isDotShown) - #expect(!room.badges.isCallShown) + #expect(room.badges.callBadgeType == .none) #expect(!room.badges.isMuteShown) #expect(!room.badges.isMentionShown) } @@ -183,7 +200,7 @@ struct HomeScreenRoomTests { #expect(!room.isHighlighted) #expect(!room.badges.isDotShown) - #expect(!room.badges.isCallShown) + #expect(room.badges.callBadgeType == .none) #expect(!room.badges.isMuteShown) #expect(!room.badges.isMentionShown) } @@ -203,7 +220,7 @@ struct HomeScreenRoomTests { #expect(room.isHighlighted) #expect(room.badges.isDotShown) - #expect(!room.badges.isCallShown) + #expect(room.badges.callBadgeType == .none) #expect(!room.badges.isMuteShown) #expect(!room.badges.isMentionShown) } @@ -221,7 +238,7 @@ struct HomeScreenRoomTests { #expect(room.isHighlighted) #expect(room.badges.isDotShown) - #expect(!room.badges.isCallShown) + #expect(room.badges.callBadgeType == .none) #expect(!room.badges.isMuteShown) #expect(room.badges.isMentionShown) } @@ -239,7 +256,7 @@ struct HomeScreenRoomTests { #expect(room.isHighlighted) #expect(room.badges.isDotShown) - #expect(room.badges.isCallShown) + #expect(room.badges.callBadgeType == .video) #expect(room.badges.isMuteShown) #expect(!room.badges.isMentionShown) } diff --git a/UnitTests/Sources/LoggingTests.swift b/UnitTests/Sources/LoggingTests.swift index 57a45ce51..2beb33a99 100644 --- a/UnitTests/Sources/LoggingTests.swift +++ b/UnitTests/Sources/LoggingTests.swift @@ -91,6 +91,7 @@ final class LoggingTests { canonicalAlias: nil, alternativeAliases: [], hasOngoingCall: false, + activeCallIntent: nil, isMarkedUnread: false, isFavourite: false, isTombstoned: false) diff --git a/UnitTests/Sources/RoomSummaryTests.swift b/UnitTests/Sources/RoomSummaryTests.swift index 4151ec6fb..cc5108b5c 100644 --- a/UnitTests/Sources/RoomSummaryTests.swift +++ b/UnitTests/Sources/RoomSummaryTests.swift @@ -114,6 +114,7 @@ struct RoomSummaryTests { canonicalAlias: nil, alternativeAliases: [], hasOngoingCall: false, + activeCallIntent: nil, isMarkedUnread: false, isFavourite: false, isTombstoned: isTombstoned)