Skip to content

Commit

Permalink
Add roles and permissions screen
Browse files Browse the repository at this point in the history
The screen hasn't been added to the flows yet.
  • Loading branch information
pixlwave committed Feb 27, 2024
1 parent fdbaef5 commit 48dcc52
Show file tree
Hide file tree
Showing 11 changed files with 444 additions and 2 deletions.
65 changes: 65 additions & 0 deletions ElementX.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
2 changes: 1 addition & 1 deletion ElementX/Sources/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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") }
Expand Down
25 changes: 25 additions & 0 deletions ElementX/Sources/Mocks/RoomMemberProxyMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -154,4 +167,16 @@ extension Array where Element == RoomMemberProxyMock {
.mockInvited,
.mockIgnored
]

static let allMembersAsAdmin: [RoomMemberProxyMock] = [
.mockMeAdmin,
.mockAlice,
.mockBob,
.mockCharlie,
.mockDan,
.mockInvited,
.mockIgnored,
.mockAdmin,
.mockModerator
]
}
Original file line number Diff line number Diff line change
@@ -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))
}
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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 }
}
Original file line number Diff line number Diff line change
@@ -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)
}

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()
}
}

var administratorDetails: ListRowDetails<Image> {
if let administratorCount = context.viewState.administratorCount {
.title("\(administratorCount)")
} else {
.isWaiting(true)
}
}

var moderatorDetails: ListRowDetails<Image> {
if let moderatorCount = context.viewState.moderatorCount {
.title("\(moderatorCount)")
} else {
.isWaiting(true)
}
}

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()
}
}

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)
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 48dcc52

Please sign in to comment.