From 9db54f779a2a282b2b94636bb875d12f83a5c5ea Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Wed, 6 Dec 2023 13:14:23 +0200 Subject: [PATCH] Element Call permission tweaks (#2187) * vector-im/element-meta/issues/2230 - Specify `call.member` default power levels when creating a room * vector-im/element-meta/issues/2229 - Take into account permissions when configuring the room screen call button --- .../Mocks/Generated/GeneratedMocks.swift | 21 ++++++++++++++ ElementX/Sources/Mocks/RoomProxyMock.swift | 5 +++- .../Screens/RoomScreen/RoomScreenModels.swift | 1 + .../RoomScreen/RoomScreenViewModel.swift | 7 +++++ .../Screens/RoomScreen/View/RoomScreen.swift | 1 + .../Sources/Services/Client/ClientProxy.swift | 29 +++++++++++++++++-- .../Sources/Services/Room/RoomProxy.swift | 9 ++++++ .../Services/Room/RoomProxyProtocol.swift | 2 ++ .../CompletionSuggestionServiceTests.swift | 4 +-- .../PreviewTests/test_timelineView.1.png | 4 +-- .../PreviewTests/test_uITimelineView.1.png | 4 +-- 11 files changed, 78 insertions(+), 9 deletions(-) diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index b8c927cdd..c0d5adbb8 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -2357,6 +2357,27 @@ class RoomProxyMock: RoomProxyProtocol { return canUserTriggerRoomNotificationUserIDReturnValue } } + //MARK: - canUserJoinCall + + var canUserJoinCallUserIDCallsCount = 0 + var canUserJoinCallUserIDCalled: Bool { + return canUserJoinCallUserIDCallsCount > 0 + } + var canUserJoinCallUserIDReceivedUserID: String? + var canUserJoinCallUserIDReceivedInvocations: [String] = [] + var canUserJoinCallUserIDReturnValue: Result! + var canUserJoinCallUserIDClosure: ((String) async -> Result)? + + func canUserJoinCall(userID: String) async -> Result { + canUserJoinCallUserIDCallsCount += 1 + canUserJoinCallUserIDReceivedUserID = userID + canUserJoinCallUserIDReceivedInvocations.append(userID) + if let canUserJoinCallUserIDClosure = canUserJoinCallUserIDClosure { + return await canUserJoinCallUserIDClosure(userID) + } else { + return canUserJoinCallUserIDReturnValue + } + } //MARK: - elementCallWidgetDriver var elementCallWidgetDriverCallsCount = 0 diff --git a/ElementX/Sources/Mocks/RoomProxyMock.swift b/ElementX/Sources/Mocks/RoomProxyMock.swift index 29e4a3385..b632ace75 100644 --- a/ElementX/Sources/Mocks/RoomProxyMock.swift +++ b/ElementX/Sources/Mocks/RoomProxyMock.swift @@ -32,7 +32,6 @@ struct RoomProxyMockConfiguration { var canonicalAlias: String? var alternativeAliases: [String] = [] var hasUnreadNotifications = Bool.random() - var canUserTriggerRoomNotification = false var timeline = { let mock = TimelineProxyMock() @@ -45,6 +44,9 @@ struct RoomProxyMockConfiguration { var memberForID: RoomMemberProxyMock = .mockMe var ownUserID = "@alice:somewhere.org" + var canUserTriggerRoomNotification = false + var canUserJoinCall = false + var invitedMembersCount = 100 var joinedMembersCount = 50 var activeMembersCount = 25 @@ -94,5 +96,6 @@ extension RoomProxyMock { getMemberUserIDReturnValue = .success(configuration.memberForID) canUserRedactUserIDReturnValue = .success(false) canUserTriggerRoomNotificationUserIDReturnValue = .success(configuration.canUserTriggerRoomNotification) + canUserJoinCallUserIDReturnValue = .success(configuration.canUserJoinCall) } } diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift index 3968223ee..1996b25a0 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift @@ -129,6 +129,7 @@ struct RoomScreenViewState: BindableState { var ownUserID: String + var canJoinCall = false var hasOngoingCall = false var bindings: RoomScreenViewStateBindings diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index afdb51e97..492311628 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -115,6 +115,13 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol // Note: beware if we get to e.g. restore a reply / edit, // maybe we are tracking a non-needed first initial state trackComposerMode(.default) + + Task { + let userID = roomProxy.ownUserID + if case let .success(permission) = await roomProxy.canUserJoinCall(userID: userID) { + state.canJoinCall = permission + } + } } // MARK: - Public diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift index 22a15d542..97e157cef 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift @@ -162,6 +162,7 @@ struct RoomScreen: View { if !ProcessInfo.processInfo.isiOSAppOnMac { ToolbarItem(placement: .primaryAction) { callButton + .disabled(context.viewState.canJoinCall == false) } } } diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index 64524e98f..22a46da45 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -50,6 +50,22 @@ class ClientProxy: ClientProxyProtocol { let secureBackupController: SecureBackupControllerProtocol private let roomListRecencyOrderingAllowedEventTypes = ["m.room.message", "m.room.encrypted", "m.sticker"] + + private static var roomCreationPowerLevelOverrides: PowerLevels { + .init(usersDefault: nil, + eventsDefault: nil, + stateDefault: nil, + ban: nil, + kick: nil, + redact: nil, + invite: nil, + notifications: nil, + users: [:], + events: [ + "m.call.member": Int32(0), + "org.matrix.msc3401.call.member": Int32(0) + ]) + } private var loadCachedAvatarURLTask: Task? private let userAvatarURLSubject = CurrentValueSubject(nil) @@ -208,7 +224,15 @@ class ClientProxy: ClientProxyProtocol { func createDirectRoom(with userID: String, expectedRoomName: String?) async -> Result { let result: Result = await Task.dispatch(on: clientQueue) { do { - let parameters = CreateRoomParameters(name: nil, topic: nil, isEncrypted: true, isDirect: true, visibility: .private, preset: .trustedPrivateChat, invite: [userID], avatar: nil) + let parameters = CreateRoomParameters(name: nil, + topic: nil, + isEncrypted: true, + isDirect: true, + visibility: .private, + preset: .trustedPrivateChat, + invite: [userID], + avatar: nil, + powerLevelContentOverride: Self.roomCreationPowerLevelOverrides) let result = try self.client.createRoom(request: parameters) return .success(result) } catch { @@ -229,7 +253,8 @@ class ClientProxy: ClientProxyProtocol { visibility: isRoomPrivate ? .private : .public, preset: isRoomPrivate ? .privateChat : .publicChat, invite: userIDs, - avatar: avatarURL?.absoluteString) + avatar: avatarURL?.absoluteString, + powerLevelContentOverride: Self.roomCreationPowerLevelOverrides) let roomID = try self.client.createRoom(request: parameters) return .success(roomID) } catch { diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index c17c4b4e3..0a095982d 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -396,6 +396,15 @@ class RoomProxy: RoomProxyProtocol { } } + func canUserJoinCall(userID: String) async -> Result { + do { + return try await .success(room.canUserSendState(userId: userID, stateEvent: .callMember)) + } catch { + MXLog.error("Failed checking if the user can trigger room notification with error: \(error)") + return .failure(.failedCheckingPermission) + } + } + // MARK: - Element Call func elementCallWidgetDriver() -> ElementCallWidgetDriverProtocol { diff --git a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift index 7fb6cb345..f5ff66b1a 100644 --- a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift @@ -114,6 +114,8 @@ protocol RoomProxyProtocol { func canUserTriggerRoomNotification(userID: String) async -> Result + func canUserJoinCall(userID: String) async -> Result + // MARK: - Element Call func elementCallWidgetDriver() -> ElementCallWidgetDriverProtocol diff --git a/UnitTests/Sources/CompletionSuggestionServiceTests.swift b/UnitTests/Sources/CompletionSuggestionServiceTests.swift index ec6c95c58..b8b260bf9 100644 --- a/UnitTests/Sources/CompletionSuggestionServiceTests.swift +++ b/UnitTests/Sources/CompletionSuggestionServiceTests.swift @@ -66,7 +66,7 @@ final class CompletionSuggestionServiceTests: XCTestCase { func testUserSuggestonsIncludingAllUsers() async throws { let alice: RoomMemberProxyMock = .mockAlice let members: [RoomMemberProxyMock] = [alice, .mockBob, .mockCharlie, .mockMe] - let roomProxyMock = RoomProxyMock(with: .init(displayName: "test", canUserTriggerRoomNotification: true, members: members)) + let roomProxyMock = RoomProxyMock(with: .init(displayName: "test", members: members, canUserTriggerRoomNotification: true)) let service = CompletionSuggestionService(roomProxy: roomProxyMock) var deferred = deferFulfillment(service.suggestionsPublisher) { suggestions in @@ -92,7 +92,7 @@ final class CompletionSuggestionServiceTests: XCTestCase { let alice: RoomMemberProxyMock = .mockAlice let bob: RoomMemberProxyMock = .mockBob let members: [RoomMemberProxyMock] = [alice, bob, .mockMe] - let roomProxyMock = RoomProxyMock(with: .init(displayName: "test", canUserTriggerRoomNotification: true, members: members)) + let roomProxyMock = RoomProxyMock(with: .init(displayName: "test", members: members, canUserTriggerRoomNotification: true)) let service = CompletionSuggestionService(roomProxy: roomProxyMock) var deferred = deferFulfillment(service.suggestionsPublisher) { suggestions in diff --git a/UnitTests/__Snapshots__/PreviewTests/test_timelineView.1.png b/UnitTests/__Snapshots__/PreviewTests/test_timelineView.1.png index a3c54c90a..b239dbfc7 100644 --- a/UnitTests/__Snapshots__/PreviewTests/test_timelineView.1.png +++ b/UnitTests/__Snapshots__/PreviewTests/test_timelineView.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:14aa29c8a73cb71b322ba5ba067ebe8f317b477cf4db754c93902061465a1413 -size 308497 +oid sha256:b04690d8cebb4b16b56075243f5a337728c51fc7a72c3be0ef6dc60d7d7aede2 +size 308386 diff --git a/UnitTests/__Snapshots__/PreviewTests/test_uITimelineView.1.png b/UnitTests/__Snapshots__/PreviewTests/test_uITimelineView.1.png index a3c54c90a..b239dbfc7 100644 --- a/UnitTests/__Snapshots__/PreviewTests/test_uITimelineView.1.png +++ b/UnitTests/__Snapshots__/PreviewTests/test_uITimelineView.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:14aa29c8a73cb71b322ba5ba067ebe8f317b477cf4db754c93902061465a1413 -size 308497 +oid sha256:b04690d8cebb4b16b56075243f5a337728c51fc7a72c3be0ef6dc60d7d7aede2 +size 308386