Moderation tweaks (#2548)

* Only allow admins to see the roles and permissions screen.

* Hide the selection checkbox on Admins when changing roles.

* Show an empty state for banned users.

* Add separate actions for ban and remove.

* Implement reset permissions and demote self alerts.

* Add tests for resetting permissions and demoting self.

* Add a warning when promoting someone to administrator.
This commit is contained in:
Doug
2024-03-11 14:19:53 +00:00
committed by GitHub
parent 9ce12e6e16
commit 71721f01b1
37 changed files with 411 additions and 104 deletions
@@ -114,10 +114,6 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
self.state.canKickUsers = await canKickUsers == .success(true)
self.state.canBanUsers = await canBanUsers == .success(true)
if state.bindings.mode == .banned, roomMembersDetails.bannedMembers.isEmpty {
state.bindings.mode = .members
}
hideLoader()
}
}
@@ -19,9 +19,9 @@ import SwiftUI
struct RoomMembersListManageMemberSheet: View {
let member: RoomMemberDetails
let context: RoomMembersListScreenViewModel.Context
@ObservedObject var context: RoomMembersListScreenViewModel.Context
@State private var isPresentingRemoveConfirmation = false
@State private var isPresentingBanConfirmation = false
var body: some View {
Form {
@@ -33,25 +33,25 @@ struct RoomMembersListManageMemberSheet: View {
Section {
ListRow(label: .default(title: L10n.screenRoomMemberListManageMemberUserInfo,
icon: \.info),
icon: \.userProfileSolid),
kind: .button {
context.send(viewAction: .showMemberDetails(member))
})
if !member.isBanned {
if context.viewState.canKickUsers, !member.isBanned {
ListRow(label: .default(title: L10n.screenRoomMemberListManageMemberRemove,
icon: \.block,
role: .destructive),
icon: \.close),
kind: .button {
isPresentingRemoveConfirmation = true
context.send(viewAction: .kickMember(member))
})
} else {
// Theoretically we shouldn't reach this branch but just in case we do.
ListRow(label: .default(title: L10n.screenRoomMemberListManageMemberUnbanAction,
}
if context.viewState.canBanUsers, !member.isBanned {
ListRow(label: .default(title: L10n.screenRoomMemberListManageMemberBan,
icon: \.block,
role: .destructive),
kind: .button {
context.send(viewAction: .unbanMember(member))
isPresentingBanConfirmation = true
})
}
}
@@ -59,20 +59,14 @@ struct RoomMembersListManageMemberSheet: View {
.compoundList()
.scrollBounceBehavior(.basedOnSize)
.presentationDragIndicator(.visible)
.presentationDetents([.large, .fraction(0.5)]) // TODO: Use the ideal height somehow?
.confirmationDialog(L10n.screenRoomMemberListManageMemberRemoveConfirmationTitle,
isPresented: $isPresentingRemoveConfirmation,
titleVisibility: .visible) {
if context.viewState.canKickUsers {
Button(L10n.screenRoomMemberListManageMemberRemoveConfirmationKick) {
context.send(viewAction: .kickMember(member))
}
}
if context.viewState.canBanUsers {
Button(L10n.screenRoomMemberListManageMemberRemoveConfirmationBan, role: .destructive) {
context.send(viewAction: .banMember(member))
}
.presentationDetents([.large, .fraction(0.54)]) // TODO: Use the ideal height somehow?
.alert(L10n.screenRoomMemberListBanMemberConfirmationTitle, isPresented: $isPresentingBanConfirmation) {
Button(L10n.actionCancel, role: .cancel) { }
Button(L10n.screenRoomMemberListBanMemberConfirmationAction) {
context.send(viewAction: .banMember(member))
}
} message: {
Text(L10n.screenRoomMemberListBanMemberConfirmationDescription)
}
}
}
@@ -84,10 +78,12 @@ struct RoomMembersListManageMemberSheet_Previews: PreviewProvider, TestablePrevi
RoomMembersListManageMemberSheet(member: .init(withProxy: RoomMemberProxyMock.mockDan),
context: viewModel.context)
.previewDisplayName("Joined")
.snapshot(delay: 0.2)
RoomMembersListManageMemberSheet(member: .init(withProxy: RoomMemberProxyMock.mockBanned[3]),
context: viewModel.context)
.previewDisplayName("Banned")
.snapshot(delay: 0.2)
}
}
@@ -107,7 +103,7 @@ struct RoomMembersListManageMemberSheetLive_Previews: PreviewProvider {
private extension RoomMembersListScreenViewModel {
static var mock: RoomMembersListScreenViewModel {
RoomMembersListScreenViewModel(initialMode: .members,
roomProxy: RoomProxyMock(with: .init()),
roomProxy: RoomProxyMock(with: .init(members: .allMembersAsAdmin)),
mediaProvider: MockMediaProvider(),
userIndicatorController: ServiceLocator.shared.userIndicatorController,
appSettings: ServiceLocator.shared.settings)
@@ -22,9 +22,7 @@ struct RoomMembersListScreen: View {
var body: some View {
ScrollView {
if context.viewState.canBanUsers,
context.viewState.bannedMembersCount > 0 {
// Maybe this should go into the search bar if it can be pinned when not focussed?
if context.viewState.canBanUsers {
Picker("", selection: $context.mode) {
Text(L10n.screenRoomMemberListModeMembers)
.tag(RoomMembersListScreenMode.members)
@@ -41,6 +39,16 @@ struct RoomMembersListScreen: View {
bannedUsers
}
}
.overlay {
if context.mode == .banned, context.viewState.bannedMembersCount == 0 {
Text(L10n.screenRoomMemberListBannedEmpty)
.font(.compound.bodyMD)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity)
.background(.compound.bgCanvasDefault)
}
}
.searchable(text: $context.searchQuery, placement: .navigationBarDrawer(displayMode: .always))
.compoundSearchField()
.autocorrectionDisabled()
@@ -110,6 +118,7 @@ struct RoomMembersListScreen_Previews: PreviewProvider, TestablePreview {
static let invitesViewModel = makeViewModel(withInvites: true)
static let adminViewModel = makeViewModel(isAdmin: true, initialMode: .members)
static let bannedViewModel = makeViewModel(isAdmin: true, initialMode: .banned)
static let emptyBannedViewModel = makeViewModel(withBanned: false, isAdmin: true, initialMode: .banned)
static var previews: some View {
NavigationStack {
@@ -135,9 +144,16 @@ struct RoomMembersListScreen_Previews: PreviewProvider, TestablePreview {
}
.snapshot(delay: 1.0)
.previewDisplayName("Admin: Banned")
NavigationStack {
RoomMembersListScreen(context: emptyBannedViewModel.context)
}
.snapshot(delay: 1.0)
.previewDisplayName("Admin: Empty Banned")
}
static func makeViewModel(withInvites: Bool = false,
withBanned: Bool = true,
isAdmin: Bool = false,
initialMode: RoomMembersListScreenMode = .members) -> RoomMembersListScreenViewModel {
let mockAdmin = RoomMemberProxyMock.mockAdmin
@@ -150,7 +166,11 @@ struct RoomMembersListScreen_Previews: PreviewProvider, TestablePreview {
.mockCharlie,
mockAdmin,
.mockModerator
] + RoomMemberProxyMock.mockBanned
]
if withBanned {
members.append(contentsOf: RoomMemberProxyMock.mockBanned)
}
if withInvites {
members.append(.mockInvited)