Add roles and permissions screen (#2505)

The screen hasn't been added to the flows yet.
This commit is contained in:
Doug
2024-02-27 17:06:39 +00:00
committed by GitHub
parent 40a8c80f56
commit a0b510cb6b
12 changed files with 420 additions and 2 deletions
@@ -564,7 +564,7 @@
"screen_room_roles_and_permissions_messages_and_content" = "Messages and content";
"screen_room_roles_and_permissions_moderators" = "Moderators";
"screen_room_roles_and_permissions_permissions_header" = "Permissions";
"screen_room_roles_and_permissions_reset" = "Reset roles and permissions";
"screen_room_roles_and_permissions_reset" = "Reset permissions";
"screen_room_roles_and_permissions_roles_header" = "Roles";
"screen_room_roles_and_permissions_room_details" = "Room details";
"screen_room_roles_and_permissions_title" = "Roles and permissions";
+1 -1
View File
@@ -1385,7 +1385,7 @@ internal enum L10n {
internal static var screenRoomRolesAndPermissionsModerators: String { return L10n.tr("Localizable", "screen_room_roles_and_permissions_moderators") }
/// Permissions
internal static var screenRoomRolesAndPermissionsPermissionsHeader: String { return L10n.tr("Localizable", "screen_room_roles_and_permissions_permissions_header") }
/// Reset roles and permissions
/// Reset permissions
internal static var screenRoomRolesAndPermissionsReset: String { return L10n.tr("Localizable", "screen_room_roles_and_permissions_reset") }
/// Roles
internal static var screenRoomRolesAndPermissionsRolesHeader: String { return L10n.tr("Localizable", "screen_room_roles_and_permissions_roles_header") }
@@ -59,6 +59,19 @@ extension RoomMemberProxyMock {
canInviteUsers: true))
}
static var mockMeAdmin: RoomMemberProxyMock {
RoomMemberProxyMock(with: .init(userID: "@me:matrix.org",
displayName: "Me admin",
avatarURL: URL.picturesDirectory,
membership: .join,
isAccountOwner: true,
powerLevel: 100,
role: .administrator,
canInviteUsers: true,
canKickUsers: true,
canBanUsers: true))
}
static var mockAlice: RoomMemberProxyMock {
RoomMemberProxyMock(with: .init(userID: "@alice:matrix.org",
displayName: "Alice",
@@ -154,4 +167,16 @@ extension Array where Element == RoomMemberProxyMock {
.mockInvited,
.mockIgnored
]
static let allMembersAsAdmin: [RoomMemberProxyMock] = [
.mockMeAdmin,
.mockAlice,
.mockBob,
.mockCharlie,
.mockDan,
.mockInvited,
.mockIgnored,
.mockAdmin,
.mockModerator
]
}
@@ -0,0 +1,62 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// periphery:ignore:all - this is just a roomRolesAndPermissions remove this comment once generating the final file
import Combine
import SwiftUI
struct RoomRolesAndPermissionsScreenCoordinatorParameters {
let roomProxy: RoomProxyProtocol
}
enum RoomRolesAndPermissionsScreenCoordinatorAction {
case editRoles(RoomRolesAndPermissionsScreenRole)
case editPermissions(RoomRolesAndPermissionsScreenPermissionsGroup)
}
final class RoomRolesAndPermissionsScreenCoordinator: CoordinatorProtocol {
private var viewModel: RoomRolesAndPermissionsScreenViewModelProtocol
private let actionsSubject: PassthroughSubject<RoomRolesAndPermissionsScreenCoordinatorAction, Never> = .init()
private var cancellables = Set<AnyCancellable>()
var actions: AnyPublisher<RoomRolesAndPermissionsScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: RoomRolesAndPermissionsScreenCoordinatorParameters) {
viewModel = RoomRolesAndPermissionsScreenViewModel(roomProxy: parameters.roomProxy)
}
func start() {
viewModel.actions.sink { [weak self] action in
MXLog.info("Coordinator: received view model action: \(action)")
guard let self else { return }
switch action {
case .editRoles(let role):
actionsSubject.send(.editRoles(role))
case .editPermissions(let permissionsGroup):
actionsSubject.send(.editPermissions(permissionsGroup))
}
}
.store(in: &cancellables)
}
func toPresentable() -> AnyView {
AnyView(RoomRolesAndPermissionsScreen(context: viewModel.context))
}
}
@@ -0,0 +1,44 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
enum RoomRolesAndPermissionsScreenViewModelAction {
case editRoles(RoomRolesAndPermissionsScreenRole)
case editPermissions(RoomRolesAndPermissionsScreenPermissionsGroup)
}
struct RoomRolesAndPermissionsScreenViewState: BindableState {
var administratorCount: Int?
var moderatorCount: Int?
}
enum RoomRolesAndPermissionsScreenViewAction {
case editRoles(RoomRolesAndPermissionsScreenRole)
case editPermissions(RoomRolesAndPermissionsScreenPermissionsGroup)
case reset
}
enum RoomRolesAndPermissionsScreenRole {
case administrators
case moderators
}
enum RoomRolesAndPermissionsScreenPermissionsGroup {
case roomDetails
case messagesAndContent
case memberModeration
}
@@ -0,0 +1,63 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
import SwiftUI
typealias RoomRolesAndPermissionsScreenViewModelType = StateStoreViewModel<RoomRolesAndPermissionsScreenViewState, RoomRolesAndPermissionsScreenViewAction>
class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewModelType, RoomRolesAndPermissionsScreenViewModelProtocol {
private let roomProxy: RoomProxyProtocol
private var actionsSubject: PassthroughSubject<RoomRolesAndPermissionsScreenViewModelAction, Never> = .init()
var actions: AnyPublisher<RoomRolesAndPermissionsScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(roomProxy: RoomProxyProtocol) {
self.roomProxy = roomProxy
super.init(initialViewState: RoomRolesAndPermissionsScreenViewState())
roomProxy.members.sink { [weak self] members in
self?.updateMembers(members)
}
.store(in: &cancellables)
updateMembers(roomProxy.members.value)
}
// MARK: - Public
override func process(viewAction: RoomRolesAndPermissionsScreenViewAction) {
MXLog.info("View model: received view action: \(viewAction)")
switch viewAction {
case .editRoles(let role):
actionsSubject.send(.editRoles(role))
case .editPermissions(let permissionsGroup):
actionsSubject.send(.editPermissions(permissionsGroup))
case .reset:
break
}
}
// MARK: - Members
private func updateMembers(_ members: [RoomMemberProxyProtocol]) {
state.administratorCount = members.filter { $0.role == .administrator }.count
state.moderatorCount = members.filter { $0.role == .moderator }.count
}
}
@@ -0,0 +1,23 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
@MainActor
protocol RoomRolesAndPermissionsScreenViewModelProtocol {
var actions: AnyPublisher<RoomRolesAndPermissionsScreenViewModelAction, Never> { get }
var context: RoomRolesAndPermissionsScreenViewModelType.Context { get }
}
@@ -0,0 +1,117 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Compound
import SwiftUI
struct RoomRolesAndPermissionsScreen: View {
@ObservedObject var context: RoomRolesAndPermissionsScreenViewModel.Context
var body: some View {
Form {
rolesSection
permissionsSection
resetSection
}
.compoundList()
.navigationTitle(L10n.screenRoomRolesAndPermissionsTitle)
.navigationBarTitleDisplayMode(.inline)
}
private var rolesSection: some View {
Section {
ListRow(label: .default(title: L10n.screenRoomRolesAndPermissionsAdmins,
icon: \.admin),
details: administratorDetails,
kind: .navigationLink {
context.send(viewAction: .editRoles(.administrators))
})
ListRow(label: .default(title: L10n.screenRoomRolesAndPermissionsModerators,
icon: \.chatProblem),
details: moderatorDetails,
kind: .navigationLink {
context.send(viewAction: .editRoles(.moderators))
})
} header: {
Text(L10n.screenRoomRolesAndPermissionsRolesHeader)
.compoundListSectionHeader()
}
}
private var administratorDetails: ListRowDetails<Image> {
if let administratorCount = context.viewState.administratorCount {
.title("\(administratorCount)")
} else {
.isWaiting(true)
}
}
private var moderatorDetails: ListRowDetails<Image> {
if let moderatorCount = context.viewState.moderatorCount {
.title("\(moderatorCount)")
} else {
.isWaiting(true)
}
}
private var permissionsSection: some View {
Section {
ListRow(label: .default(title: L10n.screenRoomRolesAndPermissionsRoomDetails,
icon: \.info),
kind: .navigationLink {
context.send(viewAction: .editPermissions(.roomDetails))
})
ListRow(label: .default(title: L10n.screenRoomRolesAndPermissionsMessagesAndContent,
icon: \.chat),
kind: .navigationLink {
context.send(viewAction: .editPermissions(.messagesAndContent))
})
ListRow(label: .default(title: L10n.screenRoomRolesAndPermissionsMemberModeration,
icon: \.user),
kind: .navigationLink {
context.send(viewAction: .editPermissions(.memberModeration))
})
} header: {
Text(L10n.screenRoomRolesAndPermissionsPermissionsHeader)
.compoundListSectionHeader()
}
}
private var resetSection: some View {
Section {
ListRow(label: .plain(title: L10n.screenRoomRolesAndPermissionsReset,
role: .destructive),
kind: .button {
context.send(viewAction: .reset)
})
}
}
}
// MARK: - Previews
struct RoomRolesAndPermissionsScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = RoomRolesAndPermissionsScreenViewModel(roomProxy: RoomProxyMock(with: .init(members: .allMembersAsAdmin)))
static var previews: some View {
NavigationStack {
RoomRolesAndPermissionsScreen(context: viewModel.context)
}
}
}