mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Kick/ban room members (#2501)
* Add a temporary membershipChangePublisher on the timeline. * Kick/Ban users from a room. * Unit tests. * Disable autocorrection on the members list search field.
This commit is contained in:
+115
@@ -0,0 +1,115 @@
|
||||
//
|
||||
// Copyright 2024 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 RoomMembersListManageMemberSheet: View {
|
||||
let member: RoomMemberDetails
|
||||
let context: RoomMembersListScreenViewModel.Context
|
||||
|
||||
@State private var isPresentingRemoveConfirmation = false
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
AvatarHeaderView(member: member,
|
||||
avatarSize: .user(on: .memberDetails),
|
||||
imageProvider: context.imageProvider) {
|
||||
EmptyView()
|
||||
}
|
||||
|
||||
Section {
|
||||
ListRow(label: .default(title: L10n.screenRoomMemberListManageMemberUserInfo,
|
||||
icon: \.info),
|
||||
kind: .button {
|
||||
context.send(viewAction: .showMemberDetails(member))
|
||||
})
|
||||
|
||||
if !member.isBanned {
|
||||
ListRow(label: .default(title: L10n.screenRoomMemberListManageMemberRemove,
|
||||
icon: \.block,
|
||||
role: .destructive),
|
||||
kind: .button {
|
||||
isPresentingRemoveConfirmation = true
|
||||
})
|
||||
} else {
|
||||
// Theoretically we shouldn't reach this branch but just in case we do.
|
||||
ListRow(label: .default(title: L10n.screenRoomMemberListManageMemberUnbanAction,
|
||||
icon: \.block,
|
||||
role: .destructive),
|
||||
kind: .button {
|
||||
context.send(viewAction: .unbanMember(member))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
.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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RoomMembersListManageMemberSheet_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = RoomMembersListScreenViewModel.mock
|
||||
|
||||
static var previews: some View {
|
||||
RoomMembersListManageMemberSheet(member: .init(withProxy: RoomMemberProxyMock.mockDan),
|
||||
context: viewModel.context)
|
||||
.previewDisplayName("Joined")
|
||||
|
||||
RoomMembersListManageMemberSheet(member: .init(withProxy: RoomMemberProxyMock.mockBanned[3]),
|
||||
context: viewModel.context)
|
||||
.previewDisplayName("Banned")
|
||||
}
|
||||
}
|
||||
|
||||
struct RoomMembersListManageMemberSheetLive_Previews: PreviewProvider {
|
||||
static let viewModel = RoomMembersListScreenViewModel.mock
|
||||
|
||||
static var previews: some View {
|
||||
Color.clear
|
||||
.sheet(isPresented: .constant(true)) {
|
||||
RoomMembersListManageMemberSheet(member: .init(withProxy: RoomMemberProxyMock.mockDan),
|
||||
context: viewModel.context)
|
||||
}
|
||||
.previewDisplayName("Sheet")
|
||||
}
|
||||
}
|
||||
|
||||
private extension RoomMembersListScreenViewModel {
|
||||
static var mock: RoomMembersListScreenViewModel {
|
||||
RoomMembersListScreenViewModel(initialMode: .members,
|
||||
roomProxy: RoomProxyMock(with: .init()),
|
||||
mediaProvider: MockMediaProvider(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
}
|
||||
}
|
||||
@@ -43,14 +43,14 @@ struct RoomMembersListScreen: View {
|
||||
}
|
||||
.searchable(text: $context.searchQuery, placement: .navigationBarDrawer(displayMode: .always))
|
||||
.compoundSearchField()
|
||||
.autocorrectionDisabled()
|
||||
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
|
||||
.navigationTitle(L10n.commonPeople)
|
||||
.alert(item: $context.alertInfo)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
inviteButton
|
||||
}
|
||||
.sheet(item: $context.memberToManage) {
|
||||
RoomMembersListManageMemberSheet(member: $0, context: context)
|
||||
}
|
||||
.alert(item: $context.alertInfo)
|
||||
.toolbar { toolbar }
|
||||
.track(screen: .RoomMembers)
|
||||
}
|
||||
|
||||
@@ -91,13 +91,13 @@ struct RoomMembersListScreen: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var inviteButton: some View {
|
||||
if context.viewState.canInviteUsers {
|
||||
Button {
|
||||
context.send(viewAction: .invite)
|
||||
} label: {
|
||||
Text(L10n.actionInvite)
|
||||
@ToolbarContentBuilder
|
||||
private var toolbar: some ToolbarContent {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
if context.viewState.canInviteUsers {
|
||||
Button(L10n.actionInvite) {
|
||||
context.send(viewAction: .invite)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,6 +162,7 @@ struct RoomMembersListScreen_Previews: PreviewProvider, TestablePreview {
|
||||
return RoomMembersListScreenViewModel(initialMode: initialMode,
|
||||
roomProxy: RoomProxyMock(with: .init(name: "Some room", members: members)),
|
||||
mediaProvider: MockMediaProvider(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -22,7 +22,7 @@ struct RoomMembersListScreenMemberCell: View {
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
context.send(viewAction: .selectMember(id: member.id))
|
||||
context.send(viewAction: .selectMember(member))
|
||||
} label: {
|
||||
HStack(spacing: 8) {
|
||||
LoadableAvatarImage(url: avatarURL,
|
||||
@@ -109,7 +109,8 @@ struct RoomMembersListMemberCell_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = RoomMembersListScreenViewModel(roomProxy: RoomProxyMock(with: .init(name: "Some room",
|
||||
members: members)),
|
||||
mediaProvider: MockMediaProvider(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
static var previews: some View {
|
||||
VStack(spacing: 12) {
|
||||
Section("Invited/Joined") {
|
||||
|
||||
Reference in New Issue
Block a user