diff --git a/ElementX/Sources/FlowCoordinators/RoomRolesAndPermissionsFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomRolesAndPermissionsFlowCoordinator.swift index 665db1b65..aba137a9c 100644 --- a/ElementX/Sources/FlowCoordinators/RoomRolesAndPermissionsFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomRolesAndPermissionsFlowCoordinator.swift @@ -119,10 +119,10 @@ class RoomRolesAndPermissionsFlowCoordinator: FlowCoordinatorProtocol { stateMachine.addRoutes(event: .finishedChangingRoles, transitions: [.changingRoles => .rolesAndPermissionsScreen]) stateMachine.addRoutes(event: .changePermissions, transitions: [.rolesAndPermissionsScreen => .changingPermissions]) { [weak self] context in - guard let permissions = context.userInfo as? RoomPermissions else { + guard let (ownPowerLevel, permissions) = context.userInfo as? (RoomPowerLevel, RoomPermissions) else { fatalError("Expected a group and the current permissions") } - self?.presentChangePermissionsScreen(permissions: permissions) + self?.presentChangePermissionsScreen(ownPowerLevel: ownPowerLevel, permissions: permissions) } stateMachine.addRoutes(event: .finishedChangingPermissions, transitions: [.changingPermissions => .rolesAndPermissionsScreen]) @@ -144,8 +144,8 @@ class RoomRolesAndPermissionsFlowCoordinator: FlowCoordinatorProtocol { switch action { case .editRoles(let role): stateMachine.tryEvent(.changeRoles, userInfo: role) - case .editPermissions(let permissions): - stateMachine.tryEvent(.changePermissions, userInfo: permissions) + case .editPermissions(let ownPowerLevel, let permissions): + stateMachine.tryEvent(.changePermissions, userInfo: (ownPowerLevel, permissions)) case .demotedOwnUser: stateMachine.tryEvent(.demotedOwnUser) } @@ -179,8 +179,9 @@ class RoomRolesAndPermissionsFlowCoordinator: FlowCoordinatorProtocol { } } - private func presentChangePermissionsScreen(permissions: RoomPermissions) { - let parameters = RoomChangePermissionsScreenCoordinatorParameters(permissions: permissions, + private func presentChangePermissionsScreen(ownPowerLevel: RoomPowerLevel, permissions: RoomPermissions) { + let parameters = RoomChangePermissionsScreenCoordinatorParameters(ownPowerLevel: ownPowerLevel, + permissions: permissions, roomProxy: roomProxy, userIndicatorController: userIndicatorController, analytics: analytics) diff --git a/ElementX/Sources/Screens/RoomChangePermissionsScreen/RoomChangePermissionsScreenCoordinator.swift b/ElementX/Sources/Screens/RoomChangePermissionsScreen/RoomChangePermissionsScreenCoordinator.swift index eaedf8348..3fa28741e 100644 --- a/ElementX/Sources/Screens/RoomChangePermissionsScreen/RoomChangePermissionsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomChangePermissionsScreen/RoomChangePermissionsScreenCoordinator.swift @@ -10,6 +10,7 @@ import Combine import SwiftUI struct RoomChangePermissionsScreenCoordinatorParameters { + let ownPowerLevel: RoomPowerLevel let permissions: RoomPermissions let roomProxy: JoinedRoomProxyProtocol let userIndicatorController: UserIndicatorControllerProtocol @@ -21,7 +22,6 @@ enum RoomChangePermissionsScreenCoordinatorAction { } final class RoomChangePermissionsScreenCoordinator: CoordinatorProtocol { - private let parameters: RoomChangePermissionsScreenCoordinatorParameters private var viewModel: RoomChangePermissionsScreenViewModelProtocol private var cancellables = Set() @@ -31,9 +31,8 @@ final class RoomChangePermissionsScreenCoordinator: CoordinatorProtocol { } init(parameters: RoomChangePermissionsScreenCoordinatorParameters) { - self.parameters = parameters - viewModel = RoomChangePermissionsScreenViewModel(currentPermissions: parameters.permissions, + ownPowerLevel: parameters.ownPowerLevel, roomProxy: parameters.roomProxy, userIndicatorController: parameters.userIndicatorController, analytics: parameters.analytics) diff --git a/ElementX/Sources/Screens/RoomChangePermissionsScreen/RoomChangePermissionsScreenModels.swift b/ElementX/Sources/Screens/RoomChangePermissionsScreen/RoomChangePermissionsScreenModels.swift index 900a2a23b..901784307 100644 --- a/ElementX/Sources/Screens/RoomChangePermissionsScreen/RoomChangePermissionsScreenModels.swift +++ b/ElementX/Sources/Screens/RoomChangePermissionsScreen/RoomChangePermissionsScreenModels.swift @@ -69,7 +69,7 @@ extension RoomChangePermissionsScreenViewState { /// - Parameters: /// - currentPermissions: The current permissions for the room. /// - isSpace: if the room is a space or a normal room. - init(currentPermissions: RoomPermissions, isSpace: Bool) { + init(ownPowerLevel: RoomPowerLevel, currentPermissions: RoomPermissions, isSpace: Bool) { var settings = [RoomChangePermissionsScreenGroup: [RoomPermissionsSetting]]() for group in RoomChangePermissionsScreenGroup.allCases { switch group { @@ -77,12 +77,15 @@ extension RoomChangePermissionsScreenViewState { settings[group] = [ RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsRoomName, value: currentPermissions.roomName, + ownPowerLevel: ownPowerLevel, keyPath: \.roomName), RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsRoomAvatar, value: currentPermissions.roomAvatar, + ownPowerLevel: ownPowerLevel, keyPath: \.roomAvatar), RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsRoomTopic, value: currentPermissions.roomTopic, + ownPowerLevel: ownPowerLevel, keyPath: \.roomTopic) ] @@ -90,12 +93,15 @@ extension RoomChangePermissionsScreenViewState { settings[group] = [ RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsInvitePeople, value: currentPermissions.invite, + ownPowerLevel: ownPowerLevel, keyPath: \.invite), RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsRemovePeople, value: currentPermissions.kick, + ownPowerLevel: ownPowerLevel, keyPath: \.kick), RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsBanPeople, value: currentPermissions.ban, + ownPowerLevel: ownPowerLevel, keyPath: \.ban) ] case .messagesAndContent: @@ -103,9 +109,11 @@ extension RoomChangePermissionsScreenViewState { settings[group] = [ RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsSendMessages, value: currentPermissions.eventsDefault, + ownPowerLevel: ownPowerLevel, keyPath: \.eventsDefault), RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsDeleteMessages, value: currentPermissions.redact, + ownPowerLevel: ownPowerLevel, keyPath: \.redact) ] } @@ -114,6 +122,7 @@ extension RoomChangePermissionsScreenViewState { settings[group] = [ RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsManageSpaceRooms, value: currentPermissions.spaceChild, + ownPowerLevel: ownPowerLevel, keyPath: \.spaceChild) ] } diff --git a/ElementX/Sources/Screens/RoomChangePermissionsScreen/RoomChangePermissionsScreenViewModel.swift b/ElementX/Sources/Screens/RoomChangePermissionsScreen/RoomChangePermissionsScreenViewModel.swift index e4ac12f22..a827442b0 100644 --- a/ElementX/Sources/Screens/RoomChangePermissionsScreen/RoomChangePermissionsScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomChangePermissionsScreen/RoomChangePermissionsScreenViewModel.swift @@ -23,13 +23,16 @@ class RoomChangePermissionsScreenViewModel: RoomChangePermissionsScreenViewModel } init(currentPermissions: RoomPermissions, + ownPowerLevel: RoomPowerLevel, roomProxy: JoinedRoomProxyProtocol, userIndicatorController: UserIndicatorControllerProtocol, analytics: AnalyticsService) { self.roomProxy = roomProxy self.userIndicatorController = userIndicatorController self.analytics = analytics - super.init(initialViewState: .init(currentPermissions: currentPermissions, isSpace: roomProxy.infoPublisher.value.isSpace)) + super.init(initialViewState: .init(ownPowerLevel: ownPowerLevel, + currentPermissions: currentPermissions, + isSpace: roomProxy.infoPublisher.value.isSpace)) } // MARK: - Public @@ -64,7 +67,7 @@ class RoomChangePermissionsScreenViewModel: RoomChangePermissionsScreenViewModel .flatMap { $0 } .filter { state.currentPermissions[keyPath: $0.keyPath] != $0.value } for setting in changedSettings { - changes[keyPath: setting.rustKeyPath] = setting.value.powerLevelValue + changes[keyPath: setting.rustKeyPath] = setting.value } switch await roomProxy.applyPowerLevelChanges(changes) { @@ -106,14 +109,14 @@ class RoomChangePermissionsScreenViewModel: RoomChangePermissionsScreenViewModel private func trackChanges(_ settings: [RoomPermissionsSetting]) { for setting in settings { switch setting.keyPath { - case \.ban: analytics.trackRoomModeration(action: .ChangePermissionsBanMembers, role: setting.value) - case \.invite: analytics.trackRoomModeration(action: .ChangePermissionsInviteUsers, role: setting.value) - case \.kick: analytics.trackRoomModeration(action: .ChangePermissionsKickMembers, role: setting.value) - case \.redact: analytics.trackRoomModeration(action: .ChangePermissionsRedactMessages, role: setting.value) - case \.eventsDefault: analytics.trackRoomModeration(action: .ChangePermissionsSendMessages, role: setting.value) - case \.roomName: analytics.trackRoomModeration(action: .ChangePermissionsRoomName, role: setting.value) - case \.roomAvatar: analytics.trackRoomModeration(action: .ChangePermissionsRoomAvatar, role: setting.value) - case \.roomTopic: analytics.trackRoomModeration(action: .ChangePermissionsRoomTopic, role: setting.value) + case \.ban: analytics.trackRoomModeration(action: .ChangePermissionsBanMembers, role: setting.roleValue) + case \.invite: analytics.trackRoomModeration(action: .ChangePermissionsInviteUsers, role: setting.roleValue) + case \.kick: analytics.trackRoomModeration(action: .ChangePermissionsKickMembers, role: setting.roleValue) + case \.redact: analytics.trackRoomModeration(action: .ChangePermissionsRedactMessages, role: setting.roleValue) + case \.eventsDefault: analytics.trackRoomModeration(action: .ChangePermissionsSendMessages, role: setting.roleValue) + case \.roomName: analytics.trackRoomModeration(action: .ChangePermissionsRoomName, role: setting.roleValue) + case \.roomAvatar: analytics.trackRoomModeration(action: .ChangePermissionsRoomAvatar, role: setting.roleValue) + case \.roomTopic: analytics.trackRoomModeration(action: .ChangePermissionsRoomTopic, role: setting.roleValue) default: MXLog.warning("Unexpected change: \(setting.keyPath).") } } diff --git a/ElementX/Sources/Screens/RoomChangePermissionsScreen/View/RoomChangePermissionsScreen.swift b/ElementX/Sources/Screens/RoomChangePermissionsScreen/View/RoomChangePermissionsScreen.swift index ef4e73c00..89766d5ab 100644 --- a/ElementX/Sources/Screens/RoomChangePermissionsScreen/View/RoomChangePermissionsScreen.swift +++ b/ElementX/Sources/Screens/RoomChangePermissionsScreen/View/RoomChangePermissionsScreen.swift @@ -32,7 +32,9 @@ struct RoomChangePermissionsScreen: View { Section { ForEach(settings) { $setting in ListRow(label: .plain(title: setting.title), - kind: .picker(selection: $setting.value, items: setting.allValues)) + kind: .picker(selection: $setting.roleValue, + items: setting.availableValues)) + .disabled(setting.isDisabled) } } header: { Text(group.name) @@ -64,6 +66,7 @@ struct RoomChangePermissionsScreen: View { struct RoomChangePermissionsScreen_Previews: PreviewProvider, TestablePreview { static let roomViewModel = makeViewModel(isSpace: false) + static let roomAsUserViewModel = makeViewModel(isSpace: false, ownPowerLevel: RoomRole.user.powerLevel) static let spaceViewModel = makeViewModel(isSpace: true) static var previews: some View { @@ -72,14 +75,20 @@ struct RoomChangePermissionsScreen_Previews: PreviewProvider, TestablePreview { } .previewDisplayName("Room") + NavigationStack { + RoomChangePermissionsScreen(context: roomAsUserViewModel.context) + } + .previewDisplayName("Room as User") + NavigationStack { RoomChangePermissionsScreen(context: spaceViewModel.context) } .previewDisplayName("Space") } - static func makeViewModel(isSpace: Bool) -> RoomChangePermissionsScreenViewModel { + static func makeViewModel(isSpace: Bool, ownPowerLevel: RoomPowerLevel = RoomRole.creator.powerLevel) -> RoomChangePermissionsScreenViewModel { RoomChangePermissionsScreenViewModel(currentPermissions: .init(powerLevels: .mock), + ownPowerLevel: ownPowerLevel, roomProxy: JoinedRoomProxyMock(.init(isSpace: isSpace)), userIndicatorController: UserIndicatorControllerMock(), analytics: ServiceLocator.shared.analytics) diff --git a/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/RoomRolesAndPermissionsScreenCoordinator.swift b/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/RoomRolesAndPermissionsScreenCoordinator.swift index 611870b9a..3b8a2185a 100644 --- a/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/RoomRolesAndPermissionsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/RoomRolesAndPermissionsScreenCoordinator.swift @@ -17,7 +17,7 @@ struct RoomRolesAndPermissionsScreenCoordinatorParameters { enum RoomRolesAndPermissionsScreenCoordinatorAction { case editRoles(RoomRolesAndPermissionsScreenRole) - case editPermissions(permissions: RoomPermissions) + case editPermissions(ownPowerLevel: RoomPowerLevel, permissions: RoomPermissions) case demotedOwnUser } @@ -44,8 +44,8 @@ final class RoomRolesAndPermissionsScreenCoordinator: CoordinatorProtocol { switch action { case .editRoles(let role): actionsSubject.send(.editRoles(role)) - case .editPermissions(let permissions): - actionsSubject.send(.editPermissions(permissions: permissions)) + case .editPermissions(let ownPowerLevel, let permissions): + actionsSubject.send(.editPermissions(ownPowerLevel: ownPowerLevel, permissions: permissions)) case .demotedOwnUser: actionsSubject.send(.demotedOwnUser) } diff --git a/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/RoomRolesAndPermissionsScreenModels.swift b/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/RoomRolesAndPermissionsScreenModels.swift index 0e45eaf41..4d21d2a28 100644 --- a/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/RoomRolesAndPermissionsScreenModels.swift +++ b/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/RoomRolesAndPermissionsScreenModels.swift @@ -12,13 +12,13 @@ enum RoomRolesAndPermissionsScreenViewModelAction { /// The user would like to edit member roles. case editRoles(RoomRolesAndPermissionsScreenRole) /// The user would like to edit room permissions. - case editPermissions(permissions: RoomPermissions) + case editPermissions(ownPowerLevel: RoomPowerLevel, permissions: RoomPermissions) /// The user has demoted themself. case demotedOwnUser } struct RoomRolesAndPermissionsScreenViewState: BindableState { - var ownRole: RoomRole + var ownPowerLevel: RoomPowerLevel var administratorsAndOwnersCount: Int? /// The number of administrators in the room. diff --git a/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/RoomRolesAndPermissionsScreenViewModel.swift b/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/RoomRolesAndPermissionsScreenViewModel.swift index 7621ebcb4..87f7b1696 100644 --- a/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/RoomRolesAndPermissionsScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/RoomRolesAndPermissionsScreenViewModel.swift @@ -26,7 +26,7 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM self.roomProxy = roomProxy self.userIndicatorController = userIndicatorController self.analytics = analytics - super.init(initialViewState: RoomRolesAndPermissionsScreenViewState(ownRole: roomProxy.membersPublisher.value.first { $0.userID == roomProxy.ownUserID }?.role ?? .administrator, + super.init(initialViewState: RoomRolesAndPermissionsScreenViewState(ownPowerLevel: roomProxy.membersPublisher.value.first { $0.userID == roomProxy.ownUserID }?.powerLevel ?? .value(Int(RoomRole.administrator.powerLevelValue)), permissions: initialPermissions)) // Automatically update the admin/moderator counts. @@ -91,7 +91,7 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM state.administratorCount = members.filter { $0.role == .administrator && $0.isActive }.count state.moderatorCount = members.filter { $0.role == .moderator && $0.isActive }.count if let ownUser = members.first(where: { $0.userID == roomProxy.ownUserID }) { - state.ownRole = ownUser.role + state.ownPowerLevel = ownUser.powerLevel } } @@ -132,7 +132,7 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM MXLog.error("Missing permissions.") return } - actionsSubject.send(.editPermissions(permissions: permissions)) + actionsSubject.send(.editPermissions(ownPowerLevel: state.ownPowerLevel, permissions: permissions)) } private func resetPermissions() async { diff --git a/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/View/RoomRolesAndPermissionsScreen.swift b/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/View/RoomRolesAndPermissionsScreen.swift index 1a900d799..50c5a49d5 100644 --- a/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/View/RoomRolesAndPermissionsScreen.swift +++ b/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/View/RoomRolesAndPermissionsScreen.swift @@ -27,7 +27,7 @@ struct RoomRolesAndPermissionsScreen: View { private var rolesSection: some View { Section { - if context.viewState.ownRole == .creator { + if context.viewState.ownPowerLevel.role == .creator { ListRow(label: .default(title: L10n.screenRoomRolesAndPermissionsAdminsAndOwners, icon: \.admin), details: administratorOrOwnersDetails, @@ -53,7 +53,7 @@ struct RoomRolesAndPermissionsScreen: View { }) .accessibilityIdentifier(A11yIdentifiers.roomRolesAndPermissionsScreen.moderators) - if context.viewState.ownRole != .creator { + if context.viewState.ownPowerLevel.role != .creator { ListRow(label: .default(title: L10n.screenRoomRolesAndPermissionsChangeMyRole, icon: \.edit), kind: .button { diff --git a/ElementX/Sources/Services/Room/RoomPermissions.swift b/ElementX/Sources/Services/Room/RoomPermissions.swift index dc618d9d9..694c86f3f 100644 --- a/ElementX/Sources/Services/Room/RoomPermissions.swift +++ b/ElementX/Sources/Services/Room/RoomPermissions.swift @@ -10,24 +10,29 @@ import Foundation import MatrixRustSDK struct RoomPermissionsSetting: Identifiable { - var id: KeyPath { keyPath } + static let allValues: [(title: String, tag: RoomRole)] = [(title: L10n.screenRoomChangePermissionsAdministrators, tag: .administrator), + (title: L10n.screenRoomChangePermissionsModerators, tag: .moderator), + (title: L10n.screenRoomChangePermissionsEveryone, tag: .user)] + var id: KeyPath { keyPath } /// The title of this setting. let title: String /// The selected role of this setting. - var value: RoomRole - /// All of the available roles that this setting can be configured with. - var allValues: [(title: String, tag: RoomRole)] { - [ - (title: L10n.screenRoomChangePermissionsAdministrators, tag: .administrator), - (title: L10n.screenRoomChangePermissionsModerators, tag: .moderator), - (title: L10n.screenRoomChangePermissionsEveryone, tag: .user) - ] - } + var value: Int64 + let ownPowerLevel: RoomPowerLevel + + var roleValue: RoomRole { + get { + RoomRole(powerLevelValue: value) + } set { + value = newValue.powerLevelValue + } + } + /// The `RoomPermissions` property that this setting is for. - let keyPath: KeyPath + let keyPath: KeyPath /// The `RoomPowerLevelChanges` property that this setting is saved into. var rustKeyPath: WritableKeyPath { switch keyPath { @@ -45,46 +50,75 @@ struct RoomPermissionsSetting: Identifiable { default: fatalError("Unexpected key path: \(keyPath)") } } + + /// Can the setting be edited + var isDisabled: Bool { + switch ownPowerLevel { + case .value(let ownValue): + ownValue < value + case .infinite: + false + } + } + + /// All of the available roles that this setting can be configured with. + var availableValues: [(title: String, tag: RoomRole)] { + if isDisabled { + Self.allValues.filter { $0.tag == RoomRole(powerLevelValue: value) } + } else { + Self.allValues.filter { $0.tag <= ownPowerLevel.role } + } + } + + init(title: String, + value: Int64, + ownPowerLevel: RoomPowerLevel, + keyPath: KeyPath) { + self.ownPowerLevel = ownPowerLevel + self.title = title + self.value = value + self.keyPath = keyPath + } } struct RoomPermissions { /// The level required to ban a user. - var ban: RoomRole + var ban: Int64 /// The level required to invite a user. - var invite: RoomRole + var invite: Int64 /// The level required to kick a user. - var kick: RoomRole + var kick: Int64 /// The level required to redact an event. - var redact: RoomRole + var redact: Int64 /// The default level required to send message events. - var eventsDefault: RoomRole + var eventsDefault: Int64 /// The default level required to send state events. - var stateDefault: RoomRole + var stateDefault: Int64 /// The default power level for every user in the room. - var usersDefault: RoomRole + var usersDefault: Int64 /// The level required to change the room's name. - var roomName: RoomRole + var roomName: Int64 /// The level required to change the room's avatar. - var roomAvatar: RoomRole + var roomAvatar: Int64 /// The level required to change the room's topic. - var roomTopic: RoomRole + var roomTopic: Int64 /// The level required to add/remove childrens from a space. - var spaceChild: RoomRole + var spaceChild: Int64 } extension RoomPermissions { /// Create permissions from the room's power levels. init(powerLevels: RoomPowerLevelsValues) { - ban = RoomRole(powerLevelValue: powerLevels.ban) - invite = RoomRole(powerLevelValue: powerLevels.invite) - kick = RoomRole(powerLevelValue: powerLevels.kick) - redact = RoomRole(powerLevelValue: powerLevels.redact) - eventsDefault = RoomRole(powerLevelValue: powerLevels.eventsDefault) - stateDefault = RoomRole(powerLevelValue: powerLevels.stateDefault) - usersDefault = RoomRole(powerLevelValue: powerLevels.usersDefault) - roomName = RoomRole(powerLevelValue: powerLevels.roomName) - roomAvatar = RoomRole(powerLevelValue: powerLevels.roomAvatar) - roomTopic = RoomRole(powerLevelValue: powerLevels.roomTopic) - spaceChild = RoomRole(powerLevelValue: powerLevels.spaceChild) + ban = powerLevels.ban + invite = powerLevels.invite + kick = powerLevels.kick + redact = powerLevels.redact + eventsDefault = powerLevels.eventsDefault + stateDefault = powerLevels.stateDefault + usersDefault = powerLevels.usersDefault + roomName = powerLevels.roomName + roomAvatar = powerLevels.roomAvatar + roomTopic = powerLevels.roomTopic + spaceChild = powerLevels.spaceChild } } diff --git a/ElementX/Sources/Services/Room/RoomPowerLevel.swift b/ElementX/Sources/Services/Room/RoomPowerLevel.swift index 393e16e97..6eb342061 100644 --- a/ElementX/Sources/Services/Room/RoomPowerLevel.swift +++ b/ElementX/Sources/Services/Room/RoomPowerLevel.swift @@ -30,6 +30,10 @@ enum RoomPowerLevel: Hashable, Comparable { case .value(let value): .value(value: Int64(value)) } } + + var role: RoomRole { + RoomRole(powerLevel: self) + } } extension PowerLevel { diff --git a/ElementX/Sources/Services/Room/RoomRole.swift b/ElementX/Sources/Services/Room/RoomRole.swift index b8e229455..a32132c83 100644 --- a/ElementX/Sources/Services/Room/RoomRole.swift +++ b/ElementX/Sources/Services/Room/RoomRole.swift @@ -110,21 +110,25 @@ extension RoomRole { /// To be used when setting the power level of a user to get the suggested equivalent power level value for that specific role /// NOTE: Do not use for comparison, use the true power level instead. var powerLevelValue: Int64 { + switch powerLevel { + case .infinite: + fatalError("Impossible") + case .value(let value): + return Int64(value) + } + } + + var powerLevel: RoomPowerLevel { guard self != .owner else { // Would be better if the SDK would return this, maybe a `suggestedPowerLevelValueForRole` function would solve the problem - return 150 + return .value(150) } do { - switch try suggestedPowerLevelForRole(role: rustRole) { - case .infinite: - fatalError("Impossible") - case .value(let value): - return value - } + return try RoomPowerLevel(rustPowerLevel: suggestedPowerLevelForRole(role: rustRole)) } catch { MXLog.error("Falied to convert role to power level value: \(error)") - return 0 + return .value(0) } } } diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomChangePermissionsScreen.Room-as-User-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomChangePermissionsScreen.Room-as-User-iPad-en-GB.png new file mode 100644 index 000000000..627840c33 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomChangePermissionsScreen.Room-as-User-iPad-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2897748d30f63afc9e7cd7319ae38467604617171ebc8ff8f885ec65e66d1b4 +size 154964 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomChangePermissionsScreen.Room-as-User-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomChangePermissionsScreen.Room-as-User-iPad-pseudo.png new file mode 100644 index 000000000..60a9612a5 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomChangePermissionsScreen.Room-as-User-iPad-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b8c95c8b2d15721ab01673783510e6ec06ee119fad5e7a5a2c1336a818e0489 +size 179946 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomChangePermissionsScreen.Room-as-User-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomChangePermissionsScreen.Room-as-User-iPhone-en-GB.png new file mode 100644 index 000000000..2d917858e --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomChangePermissionsScreen.Room-as-User-iPhone-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eabd9c2b8da430c9dbab03a0c085096b40c53e8e96574216463433f0c652df68 +size 99283 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomChangePermissionsScreen.Room-as-User-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomChangePermissionsScreen.Room-as-User-iPhone-pseudo.png new file mode 100644 index 000000000..e42342aa2 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomChangePermissionsScreen.Room-as-User-iPhone-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a4238000b13f41fe0ce60d4cfe5d9b0ea10571e67e147d59f13b235688fe89f +size 135114 diff --git a/UnitTests/Sources/RoomChangePermissionsScreenViewModelTests.swift b/UnitTests/Sources/RoomChangePermissionsScreenViewModelTests.swift index 88b6ee0dc..b5a9b3a4b 100644 --- a/UnitTests/Sources/RoomChangePermissionsScreenViewModelTests.swift +++ b/UnitTests/Sources/RoomChangePermissionsScreenViewModelTests.swift @@ -26,18 +26,44 @@ class RoomChangePermissionsScreenViewModelTests: XCTestCase { XCTFail("There should be a setting for the room avatar.") return } - XCTAssertEqual(context.settings[.roomDetails]?[index].value, .moderator) + XCTAssertEqual(context.settings[.roomDetails]?[index].roleValue, .moderator) XCTAssertFalse(context.viewState.hasChanges) // When updating a setting. - let setting = RoomPermissionsSetting(title: "", value: .user, keyPath: \.roomAvatar) + let setting = RoomPermissionsSetting(title: "", + value: RoomRole.user.powerLevelValue, + ownPowerLevel: RoomRole.creator.powerLevel, + keyPath: \.roomAvatar) + XCTAssertFalse(setting.isDisabled) + XCTAssertEqual(setting.availableValues.map(\.tag), RoomPermissionsSetting.allValues.map(\.tag)) context.settings[.roomDetails]?[index] = setting // Then the setting should update and the changes should be flagged. - XCTAssertEqual(context.settings[.roomDetails]?[index].value, .user) + XCTAssertEqual(context.settings[.roomDetails]?[index].roleValue, .user) XCTAssertTrue(context.viewState.hasChanges) } + func testSettingsCantBeChanged() { + setUp(isSpace: false, ownPowerLevel: .value(25)) + // Given a screen with no changes. + guard let index = context.settings[.roomDetails]?.firstIndex(where: { $0.keyPath == \.roomAvatar }) else { + XCTFail("There should be a setting for the room avatar.") + return + } + XCTAssertEqual(context.settings[.roomDetails]?[index].roleValue, .moderator) + XCTAssertEqual(context.settings[.roomDetails]?[index].isDisabled, true) + XCTAssertEqual(context.settings[.roomDetails]?[index].availableValues.count, 1) + XCTAssertFalse(context.viewState.hasChanges) + + guard let index = context.settings[.messagesAndContent]?.firstIndex(where: { $0.keyPath == \.eventsDefault }) else { + XCTFail("There should be a setting for the events.") + return + } + XCTAssertEqual(context.settings[.messagesAndContent]?[index].roleValue, .user) + XCTAssertEqual(context.settings[.messagesAndContent]?[index].isDisabled, false) + XCTAssertEqual(context.settings[.messagesAndContent]?[index].availableValues.count, 1) + } + func testSave() async throws { setUp(isSpace: false) // Given a screen with changes. @@ -45,8 +71,13 @@ class RoomChangePermissionsScreenViewModelTests: XCTestCase { XCTFail("There should be a setting for the room avatar.") return } - context.settings[.roomDetails]?[index] = RoomPermissionsSetting(title: "", value: .user, keyPath: \.roomAvatar) - XCTAssertEqual(context.settings[.roomDetails]?[index].value, .user) + context.settings[.roomDetails]?[index] = RoomPermissionsSetting(title: "", + value: RoomRole.user.powerLevelValue, + ownPowerLevel: RoomRole.creator.powerLevel, + keyPath: \.roomAvatar) + XCTAssertEqual(context.settings[.roomDetails]?[index].roleValue, .user) + XCTAssertEqual(context.settings[.roomDetails]?[index].isDisabled, false) + XCTAssertEqual(context.settings[.roomDetails]?[index].availableValues.map(\.tag), RoomPermissionsSetting.allValues.map(\.tag)) XCTAssertTrue(context.viewState.hasChanges) XCTAssertEqual(context.settings.count, 3) @@ -89,9 +120,10 @@ class RoomChangePermissionsScreenViewModelTests: XCTestCase { XCTAssertNotNil(context.settings[.manageSpace]) } - private func setUp(isSpace: Bool) { + private func setUp(isSpace: Bool, ownPowerLevel: RoomPowerLevel = RoomRole.creator.powerLevel) { roomProxy = JoinedRoomProxyMock(.init(isSpace: isSpace)) viewModel = RoomChangePermissionsScreenViewModel(currentPermissions: .init(powerLevels: .mock), + ownPowerLevel: ownPowerLevel, roomProxy: roomProxy, userIndicatorController: UserIndicatorControllerMock(), analytics: ServiceLocator.shared.analytics) diff --git a/UnitTests/Sources/RoomPermissionsTests.swift b/UnitTests/Sources/RoomPermissionsTests.swift index de87c56c2..b33816f09 100644 --- a/UnitTests/Sources/RoomPermissionsTests.swift +++ b/UnitTests/Sources/RoomPermissionsTests.swift @@ -30,16 +30,16 @@ class RoomPermissionsTests: XCTestCase { let permissions = RoomPermissions(powerLevels: powerLevels) // Then the permissions should be created with values mapped to the correct role. - XCTAssertEqual(permissions.ban, .administrator) - XCTAssertEqual(permissions.invite, .administrator) - XCTAssertEqual(permissions.kick, .administrator) - XCTAssertEqual(permissions.redact, .moderator) - XCTAssertEqual(permissions.eventsDefault, .moderator) - XCTAssertEqual(permissions.stateDefault, .moderator) - XCTAssertEqual(permissions.usersDefault, .user) - XCTAssertEqual(permissions.roomName, .user) - XCTAssertEqual(permissions.roomAvatar, .user) - XCTAssertEqual(permissions.roomTopic, .user) - XCTAssertEqual(permissions.spaceChild, .administrator) + XCTAssertEqual(permissions.ban, RoomRole.administrator.powerLevelValue) + XCTAssertEqual(permissions.invite, RoomRole.administrator.powerLevelValue) + XCTAssertEqual(permissions.kick, RoomRole.administrator.powerLevelValue) + XCTAssertEqual(permissions.redact, RoomRole.moderator.powerLevelValue) + XCTAssertEqual(permissions.eventsDefault, RoomRole.moderator.powerLevelValue) + XCTAssertEqual(permissions.stateDefault, RoomRole.moderator.powerLevelValue) + XCTAssertEqual(permissions.usersDefault, RoomRole.user.powerLevelValue) + XCTAssertEqual(permissions.roomName, RoomRole.user.powerLevelValue) + XCTAssertEqual(permissions.roomAvatar, RoomRole.user.powerLevelValue) + XCTAssertEqual(permissions.roomTopic, RoomRole.user.powerLevelValue) + XCTAssertEqual(permissions.spaceChild, RoomRole.administrator.powerLevelValue) } }