implemented a way to filter out roles that can't be assigned to permission, and disable permissions that are above the current power level of the user.

This commit is contained in:
Mauro Romito
2025-12-18 18:43:19 +01:00
committed by Mauro
parent f2fc164e2b
commit 88cd816392
18 changed files with 197 additions and 90 deletions
@@ -119,10 +119,10 @@ class RoomRolesAndPermissionsFlowCoordinator: FlowCoordinatorProtocol {
stateMachine.addRoutes(event: .finishedChangingRoles, transitions: [.changingRoles => .rolesAndPermissionsScreen]) stateMachine.addRoutes(event: .finishedChangingRoles, transitions: [.changingRoles => .rolesAndPermissionsScreen])
stateMachine.addRoutes(event: .changePermissions, transitions: [.rolesAndPermissionsScreen => .changingPermissions]) { [weak self] context in 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") 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]) stateMachine.addRoutes(event: .finishedChangingPermissions, transitions: [.changingPermissions => .rolesAndPermissionsScreen])
@@ -144,8 +144,8 @@ class RoomRolesAndPermissionsFlowCoordinator: FlowCoordinatorProtocol {
switch action { switch action {
case .editRoles(let role): case .editRoles(let role):
stateMachine.tryEvent(.changeRoles, userInfo: role) stateMachine.tryEvent(.changeRoles, userInfo: role)
case .editPermissions(let permissions): case .editPermissions(let ownPowerLevel, let permissions):
stateMachine.tryEvent(.changePermissions, userInfo: permissions) stateMachine.tryEvent(.changePermissions, userInfo: (ownPowerLevel, permissions))
case .demotedOwnUser: case .demotedOwnUser:
stateMachine.tryEvent(.demotedOwnUser) stateMachine.tryEvent(.demotedOwnUser)
} }
@@ -179,8 +179,9 @@ class RoomRolesAndPermissionsFlowCoordinator: FlowCoordinatorProtocol {
} }
} }
private func presentChangePermissionsScreen(permissions: RoomPermissions) { private func presentChangePermissionsScreen(ownPowerLevel: RoomPowerLevel, permissions: RoomPermissions) {
let parameters = RoomChangePermissionsScreenCoordinatorParameters(permissions: permissions, let parameters = RoomChangePermissionsScreenCoordinatorParameters(ownPowerLevel: ownPowerLevel,
permissions: permissions,
roomProxy: roomProxy, roomProxy: roomProxy,
userIndicatorController: userIndicatorController, userIndicatorController: userIndicatorController,
analytics: analytics) analytics: analytics)
@@ -10,6 +10,7 @@ import Combine
import SwiftUI import SwiftUI
struct RoomChangePermissionsScreenCoordinatorParameters { struct RoomChangePermissionsScreenCoordinatorParameters {
let ownPowerLevel: RoomPowerLevel
let permissions: RoomPermissions let permissions: RoomPermissions
let roomProxy: JoinedRoomProxyProtocol let roomProxy: JoinedRoomProxyProtocol
let userIndicatorController: UserIndicatorControllerProtocol let userIndicatorController: UserIndicatorControllerProtocol
@@ -21,7 +22,6 @@ enum RoomChangePermissionsScreenCoordinatorAction {
} }
final class RoomChangePermissionsScreenCoordinator: CoordinatorProtocol { final class RoomChangePermissionsScreenCoordinator: CoordinatorProtocol {
private let parameters: RoomChangePermissionsScreenCoordinatorParameters
private var viewModel: RoomChangePermissionsScreenViewModelProtocol private var viewModel: RoomChangePermissionsScreenViewModelProtocol
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
@@ -31,9 +31,8 @@ final class RoomChangePermissionsScreenCoordinator: CoordinatorProtocol {
} }
init(parameters: RoomChangePermissionsScreenCoordinatorParameters) { init(parameters: RoomChangePermissionsScreenCoordinatorParameters) {
self.parameters = parameters
viewModel = RoomChangePermissionsScreenViewModel(currentPermissions: parameters.permissions, viewModel = RoomChangePermissionsScreenViewModel(currentPermissions: parameters.permissions,
ownPowerLevel: parameters.ownPowerLevel,
roomProxy: parameters.roomProxy, roomProxy: parameters.roomProxy,
userIndicatorController: parameters.userIndicatorController, userIndicatorController: parameters.userIndicatorController,
analytics: parameters.analytics) analytics: parameters.analytics)
@@ -69,7 +69,7 @@ extension RoomChangePermissionsScreenViewState {
/// - Parameters: /// - Parameters:
/// - currentPermissions: The current permissions for the room. /// - currentPermissions: The current permissions for the room.
/// - isSpace: if the room is a space or a normal 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]]() var settings = [RoomChangePermissionsScreenGroup: [RoomPermissionsSetting]]()
for group in RoomChangePermissionsScreenGroup.allCases { for group in RoomChangePermissionsScreenGroup.allCases {
switch group { switch group {
@@ -77,12 +77,15 @@ extension RoomChangePermissionsScreenViewState {
settings[group] = [ settings[group] = [
RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsRoomName, RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsRoomName,
value: currentPermissions.roomName, value: currentPermissions.roomName,
ownPowerLevel: ownPowerLevel,
keyPath: \.roomName), keyPath: \.roomName),
RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsRoomAvatar, RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsRoomAvatar,
value: currentPermissions.roomAvatar, value: currentPermissions.roomAvatar,
ownPowerLevel: ownPowerLevel,
keyPath: \.roomAvatar), keyPath: \.roomAvatar),
RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsRoomTopic, RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsRoomTopic,
value: currentPermissions.roomTopic, value: currentPermissions.roomTopic,
ownPowerLevel: ownPowerLevel,
keyPath: \.roomTopic) keyPath: \.roomTopic)
] ]
@@ -90,12 +93,15 @@ extension RoomChangePermissionsScreenViewState {
settings[group] = [ settings[group] = [
RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsInvitePeople, RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsInvitePeople,
value: currentPermissions.invite, value: currentPermissions.invite,
ownPowerLevel: ownPowerLevel,
keyPath: \.invite), keyPath: \.invite),
RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsRemovePeople, RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsRemovePeople,
value: currentPermissions.kick, value: currentPermissions.kick,
ownPowerLevel: ownPowerLevel,
keyPath: \.kick), keyPath: \.kick),
RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsBanPeople, RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsBanPeople,
value: currentPermissions.ban, value: currentPermissions.ban,
ownPowerLevel: ownPowerLevel,
keyPath: \.ban) keyPath: \.ban)
] ]
case .messagesAndContent: case .messagesAndContent:
@@ -103,9 +109,11 @@ extension RoomChangePermissionsScreenViewState {
settings[group] = [ settings[group] = [
RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsSendMessages, RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsSendMessages,
value: currentPermissions.eventsDefault, value: currentPermissions.eventsDefault,
ownPowerLevel: ownPowerLevel,
keyPath: \.eventsDefault), keyPath: \.eventsDefault),
RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsDeleteMessages, RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsDeleteMessages,
value: currentPermissions.redact, value: currentPermissions.redact,
ownPowerLevel: ownPowerLevel,
keyPath: \.redact) keyPath: \.redact)
] ]
} }
@@ -114,6 +122,7 @@ extension RoomChangePermissionsScreenViewState {
settings[group] = [ settings[group] = [
RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsManageSpaceRooms, RoomPermissionsSetting(title: L10n.screenRoomChangePermissionsManageSpaceRooms,
value: currentPermissions.spaceChild, value: currentPermissions.spaceChild,
ownPowerLevel: ownPowerLevel,
keyPath: \.spaceChild) keyPath: \.spaceChild)
] ]
} }
@@ -23,13 +23,16 @@ class RoomChangePermissionsScreenViewModel: RoomChangePermissionsScreenViewModel
} }
init(currentPermissions: RoomPermissions, init(currentPermissions: RoomPermissions,
ownPowerLevel: RoomPowerLevel,
roomProxy: JoinedRoomProxyProtocol, roomProxy: JoinedRoomProxyProtocol,
userIndicatorController: UserIndicatorControllerProtocol, userIndicatorController: UserIndicatorControllerProtocol,
analytics: AnalyticsService) { analytics: AnalyticsService) {
self.roomProxy = roomProxy self.roomProxy = roomProxy
self.userIndicatorController = userIndicatorController self.userIndicatorController = userIndicatorController
self.analytics = analytics 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 // MARK: - Public
@@ -64,7 +67,7 @@ class RoomChangePermissionsScreenViewModel: RoomChangePermissionsScreenViewModel
.flatMap { $0 } .flatMap { $0 }
.filter { state.currentPermissions[keyPath: $0.keyPath] != $0.value } .filter { state.currentPermissions[keyPath: $0.keyPath] != $0.value }
for setting in changedSettings { for setting in changedSettings {
changes[keyPath: setting.rustKeyPath] = setting.value.powerLevelValue changes[keyPath: setting.rustKeyPath] = setting.value
} }
switch await roomProxy.applyPowerLevelChanges(changes) { switch await roomProxy.applyPowerLevelChanges(changes) {
@@ -106,14 +109,14 @@ class RoomChangePermissionsScreenViewModel: RoomChangePermissionsScreenViewModel
private func trackChanges(_ settings: [RoomPermissionsSetting]) { private func trackChanges(_ settings: [RoomPermissionsSetting]) {
for setting in settings { for setting in settings {
switch setting.keyPath { switch setting.keyPath {
case \.ban: analytics.trackRoomModeration(action: .ChangePermissionsBanMembers, role: setting.value) case \.ban: analytics.trackRoomModeration(action: .ChangePermissionsBanMembers, role: setting.roleValue)
case \.invite: analytics.trackRoomModeration(action: .ChangePermissionsInviteUsers, role: setting.value) case \.invite: analytics.trackRoomModeration(action: .ChangePermissionsInviteUsers, role: setting.roleValue)
case \.kick: analytics.trackRoomModeration(action: .ChangePermissionsKickMembers, role: setting.value) case \.kick: analytics.trackRoomModeration(action: .ChangePermissionsKickMembers, role: setting.roleValue)
case \.redact: analytics.trackRoomModeration(action: .ChangePermissionsRedactMessages, role: setting.value) case \.redact: analytics.trackRoomModeration(action: .ChangePermissionsRedactMessages, role: setting.roleValue)
case \.eventsDefault: analytics.trackRoomModeration(action: .ChangePermissionsSendMessages, role: setting.value) case \.eventsDefault: analytics.trackRoomModeration(action: .ChangePermissionsSendMessages, role: setting.roleValue)
case \.roomName: analytics.trackRoomModeration(action: .ChangePermissionsRoomName, role: setting.value) case \.roomName: analytics.trackRoomModeration(action: .ChangePermissionsRoomName, role: setting.roleValue)
case \.roomAvatar: analytics.trackRoomModeration(action: .ChangePermissionsRoomAvatar, role: setting.value) case \.roomAvatar: analytics.trackRoomModeration(action: .ChangePermissionsRoomAvatar, role: setting.roleValue)
case \.roomTopic: analytics.trackRoomModeration(action: .ChangePermissionsRoomTopic, role: setting.value) case \.roomTopic: analytics.trackRoomModeration(action: .ChangePermissionsRoomTopic, role: setting.roleValue)
default: MXLog.warning("Unexpected change: \(setting.keyPath).") default: MXLog.warning("Unexpected change: \(setting.keyPath).")
} }
} }
@@ -32,7 +32,9 @@ struct RoomChangePermissionsScreen: View {
Section { Section {
ForEach(settings) { $setting in ForEach(settings) { $setting in
ListRow(label: .plain(title: setting.title), 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: { } header: {
Text(group.name) Text(group.name)
@@ -64,6 +66,7 @@ struct RoomChangePermissionsScreen: View {
struct RoomChangePermissionsScreen_Previews: PreviewProvider, TestablePreview { struct RoomChangePermissionsScreen_Previews: PreviewProvider, TestablePreview {
static let roomViewModel = makeViewModel(isSpace: false) static let roomViewModel = makeViewModel(isSpace: false)
static let roomAsUserViewModel = makeViewModel(isSpace: false, ownPowerLevel: RoomRole.user.powerLevel)
static let spaceViewModel = makeViewModel(isSpace: true) static let spaceViewModel = makeViewModel(isSpace: true)
static var previews: some View { static var previews: some View {
@@ -72,14 +75,20 @@ struct RoomChangePermissionsScreen_Previews: PreviewProvider, TestablePreview {
} }
.previewDisplayName("Room") .previewDisplayName("Room")
NavigationStack {
RoomChangePermissionsScreen(context: roomAsUserViewModel.context)
}
.previewDisplayName("Room as User")
NavigationStack { NavigationStack {
RoomChangePermissionsScreen(context: spaceViewModel.context) RoomChangePermissionsScreen(context: spaceViewModel.context)
} }
.previewDisplayName("Space") .previewDisplayName("Space")
} }
static func makeViewModel(isSpace: Bool) -> RoomChangePermissionsScreenViewModel { static func makeViewModel(isSpace: Bool, ownPowerLevel: RoomPowerLevel = RoomRole.creator.powerLevel) -> RoomChangePermissionsScreenViewModel {
RoomChangePermissionsScreenViewModel(currentPermissions: .init(powerLevels: .mock), RoomChangePermissionsScreenViewModel(currentPermissions: .init(powerLevels: .mock),
ownPowerLevel: ownPowerLevel,
roomProxy: JoinedRoomProxyMock(.init(isSpace: isSpace)), roomProxy: JoinedRoomProxyMock(.init(isSpace: isSpace)),
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
analytics: ServiceLocator.shared.analytics) analytics: ServiceLocator.shared.analytics)
@@ -17,7 +17,7 @@ struct RoomRolesAndPermissionsScreenCoordinatorParameters {
enum RoomRolesAndPermissionsScreenCoordinatorAction { enum RoomRolesAndPermissionsScreenCoordinatorAction {
case editRoles(RoomRolesAndPermissionsScreenRole) case editRoles(RoomRolesAndPermissionsScreenRole)
case editPermissions(permissions: RoomPermissions) case editPermissions(ownPowerLevel: RoomPowerLevel, permissions: RoomPermissions)
case demotedOwnUser case demotedOwnUser
} }
@@ -44,8 +44,8 @@ final class RoomRolesAndPermissionsScreenCoordinator: CoordinatorProtocol {
switch action { switch action {
case .editRoles(let role): case .editRoles(let role):
actionsSubject.send(.editRoles(role)) actionsSubject.send(.editRoles(role))
case .editPermissions(let permissions): case .editPermissions(let ownPowerLevel, let permissions):
actionsSubject.send(.editPermissions(permissions: permissions)) actionsSubject.send(.editPermissions(ownPowerLevel: ownPowerLevel, permissions: permissions))
case .demotedOwnUser: case .demotedOwnUser:
actionsSubject.send(.demotedOwnUser) actionsSubject.send(.demotedOwnUser)
} }
@@ -12,13 +12,13 @@ enum RoomRolesAndPermissionsScreenViewModelAction {
/// The user would like to edit member roles. /// The user would like to edit member roles.
case editRoles(RoomRolesAndPermissionsScreenRole) case editRoles(RoomRolesAndPermissionsScreenRole)
/// The user would like to edit room permissions. /// The user would like to edit room permissions.
case editPermissions(permissions: RoomPermissions) case editPermissions(ownPowerLevel: RoomPowerLevel, permissions: RoomPermissions)
/// The user has demoted themself. /// The user has demoted themself.
case demotedOwnUser case demotedOwnUser
} }
struct RoomRolesAndPermissionsScreenViewState: BindableState { struct RoomRolesAndPermissionsScreenViewState: BindableState {
var ownRole: RoomRole var ownPowerLevel: RoomPowerLevel
var administratorsAndOwnersCount: Int? var administratorsAndOwnersCount: Int?
/// The number of administrators in the room. /// The number of administrators in the room.
@@ -26,7 +26,7 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM
self.roomProxy = roomProxy self.roomProxy = roomProxy
self.userIndicatorController = userIndicatorController self.userIndicatorController = userIndicatorController
self.analytics = analytics 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)) permissions: initialPermissions))
// Automatically update the admin/moderator counts. // Automatically update the admin/moderator counts.
@@ -91,7 +91,7 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM
state.administratorCount = members.filter { $0.role == .administrator && $0.isActive }.count state.administratorCount = members.filter { $0.role == .administrator && $0.isActive }.count
state.moderatorCount = members.filter { $0.role == .moderator && $0.isActive }.count state.moderatorCount = members.filter { $0.role == .moderator && $0.isActive }.count
if let ownUser = members.first(where: { $0.userID == roomProxy.ownUserID }) { 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.") MXLog.error("Missing permissions.")
return return
} }
actionsSubject.send(.editPermissions(permissions: permissions)) actionsSubject.send(.editPermissions(ownPowerLevel: state.ownPowerLevel, permissions: permissions))
} }
private func resetPermissions() async { private func resetPermissions() async {
@@ -27,7 +27,7 @@ struct RoomRolesAndPermissionsScreen: View {
private var rolesSection: some View { private var rolesSection: some View {
Section { Section {
if context.viewState.ownRole == .creator { if context.viewState.ownPowerLevel.role == .creator {
ListRow(label: .default(title: L10n.screenRoomRolesAndPermissionsAdminsAndOwners, ListRow(label: .default(title: L10n.screenRoomRolesAndPermissionsAdminsAndOwners,
icon: \.admin), icon: \.admin),
details: administratorOrOwnersDetails, details: administratorOrOwnersDetails,
@@ -53,7 +53,7 @@ struct RoomRolesAndPermissionsScreen: View {
}) })
.accessibilityIdentifier(A11yIdentifiers.roomRolesAndPermissionsScreen.moderators) .accessibilityIdentifier(A11yIdentifiers.roomRolesAndPermissionsScreen.moderators)
if context.viewState.ownRole != .creator { if context.viewState.ownPowerLevel.role != .creator {
ListRow(label: .default(title: L10n.screenRoomRolesAndPermissionsChangeMyRole, ListRow(label: .default(title: L10n.screenRoomRolesAndPermissionsChangeMyRole,
icon: \.edit), icon: \.edit),
kind: .button { kind: .button {
@@ -10,24 +10,29 @@ import Foundation
import MatrixRustSDK import MatrixRustSDK
struct RoomPermissionsSetting: Identifiable { struct RoomPermissionsSetting: Identifiable {
var id: KeyPath<RoomPermissions, RoomRole> { 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<RoomPermissions, Int64> { keyPath }
/// The title of this setting. /// The title of this setting.
let title: String let title: String
/// The selected role of this setting. /// The selected role of this setting.
var value: RoomRole var value: Int64
/// All of the available roles that this setting can be configured with.
var allValues: [(title: String, tag: RoomRole)] { let ownPowerLevel: RoomPowerLevel
[
(title: L10n.screenRoomChangePermissionsAdministrators, tag: .administrator), var roleValue: RoomRole {
(title: L10n.screenRoomChangePermissionsModerators, tag: .moderator), get {
(title: L10n.screenRoomChangePermissionsEveryone, tag: .user) RoomRole(powerLevelValue: value)
] } set {
value = newValue.powerLevelValue
}
} }
/// The `RoomPermissions` property that this setting is for. /// The `RoomPermissions` property that this setting is for.
let keyPath: KeyPath<RoomPermissions, RoomRole> let keyPath: KeyPath<RoomPermissions, Int64>
/// The `RoomPowerLevelChanges` property that this setting is saved into. /// The `RoomPowerLevelChanges` property that this setting is saved into.
var rustKeyPath: WritableKeyPath<RoomPowerLevelChanges, Int64?> { var rustKeyPath: WritableKeyPath<RoomPowerLevelChanges, Int64?> {
switch keyPath { switch keyPath {
@@ -45,46 +50,75 @@ struct RoomPermissionsSetting: Identifiable {
default: fatalError("Unexpected key path: \(keyPath)") 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<RoomPermissions, Int64>) {
self.ownPowerLevel = ownPowerLevel
self.title = title
self.value = value
self.keyPath = keyPath
}
} }
struct RoomPermissions { struct RoomPermissions {
/// The level required to ban a user. /// The level required to ban a user.
var ban: RoomRole var ban: Int64
/// The level required to invite a user. /// The level required to invite a user.
var invite: RoomRole var invite: Int64
/// The level required to kick a user. /// The level required to kick a user.
var kick: RoomRole var kick: Int64
/// The level required to redact an event. /// The level required to redact an event.
var redact: RoomRole var redact: Int64
/// The default level required to send message events. /// The default level required to send message events.
var eventsDefault: RoomRole var eventsDefault: Int64
/// The default level required to send state events. /// The default level required to send state events.
var stateDefault: RoomRole var stateDefault: Int64
/// The default power level for every user in the room. /// 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. /// The level required to change the room's name.
var roomName: RoomRole var roomName: Int64
/// The level required to change the room's avatar. /// The level required to change the room's avatar.
var roomAvatar: RoomRole var roomAvatar: Int64
/// The level required to change the room's topic. /// 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. /// The level required to add/remove childrens from a space.
var spaceChild: RoomRole var spaceChild: Int64
} }
extension RoomPermissions { extension RoomPermissions {
/// Create permissions from the room's power levels. /// Create permissions from the room's power levels.
init(powerLevels: RoomPowerLevelsValues) { init(powerLevels: RoomPowerLevelsValues) {
ban = RoomRole(powerLevelValue: powerLevels.ban) ban = powerLevels.ban
invite = RoomRole(powerLevelValue: powerLevels.invite) invite = powerLevels.invite
kick = RoomRole(powerLevelValue: powerLevels.kick) kick = powerLevels.kick
redact = RoomRole(powerLevelValue: powerLevels.redact) redact = powerLevels.redact
eventsDefault = RoomRole(powerLevelValue: powerLevels.eventsDefault) eventsDefault = powerLevels.eventsDefault
stateDefault = RoomRole(powerLevelValue: powerLevels.stateDefault) stateDefault = powerLevels.stateDefault
usersDefault = RoomRole(powerLevelValue: powerLevels.usersDefault) usersDefault = powerLevels.usersDefault
roomName = RoomRole(powerLevelValue: powerLevels.roomName) roomName = powerLevels.roomName
roomAvatar = RoomRole(powerLevelValue: powerLevels.roomAvatar) roomAvatar = powerLevels.roomAvatar
roomTopic = RoomRole(powerLevelValue: powerLevels.roomTopic) roomTopic = powerLevels.roomTopic
spaceChild = RoomRole(powerLevelValue: powerLevels.spaceChild) spaceChild = powerLevels.spaceChild
} }
} }
@@ -30,6 +30,10 @@ enum RoomPowerLevel: Hashable, Comparable {
case .value(let value): .value(value: Int64(value)) case .value(let value): .value(value: Int64(value))
} }
} }
var role: RoomRole {
RoomRole(powerLevel: self)
}
} }
extension PowerLevel { extension PowerLevel {
+13 -9
View File
@@ -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 /// 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. /// NOTE: Do not use for comparison, use the true power level instead.
var powerLevelValue: Int64 { var powerLevelValue: Int64 {
guard self != .owner else { switch powerLevel {
// Would be better if the SDK would return this, maybe a `suggestedPowerLevelValueForRole` function would solve the problem
return 150
}
do {
switch try suggestedPowerLevelForRole(role: rustRole) {
case .infinite: case .infinite:
fatalError("Impossible") fatalError("Impossible")
case .value(let value): case .value(let value):
return 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 .value(150)
}
do {
return try RoomPowerLevel(rustPowerLevel: suggestedPowerLevelForRole(role: rustRole))
} catch { } catch {
MXLog.error("Falied to convert role to power level value: \(error)") MXLog.error("Falied to convert role to power level value: \(error)")
return 0 return .value(0)
} }
} }
} }
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f2897748d30f63afc9e7cd7319ae38467604617171ebc8ff8f885ec65e66d1b4
size 154964
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1b8c95c8b2d15721ab01673783510e6ec06ee119fad5e7a5a2c1336a818e0489
size 179946
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:eabd9c2b8da430c9dbab03a0c085096b40c53e8e96574216463433f0c652df68
size 99283
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3a4238000b13f41fe0ce60d4cfe5d9b0ea10571e67e147d59f13b235688fe89f
size 135114
@@ -26,18 +26,44 @@ class RoomChangePermissionsScreenViewModelTests: XCTestCase {
XCTFail("There should be a setting for the room avatar.") XCTFail("There should be a setting for the room avatar.")
return return
} }
XCTAssertEqual(context.settings[.roomDetails]?[index].value, .moderator) XCTAssertEqual(context.settings[.roomDetails]?[index].roleValue, .moderator)
XCTAssertFalse(context.viewState.hasChanges) XCTAssertFalse(context.viewState.hasChanges)
// When updating a setting. // 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 context.settings[.roomDetails]?[index] = setting
// Then the setting should update and the changes should be flagged. // 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) 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 { func testSave() async throws {
setUp(isSpace: false) setUp(isSpace: false)
// Given a screen with changes. // Given a screen with changes.
@@ -45,8 +71,13 @@ class RoomChangePermissionsScreenViewModelTests: XCTestCase {
XCTFail("There should be a setting for the room avatar.") XCTFail("There should be a setting for the room avatar.")
return return
} }
context.settings[.roomDetails]?[index] = RoomPermissionsSetting(title: "", value: .user, keyPath: \.roomAvatar) context.settings[.roomDetails]?[index] = RoomPermissionsSetting(title: "",
XCTAssertEqual(context.settings[.roomDetails]?[index].value, .user) 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) XCTAssertTrue(context.viewState.hasChanges)
XCTAssertEqual(context.settings.count, 3) XCTAssertEqual(context.settings.count, 3)
@@ -89,9 +120,10 @@ class RoomChangePermissionsScreenViewModelTests: XCTestCase {
XCTAssertNotNil(context.settings[.manageSpace]) XCTAssertNotNil(context.settings[.manageSpace])
} }
private func setUp(isSpace: Bool) { private func setUp(isSpace: Bool, ownPowerLevel: RoomPowerLevel = RoomRole.creator.powerLevel) {
roomProxy = JoinedRoomProxyMock(.init(isSpace: isSpace)) roomProxy = JoinedRoomProxyMock(.init(isSpace: isSpace))
viewModel = RoomChangePermissionsScreenViewModel(currentPermissions: .init(powerLevels: .mock), viewModel = RoomChangePermissionsScreenViewModel(currentPermissions: .init(powerLevels: .mock),
ownPowerLevel: ownPowerLevel,
roomProxy: roomProxy, roomProxy: roomProxy,
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
analytics: ServiceLocator.shared.analytics) analytics: ServiceLocator.shared.analytics)
+11 -11
View File
@@ -30,16 +30,16 @@ class RoomPermissionsTests: XCTestCase {
let permissions = RoomPermissions(powerLevels: powerLevels) let permissions = RoomPermissions(powerLevels: powerLevels)
// Then the permissions should be created with values mapped to the correct role. // Then the permissions should be created with values mapped to the correct role.
XCTAssertEqual(permissions.ban, .administrator) XCTAssertEqual(permissions.ban, RoomRole.administrator.powerLevelValue)
XCTAssertEqual(permissions.invite, .administrator) XCTAssertEqual(permissions.invite, RoomRole.administrator.powerLevelValue)
XCTAssertEqual(permissions.kick, .administrator) XCTAssertEqual(permissions.kick, RoomRole.administrator.powerLevelValue)
XCTAssertEqual(permissions.redact, .moderator) XCTAssertEqual(permissions.redact, RoomRole.moderator.powerLevelValue)
XCTAssertEqual(permissions.eventsDefault, .moderator) XCTAssertEqual(permissions.eventsDefault, RoomRole.moderator.powerLevelValue)
XCTAssertEqual(permissions.stateDefault, .moderator) XCTAssertEqual(permissions.stateDefault, RoomRole.moderator.powerLevelValue)
XCTAssertEqual(permissions.usersDefault, .user) XCTAssertEqual(permissions.usersDefault, RoomRole.user.powerLevelValue)
XCTAssertEqual(permissions.roomName, .user) XCTAssertEqual(permissions.roomName, RoomRole.user.powerLevelValue)
XCTAssertEqual(permissions.roomAvatar, .user) XCTAssertEqual(permissions.roomAvatar, RoomRole.user.powerLevelValue)
XCTAssertEqual(permissions.roomTopic, .user) XCTAssertEqual(permissions.roomTopic, RoomRole.user.powerLevelValue)
XCTAssertEqual(permissions.spaceChild, .administrator) XCTAssertEqual(permissions.spaceChild, RoomRole.administrator.powerLevelValue)
} }
} }