Skip to content

Commit

Permalink
Fix various bugs in the moderation feature. (#2608)
Browse files Browse the repository at this point in the history
* Fix search field string.

* Show your own user as an Admin when changing roles.

* Also show invited users when changing roles.

* Don't allow admin's to kick/ban other admins or themselves.

* Fix a bug when left members were counted as admins/moderators.

* Show when a member is pending.

* Add sections to the change role screen.
  • Loading branch information
pixlwave authored Mar 27, 2024
1 parent bb725db commit e424a02
Show file tree
Hide file tree
Showing 51 changed files with 413 additions and 148 deletions.
12 changes: 8 additions & 4 deletions ElementX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@
5D2AF8C0DF872E7985F8FE54 /* TimelineDeliveryStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AC06FC11B6638F7BF1670E /* TimelineDeliveryStatusView.swift */; };
5D4643E485C179B2F485C519 /* MentionSuggestionItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FD0E68C42CA7DDCD4CAD68D /* MentionSuggestionItemView.swift */; };
5D53AE9342A4C06B704247ED /* MediaLoaderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A02406480C351B8C6E0682C /* MediaLoaderProtocol.swift */; };
5D56CE09743C6B90C21B04C2 /* RoomMembersListScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9E0929CEFA356090BE5FB8 /* RoomMembersListScreenViewModelTests.swift */; };
5D70FAE4D2BF4553AFFFFE41 /* NotificationItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25F7FE40EF7490A7E09D7BE6 /* NotificationItemProxy.swift */; };
5DD0EF30070DC0A82C5CCD33 /* RoomMembersListManageMemberSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC853F9B4FBE039D2C16EC6B /* RoomMembersListManageMemberSheet.swift */; };
5DD85A0FE3D85AEC3C7EFE36 /* DeveloperOptionsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7C7CFA6B2A62A685FF6CE3 /* DeveloperOptionsScreenCoordinator.swift */; };
Expand Down Expand Up @@ -829,7 +830,6 @@
C9F5B48D15B9BCAE1F8D564E /* RoomNotificationModeProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1511766C534367700C8DD75 /* RoomNotificationModeProxy.swift */; };
CA12AE0DCD57D49CD96C699A /* WaveformCursorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB9EABCA9348DFA27439A809 /* WaveformCursorView.swift */; };
CA5BFF0C2EF5A8EF40CA2D69 /* VoiceMessageRecordingComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCB6F36CCE44A29A06FCAF1C /* VoiceMessageRecordingComposer.swift */; };
CAF8755E152204F55F8D6B5B /* RoomMembersListViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69B63F817FE305548DB4B512 /* RoomMembersListViewModelTests.swift */; };
CB137BFB3E083C33E398A6CB /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 020597E28A4BC8E1BE8EDF6E /* KeychainAccess */; };
CB498F4E27AA0545DCEF0F6F /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 36B7FC232711031AA2B0D188 /* DTCoreText */; };
CB6BCBF28E4B76EA08C2926D /* StateRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16048D30F0438731C41F775 /* StateRoomTimelineItem.swift */; };
Expand All @@ -843,6 +843,7 @@
CCBEC2100CAF2EEBE9DB4156 /* TemplateScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA40B98B098B6F0371B750B3 /* TemplateScreenModels.swift */; };
CD0088B763CD970CF1CBF8CB /* DateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B5E97E9615A158C76B2AB77 /* DateTests.swift */; };
CD6A72B65D3B6076F4045C30 /* PHGPostHogConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */; };
CDAE3A37D4DF136F9D07DB61 /* RoomChangeRolesScreenSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF710CB1C31F8938EAA3A7D /* RoomChangeRolesScreenSection.swift */; };
CDCA8A559E098503DDE29477 /* AttributedStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */; };
CE1694C7BB93C3311524EF28 /* Untranslated.strings in Resources */ = {isa = PBXBuildFile; fileRef = D2F7194F440375338F8E2487 /* Untranslated.strings */; };
CE6F237360875D3D573FD0B2 /* RoomNotificationSettingsProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6B522BD637845AB9570B10 /* RoomNotificationSettingsProxy.swift */; };
Expand Down Expand Up @@ -1355,6 +1356,7 @@
3DFE4453AB0B34C203447162 /* ImageRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineItem.swift; sourceTree = "<group>"; };
3E6A9B9DFEE964962C179DE3 /* RoomAttachmentPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomAttachmentPicker.swift; sourceTree = "<group>"; };
3E93A1BE7D8A2EBCAD51EEB4 /* Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = "<group>"; };
3E9E0929CEFA356090BE5FB8 /* RoomMembersListScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModelTests.swift; sourceTree = "<group>"; };
3EF1AC723C2609C7705569CA /* MediaLoaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLoaderTests.swift; sourceTree = "<group>"; };
3FFDA99C98BE05F43A92343B /* test_pdf.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = test_pdf.pdf; sourceTree = "<group>"; };
40076C770A5FB83325252973 /* VoiceMessageMediaManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageMediaManager.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1503,7 +1505,6 @@
68010886142843705E342645 /* ProgressMaskModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressMaskModifier.swift; sourceTree = "<group>"; };
6861FE915C7B5466E6962BBA /* StartChatScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreen.swift; sourceTree = "<group>"; };
693E16574C6F7F9FA1015A8C /* Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Search.swift; sourceTree = "<group>"; };
69B63F817FE305548DB4B512 /* RoomMembersListViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListViewModelTests.swift; sourceTree = "<group>"; };
69CB8242D69B7E4D0B32E18D /* AggregatedReactionMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AggregatedReactionMock.swift; sourceTree = "<group>"; };
69D42EE0102D2857933625DD /* CreateRoomViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomViewModelTests.swift; sourceTree = "<group>"; };
6A4C9547BBFEEF30AA11329B /* TimelineItemStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemStatusView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2009,6 +2010,7 @@
E9D059BFE329BE09B6D96A9F /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ro; path = ro.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
EA4D639E27D5882A6A71AECF /* GlobalSearchScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalSearchScreenViewModelTests.swift; sourceTree = "<group>"; };
EA880E78AF4BD24E45A7808C /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/InfoPlist.strings; sourceTree = "<group>"; };
EAF710CB1C31F8938EAA3A7D /* RoomChangeRolesScreenSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreenSection.swift; sourceTree = "<group>"; };
EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAgentBuilderTests.swift; sourceTree = "<group>"; };
EB63761D9F9CE8B23CBD6179 /* PollFormScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollFormScreenModels.swift; sourceTree = "<group>"; };
EB76A9AFC6CCAD4998D9B045 /* IdentityConfirmationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenViewModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3217,6 +3219,7 @@
children = (
6B2A421198FD20AAAED20004 /* RoomChangeRolesScreen.swift */,
23E6EB7960BC9D0F7396B3BD /* RoomChangeRolesScreenRow.swift */,
EAF710CB1C31F8938EAA3A7D /* RoomChangeRolesScreenSection.swift */,
3D9B45D584D232CB9E5C7734 /* RoomChangeRolesScreenSelectedItem.swift */,
);
path = View;
Expand Down Expand Up @@ -3494,7 +3497,7 @@
4FCB2126C091EEF2454B4D56 /* RoomFlowCoordinatorTests.swift */,
8AE0C9653870803E4F91F474 /* RoomListFiltersStateTests.swift */,
EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */,
69B63F817FE305548DB4B512 /* RoomMembersListViewModelTests.swift */,
3E9E0929CEFA356090BE5FB8 /* RoomMembersListScreenViewModelTests.swift */,
58D295F0081084F38DB20893 /* RoomNotificationSettingsScreenViewModelTests.swift */,
F0096BC5DA86AF6B6E5742AC /* RoomPermissionsTests.swift */,
B40233F2989AD49906BB310D /* RoomPollsHistoryScreenViewModelTests.swift */,
Expand Down Expand Up @@ -5632,7 +5635,7 @@
095D3906CF2F940C2D2D17CC /* RoomFlowCoordinatorTests.swift in Sources */,
4C8C0C9FC10BA73AB7780534 /* RoomListFiltersStateTests.swift in Sources */,
6B31508C6334C617360C2EAB /* RoomMemberDetailsViewModelTests.swift in Sources */,
CAF8755E152204F55F8D6B5B /* RoomMembersListViewModelTests.swift in Sources */,
5D56CE09743C6B90C21B04C2 /* RoomMembersListScreenViewModelTests.swift in Sources */,
E49F74BD93230BDEFFE5EA51 /* RoomNotificationSettingsScreenViewModelTests.swift in Sources */,
2335D1AB954C151FD8779F45 /* RoomPermissionsTests.swift in Sources */,
7B1605C6FFD4D195F264A684 /* RoomPollsHistoryScreenViewModelTests.swift in Sources */,
Expand Down Expand Up @@ -6121,6 +6124,7 @@
244407B18B2F2D6466BA5961 /* RoomChangeRolesScreenCoordinator.swift in Sources */,
7FF6E1FBE6E9517FD29A1D8E /* RoomChangeRolesScreenModels.swift in Sources */,
7F941B063C94E1718DFC2CF3 /* RoomChangeRolesScreenRow.swift in Sources */,
CDAE3A37D4DF136F9D07DB61 /* RoomChangeRolesScreenSection.swift in Sources */,
BD6685592716CA957D7BAAC4 /* RoomChangeRolesScreenSelectedItem.swift in Sources */,
3EC5A41F9FB7DD63A4DC6144 /* RoomChangeRolesScreenViewModel.swift in Sources */,
4E36A66E0EDA74BF3A036FD0 /* RoomChangeRolesScreenViewModelProtocol.swift in Sources */,
Expand Down
4 changes: 4 additions & 0 deletions ElementX/Resources/Localizations/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,7 @@
"screen_room_change_role_confirm_demote_self_action" = "Demote";
"screen_room_change_role_confirm_demote_self_description" = "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.";
"screen_room_change_role_confirm_demote_self_title" = "Demote yourself?";
"screen_room_change_role_invited_member_name" = "%1$@ (Pending)";
"screen_room_change_role_moderators_title" = "Edit Moderators";
"screen_room_change_role_unsaved_changes_description" = "You have unsaved changes.";
"screen_room_change_role_unsaved_changes_title" = "Save changes?";
Expand Down Expand Up @@ -793,6 +794,9 @@
"screen_room_change_permissions_member_moderation" = "Member moderation";
"screen_room_change_permissions_messages_and_content" = "Messages and content";
"screen_room_change_permissions_room_details" = "Room details";
"screen_room_change_role_section_administrators" = "Admins";
"screen_room_change_role_section_moderators" = "Moderators";
"screen_room_change_role_section_users" = "Members";
"screen_room_details_invite_people_title" = "Invite people";
"screen_room_details_leave_conversation_title" = "Leave conversation";
"screen_room_details_leave_room_title" = "Leave room";
Expand Down
10 changes: 10 additions & 0 deletions ElementX/Sources/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1263,8 +1263,18 @@ internal enum L10n {
internal static var screenRoomChangeRoleConfirmDemoteSelfDescription: String { return L10n.tr("Localizable", "screen_room_change_role_confirm_demote_self_description") }
/// Demote yourself?
internal static var screenRoomChangeRoleConfirmDemoteSelfTitle: String { return L10n.tr("Localizable", "screen_room_change_role_confirm_demote_self_title") }
/// %1$@ (Pending)
internal static func screenRoomChangeRoleInvitedMemberName(_ p1: Any) -> String {
return L10n.tr("Localizable", "screen_room_change_role_invited_member_name", String(describing: p1))
}
/// Edit Moderators
internal static var screenRoomChangeRoleModeratorsTitle: String { return L10n.tr("Localizable", "screen_room_change_role_moderators_title") }
/// Admins
internal static var screenRoomChangeRoleSectionAdministrators: String { return L10n.tr("Localizable", "screen_room_change_role_section_administrators") }
/// Moderators
internal static var screenRoomChangeRoleSectionModerators: String { return L10n.tr("Localizable", "screen_room_change_role_section_moderators") }
/// Members
internal static var screenRoomChangeRoleSectionUsers: String { return L10n.tr("Localizable", "screen_room_change_role_section_users") }
/// You have unsaved changes.
internal static var screenRoomChangeRoleUnsavedChangesDescription: String { return L10n.tr("Localizable", "screen_room_change_role_unsaved_changes_description") }
/// Save changes?
Expand Down
2 changes: 1 addition & 1 deletion ElementX/Sources/Mocks/RoomMemberProxyMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ extension RoomMemberProxyMock {

static var mockMeAdmin: RoomMemberProxyMock {
RoomMemberProxyMock(with: .init(userID: "@me:matrix.org",
displayName: "Me admin",
displayName: "Me",
avatarURL: URL.picturesDirectory,
membership: .join,
powerLevel: 100,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,13 @@ enum RoomChangeRolesScreenViewModelAction {
struct RoomChangeRolesScreenViewState: BindableState {
/// The screen's current mode (which role we are promoting/demoting users to/from.
let mode: RoomMemberDetails.Role
/// All of the room's members.
var members: [RoomMemberDetails]
/// All of the room's members who are currently admins.
var administrators: [RoomMemberDetails]
/// All of the room's members who are currently moderators.
var moderators: [RoomMemberDetails]
/// All of the room's members who are currently neither an admin or moderator.
var users: [RoomMemberDetails]

var bindings: RoomChangeRolesScreenViewStateBindings

/// The members selected for promotion to the current role.
Expand All @@ -48,19 +53,24 @@ struct RoomChangeRolesScreenViewState: BindableState {
}
}

/// The visible members in the screen (after searching).
var visibleMembers: [RoomMemberDetails] {
guard !bindings.searchQuery.isEmpty else { return members }

return members.filter { member in
member.name?.localizedStandardContains(bindings.searchQuery) == true
|| member.id.localizedStandardContains(bindings.searchQuery)
}
/// The visible admins in the screen (after searching).
var visibleAdministrators: [RoomMemberDetails] {
administrators.filter { $0.matches(searchQuery: bindings.searchQuery) }
}

/// The visible mods in the screen (after searching).
var visibleModerators: [RoomMemberDetails] {
moderators.filter { $0.matches(searchQuery: bindings.searchQuery) }
}

/// The visible regular users in the screen (after searching).
var visibleUsers: [RoomMemberDetails] {
users.filter { $0.matches(searchQuery: bindings.searchQuery) }
}

/// All of the members who will gain/keep this screen's role after saving any changes.
var membersWithRole: [RoomMemberDetails] {
members.filter(isMemberSelected)
administrators.filter(isMemberSelected) + moderators.filter(isMemberSelected) + users.filter(isMemberSelected)
}

/// Whether or not any changes have been made to the members.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh
self.analytics = analytics

super.init(initialViewState: RoomChangeRolesScreenViewState(mode: mode,
members: [],
administrators: [],
moderators: [],
users: [],
bindings: .init()))

roomProxy.membersPublisher
Expand Down Expand Up @@ -82,10 +84,27 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh
// MARK: - Private

private func updateMembers(_ members: [RoomMemberProxyProtocol]) {
state.members = members.sorted().compactMap { member in
guard member.membership == .join, member.userID != roomProxy.ownUserID else { return nil }
return RoomMemberDetails(withProxy: member)
var administrators = [RoomMemberDetails]()
var moderators = [RoomMemberDetails]()
var users = [RoomMemberDetails]()

for member in members.sorted() {
guard member.isActive else { continue }
let memberDetails = RoomMemberDetails(withProxy: member)

switch member.role {
case .administrator:
administrators.append(memberDetails)
case .moderator:
moderators.append(memberDetails)
case .user:
users.append(memberDetails)
}
}

state.administrators = administrators
state.moderators = moderators
state.users = users
}

private func toggleMember(_ member: RoomMemberDetails) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,34 +56,20 @@ struct RoomChangeRolesScreen: View {
}
}

membersSection
RoomChangeRolesScreenSection(members: context.viewState.administrators,
title: L10n.screenRoomChangeRoleSectionAdministrators,
context: context)
RoomChangeRolesScreenSection(members: context.viewState.moderators,
title: L10n.screenRoomChangeRoleSectionModerators,
context: context)
RoomChangeRolesScreenSection(members: context.viewState.users,
title: L10n.screenRoomChangeRoleSectionUsers,
context: context)
}
}
}

@ViewBuilder
private var membersSection: some View {
if !context.viewState.visibleMembers.isEmpty {
Section {
ForEach(context.viewState.visibleMembers, id: \.id) { member in
RoomChangeRolesScreenRow(member: member,
imageProvider: context.imageProvider,
isSelected: context.viewState.isMemberSelected(member)) {
context.send(viewAction: .toggleMember(member))
}
.disabled(member.role == .administrator)
}
} header: {
Text(L10n.screenRoomMemberListRoomMembersHeaderTitle)
.compoundListSectionHeader()
}
} else {
Section.empty
}
}

@ScaledMetric private var cellWidth: CGFloat = 72

private var membersWithRoleSection: some View {
ScrollView(.horizontal, showsIndicators: false) {
ScrollViewReader { scrollView in
Expand Down
Loading

0 comments on commit e424a02

Please sign in to comment.