Fix various bugs in the moderation feature. (#2608)

* 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.
This commit is contained in:
Doug
2024-03-27 10:33:40 +00:00
committed by GitHub
parent bb725db6bb
commit e424a02a2b
51 changed files with 413 additions and 148 deletions
+8 -4
View File
@@ -379,6 +379,7 @@
5D2AF8C0DF872E7985F8FE54 /* TimelineDeliveryStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AC06FC11B6638F7BF1670E /* TimelineDeliveryStatusView.swift */; }; 5D2AF8C0DF872E7985F8FE54 /* TimelineDeliveryStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AC06FC11B6638F7BF1670E /* TimelineDeliveryStatusView.swift */; };
5D4643E485C179B2F485C519 /* MentionSuggestionItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FD0E68C42CA7DDCD4CAD68D /* MentionSuggestionItemView.swift */; }; 5D4643E485C179B2F485C519 /* MentionSuggestionItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FD0E68C42CA7DDCD4CAD68D /* MentionSuggestionItemView.swift */; };
5D53AE9342A4C06B704247ED /* MediaLoaderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A02406480C351B8C6E0682C /* MediaLoaderProtocol.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 */; }; 5D70FAE4D2BF4553AFFFFE41 /* NotificationItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25F7FE40EF7490A7E09D7BE6 /* NotificationItemProxy.swift */; };
5DD0EF30070DC0A82C5CCD33 /* RoomMembersListManageMemberSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC853F9B4FBE039D2C16EC6B /* RoomMembersListManageMemberSheet.swift */; }; 5DD0EF30070DC0A82C5CCD33 /* RoomMembersListManageMemberSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC853F9B4FBE039D2C16EC6B /* RoomMembersListManageMemberSheet.swift */; };
5DD85A0FE3D85AEC3C7EFE36 /* DeveloperOptionsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7C7CFA6B2A62A685FF6CE3 /* DeveloperOptionsScreenCoordinator.swift */; }; 5DD85A0FE3D85AEC3C7EFE36 /* DeveloperOptionsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7C7CFA6B2A62A685FF6CE3 /* DeveloperOptionsScreenCoordinator.swift */; };
@@ -829,7 +830,6 @@
C9F5B48D15B9BCAE1F8D564E /* RoomNotificationModeProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1511766C534367700C8DD75 /* RoomNotificationModeProxy.swift */; }; C9F5B48D15B9BCAE1F8D564E /* RoomNotificationModeProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1511766C534367700C8DD75 /* RoomNotificationModeProxy.swift */; };
CA12AE0DCD57D49CD96C699A /* WaveformCursorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB9EABCA9348DFA27439A809 /* WaveformCursorView.swift */; }; CA12AE0DCD57D49CD96C699A /* WaveformCursorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB9EABCA9348DFA27439A809 /* WaveformCursorView.swift */; };
CA5BFF0C2EF5A8EF40CA2D69 /* VoiceMessageRecordingComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCB6F36CCE44A29A06FCAF1C /* VoiceMessageRecordingComposer.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 */; }; CB137BFB3E083C33E398A6CB /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 020597E28A4BC8E1BE8EDF6E /* KeychainAccess */; };
CB498F4E27AA0545DCEF0F6F /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 36B7FC232711031AA2B0D188 /* DTCoreText */; }; CB498F4E27AA0545DCEF0F6F /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 36B7FC232711031AA2B0D188 /* DTCoreText */; };
CB6BCBF28E4B76EA08C2926D /* StateRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16048D30F0438731C41F775 /* StateRoomTimelineItem.swift */; }; CB6BCBF28E4B76EA08C2926D /* StateRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16048D30F0438731C41F775 /* StateRoomTimelineItem.swift */; };
@@ -843,6 +843,7 @@
CCBEC2100CAF2EEBE9DB4156 /* TemplateScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA40B98B098B6F0371B750B3 /* TemplateScreenModels.swift */; }; CCBEC2100CAF2EEBE9DB4156 /* TemplateScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA40B98B098B6F0371B750B3 /* TemplateScreenModels.swift */; };
CD0088B763CD970CF1CBF8CB /* DateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B5E97E9615A158C76B2AB77 /* DateTests.swift */; }; CD0088B763CD970CF1CBF8CB /* DateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B5E97E9615A158C76B2AB77 /* DateTests.swift */; };
CD6A72B65D3B6076F4045C30 /* PHGPostHogConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.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 */; }; CDCA8A559E098503DDE29477 /* AttributedStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */; };
CE1694C7BB93C3311524EF28 /* Untranslated.strings in Resources */ = {isa = PBXBuildFile; fileRef = D2F7194F440375338F8E2487 /* Untranslated.strings */; }; CE1694C7BB93C3311524EF28 /* Untranslated.strings in Resources */ = {isa = PBXBuildFile; fileRef = D2F7194F440375338F8E2487 /* Untranslated.strings */; };
CE6F237360875D3D573FD0B2 /* RoomNotificationSettingsProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6B522BD637845AB9570B10 /* RoomNotificationSettingsProxy.swift */; }; CE6F237360875D3D573FD0B2 /* RoomNotificationSettingsProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6B522BD637845AB9570B10 /* RoomNotificationSettingsProxy.swift */; };
@@ -1355,6 +1356,7 @@
3DFE4453AB0B34C203447162 /* ImageRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineItem.swift; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 40076C770A5FB83325252973 /* VoiceMessageMediaManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageMediaManager.swift; sourceTree = "<group>"; };
@@ -1503,7 +1505,6 @@
68010886142843705E342645 /* ProgressMaskModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressMaskModifier.swift; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 6A4C9547BBFEEF30AA11329B /* TimelineItemStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemStatusView.swift; sourceTree = "<group>"; };
@@ -2009,6 +2010,7 @@
E9D059BFE329BE09B6D96A9F /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ro; path = ro.lproj/Localizable.stringsdict; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; EB76A9AFC6CCAD4998D9B045 /* IdentityConfirmationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenViewModel.swift; sourceTree = "<group>"; };
@@ -3217,6 +3219,7 @@
children = ( children = (
6B2A421198FD20AAAED20004 /* RoomChangeRolesScreen.swift */, 6B2A421198FD20AAAED20004 /* RoomChangeRolesScreen.swift */,
23E6EB7960BC9D0F7396B3BD /* RoomChangeRolesScreenRow.swift */, 23E6EB7960BC9D0F7396B3BD /* RoomChangeRolesScreenRow.swift */,
EAF710CB1C31F8938EAA3A7D /* RoomChangeRolesScreenSection.swift */,
3D9B45D584D232CB9E5C7734 /* RoomChangeRolesScreenSelectedItem.swift */, 3D9B45D584D232CB9E5C7734 /* RoomChangeRolesScreenSelectedItem.swift */,
); );
path = View; path = View;
@@ -3494,7 +3497,7 @@
4FCB2126C091EEF2454B4D56 /* RoomFlowCoordinatorTests.swift */, 4FCB2126C091EEF2454B4D56 /* RoomFlowCoordinatorTests.swift */,
8AE0C9653870803E4F91F474 /* RoomListFiltersStateTests.swift */, 8AE0C9653870803E4F91F474 /* RoomListFiltersStateTests.swift */,
EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */, EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */,
69B63F817FE305548DB4B512 /* RoomMembersListViewModelTests.swift */, 3E9E0929CEFA356090BE5FB8 /* RoomMembersListScreenViewModelTests.swift */,
58D295F0081084F38DB20893 /* RoomNotificationSettingsScreenViewModelTests.swift */, 58D295F0081084F38DB20893 /* RoomNotificationSettingsScreenViewModelTests.swift */,
F0096BC5DA86AF6B6E5742AC /* RoomPermissionsTests.swift */, F0096BC5DA86AF6B6E5742AC /* RoomPermissionsTests.swift */,
B40233F2989AD49906BB310D /* RoomPollsHistoryScreenViewModelTests.swift */, B40233F2989AD49906BB310D /* RoomPollsHistoryScreenViewModelTests.swift */,
@@ -5632,7 +5635,7 @@
095D3906CF2F940C2D2D17CC /* RoomFlowCoordinatorTests.swift in Sources */, 095D3906CF2F940C2D2D17CC /* RoomFlowCoordinatorTests.swift in Sources */,
4C8C0C9FC10BA73AB7780534 /* RoomListFiltersStateTests.swift in Sources */, 4C8C0C9FC10BA73AB7780534 /* RoomListFiltersStateTests.swift in Sources */,
6B31508C6334C617360C2EAB /* RoomMemberDetailsViewModelTests.swift in Sources */, 6B31508C6334C617360C2EAB /* RoomMemberDetailsViewModelTests.swift in Sources */,
CAF8755E152204F55F8D6B5B /* RoomMembersListViewModelTests.swift in Sources */, 5D56CE09743C6B90C21B04C2 /* RoomMembersListScreenViewModelTests.swift in Sources */,
E49F74BD93230BDEFFE5EA51 /* RoomNotificationSettingsScreenViewModelTests.swift in Sources */, E49F74BD93230BDEFFE5EA51 /* RoomNotificationSettingsScreenViewModelTests.swift in Sources */,
2335D1AB954C151FD8779F45 /* RoomPermissionsTests.swift in Sources */, 2335D1AB954C151FD8779F45 /* RoomPermissionsTests.swift in Sources */,
7B1605C6FFD4D195F264A684 /* RoomPollsHistoryScreenViewModelTests.swift in Sources */, 7B1605C6FFD4D195F264A684 /* RoomPollsHistoryScreenViewModelTests.swift in Sources */,
@@ -6121,6 +6124,7 @@
244407B18B2F2D6466BA5961 /* RoomChangeRolesScreenCoordinator.swift in Sources */, 244407B18B2F2D6466BA5961 /* RoomChangeRolesScreenCoordinator.swift in Sources */,
7FF6E1FBE6E9517FD29A1D8E /* RoomChangeRolesScreenModels.swift in Sources */, 7FF6E1FBE6E9517FD29A1D8E /* RoomChangeRolesScreenModels.swift in Sources */,
7F941B063C94E1718DFC2CF3 /* RoomChangeRolesScreenRow.swift in Sources */, 7F941B063C94E1718DFC2CF3 /* RoomChangeRolesScreenRow.swift in Sources */,
CDAE3A37D4DF136F9D07DB61 /* RoomChangeRolesScreenSection.swift in Sources */,
BD6685592716CA957D7BAAC4 /* RoomChangeRolesScreenSelectedItem.swift in Sources */, BD6685592716CA957D7BAAC4 /* RoomChangeRolesScreenSelectedItem.swift in Sources */,
3EC5A41F9FB7DD63A4DC6144 /* RoomChangeRolesScreenViewModel.swift in Sources */, 3EC5A41F9FB7DD63A4DC6144 /* RoomChangeRolesScreenViewModel.swift in Sources */,
4E36A66E0EDA74BF3A036FD0 /* RoomChangeRolesScreenViewModelProtocol.swift in Sources */, 4E36A66E0EDA74BF3A036FD0 /* RoomChangeRolesScreenViewModelProtocol.swift in Sources */,
@@ -519,6 +519,7 @@
"screen_room_change_role_confirm_demote_self_action" = "Demote"; "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_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_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_moderators_title" = "Edit Moderators";
"screen_room_change_role_unsaved_changes_description" = "You have unsaved changes."; "screen_room_change_role_unsaved_changes_description" = "You have unsaved changes.";
"screen_room_change_role_unsaved_changes_title" = "Save changes?"; "screen_room_change_role_unsaved_changes_title" = "Save changes?";
@@ -793,6 +794,9 @@
"screen_room_change_permissions_member_moderation" = "Member moderation"; "screen_room_change_permissions_member_moderation" = "Member moderation";
"screen_room_change_permissions_messages_and_content" = "Messages and content"; "screen_room_change_permissions_messages_and_content" = "Messages and content";
"screen_room_change_permissions_room_details" = "Room details"; "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_invite_people_title" = "Invite people";
"screen_room_details_leave_conversation_title" = "Leave conversation"; "screen_room_details_leave_conversation_title" = "Leave conversation";
"screen_room_details_leave_room_title" = "Leave room"; "screen_room_details_leave_room_title" = "Leave room";
+10
View File
@@ -1263,8 +1263,18 @@ internal enum L10n {
internal static var screenRoomChangeRoleConfirmDemoteSelfDescription: String { return L10n.tr("Localizable", "screen_room_change_role_confirm_demote_self_description") } internal static var screenRoomChangeRoleConfirmDemoteSelfDescription: String { return L10n.tr("Localizable", "screen_room_change_role_confirm_demote_self_description") }
/// Demote yourself? /// Demote yourself?
internal static var screenRoomChangeRoleConfirmDemoteSelfTitle: String { return L10n.tr("Localizable", "screen_room_change_role_confirm_demote_self_title") } 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 /// Edit Moderators
internal static var screenRoomChangeRoleModeratorsTitle: String { return L10n.tr("Localizable", "screen_room_change_role_moderators_title") } 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. /// You have unsaved changes.
internal static var screenRoomChangeRoleUnsavedChangesDescription: String { return L10n.tr("Localizable", "screen_room_change_role_unsaved_changes_description") } internal static var screenRoomChangeRoleUnsavedChangesDescription: String { return L10n.tr("Localizable", "screen_room_change_role_unsaved_changes_description") }
/// Save changes? /// Save changes?
@@ -53,7 +53,7 @@ extension RoomMemberProxyMock {
static var mockMeAdmin: RoomMemberProxyMock { static var mockMeAdmin: RoomMemberProxyMock {
RoomMemberProxyMock(with: .init(userID: "@me:matrix.org", RoomMemberProxyMock(with: .init(userID: "@me:matrix.org",
displayName: "Me admin", displayName: "Me",
avatarURL: URL.picturesDirectory, avatarURL: URL.picturesDirectory,
membership: .join, membership: .join,
powerLevel: 100, powerLevel: 100,
@@ -24,8 +24,13 @@ enum RoomChangeRolesScreenViewModelAction {
struct RoomChangeRolesScreenViewState: BindableState { struct RoomChangeRolesScreenViewState: BindableState {
/// The screen's current mode (which role we are promoting/demoting users to/from. /// The screen's current mode (which role we are promoting/demoting users to/from.
let mode: RoomMemberDetails.Role let mode: RoomMemberDetails.Role
/// All of the room's members. /// All of the room's members who are currently admins.
var members: [RoomMemberDetails] 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 var bindings: RoomChangeRolesScreenViewStateBindings
/// The members selected for promotion to the current role. /// The members selected for promotion to the current role.
@@ -48,19 +53,24 @@ struct RoomChangeRolesScreenViewState: BindableState {
} }
} }
/// The visible members in the screen (after searching). /// The visible admins in the screen (after searching).
var visibleMembers: [RoomMemberDetails] { var visibleAdministrators: [RoomMemberDetails] {
guard !bindings.searchQuery.isEmpty else { return members } administrators.filter { $0.matches(searchQuery: bindings.searchQuery) }
}
return members.filter { member in
member.name?.localizedStandardContains(bindings.searchQuery) == true /// The visible mods in the screen (after searching).
|| member.id.localizedStandardContains(bindings.searchQuery) 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. /// All of the members who will gain/keep this screen's role after saving any changes.
var membersWithRole: [RoomMemberDetails] { 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. /// Whether or not any changes have been made to the members.
@@ -40,7 +40,9 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh
self.analytics = analytics self.analytics = analytics
super.init(initialViewState: RoomChangeRolesScreenViewState(mode: mode, super.init(initialViewState: RoomChangeRolesScreenViewState(mode: mode,
members: [], administrators: [],
moderators: [],
users: [],
bindings: .init())) bindings: .init()))
roomProxy.membersPublisher roomProxy.membersPublisher
@@ -82,10 +84,27 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh
// MARK: - Private // MARK: - Private
private func updateMembers(_ members: [RoomMemberProxyProtocol]) { private func updateMembers(_ members: [RoomMemberProxyProtocol]) {
state.members = members.sorted().compactMap { member in var administrators = [RoomMemberDetails]()
guard member.membership == .join, member.userID != roomProxy.ownUserID else { return nil } var moderators = [RoomMemberDetails]()
return RoomMemberDetails(withProxy: member) 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) { private func toggleMember(_ member: RoomMemberDetails) {
@@ -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 @ScaledMetric private var cellWidth: CGFloat = 72
private var membersWithRoleSection: some View { private var membersWithRoleSection: some View {
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {
ScrollViewReader { scrollView in ScrollViewReader { scrollView in
@@ -28,10 +28,20 @@ struct RoomChangeRolesScreenRow: View {
let action: () -> Void let action: () -> Void
var body: some View { var body: some View {
ListRow(label: .avatar(title: member.name ?? member.id, ListRow(label: .avatar(title: memberName,
description: member.name == nil ? nil : member.id, description: member.name == nil ? nil : member.id,
icon: avatar), icon: avatar),
kind: isEnabled ? .multiSelection(isSelected: isSelected, action: action) : .label) kind: .multiSelection(isSelected: isSelected, action: action))
}
var memberName: String {
let name = member.name ?? member.id
return if member.isInvited {
L10n.screenRoomChangeRoleInvitedMemberName(name)
} else {
name
}
} }
var avatar: LoadableAvatarImage { var avatar: LoadableAvatarImage {
@@ -58,6 +68,11 @@ struct RoomChangeRolesScreenRow_Previews: PreviewProvider, TestablePreview {
isSelected: false, isSelected: false,
action: action) action: action)
RoomChangeRolesScreenRow(member: .init(withProxy: RoomMemberProxyMock.mockInvited),
imageProvider: MockMediaProvider(),
isSelected: false,
action: action)
RoomChangeRolesScreenRow(member: .init(withProxy: RoomMemberProxyMock.mockCharlie), RoomChangeRolesScreenRow(member: .init(withProxy: RoomMemberProxyMock.mockCharlie),
imageProvider: MockMediaProvider(), imageProvider: MockMediaProvider(),
isSelected: true, isSelected: true,
@@ -0,0 +1,43 @@
//
// 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 RoomChangeRolesScreenSection: View {
let members: [RoomMemberDetails]
let title: String
@ObservedObject var context: RoomChangeRolesScreenViewModel.Context
var body: some View {
if !members.isEmpty {
Section {
ForEach(members, 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(title)
.compoundListSectionHeader()
}
}
}
}
@@ -19,6 +19,15 @@ import Foundation
enum RoomMembersListScreenViewModelAction { enum RoomMembersListScreenViewModelAction {
case selectMember(_ member: RoomMemberProxyProtocol) case selectMember(_ member: RoomMemberProxyProtocol)
case invite case invite
var isSelectMember: Bool {
switch self {
case .selectMember:
true
default:
false
}
}
} }
/// The different modes that the screen can be in. /// The different modes that the screen can be in.
@@ -54,7 +63,7 @@ struct RoomMembersListScreenViewState: BindableState {
self.bannedMembers = bannedMembers self.bannedMembers = bannedMembers
self.bindings = bindings self.bindings = bindings
} }
var visibleJoinedMembers: [RoomMemberDetails] { var visibleJoinedMembers: [RoomMemberDetails] {
joinedMembers joinedMembers
.filter { $0.matches(searchQuery: bindings.searchQuery) } .filter { $0.matches(searchQuery: bindings.searchQuery) }
@@ -76,12 +85,25 @@ struct RoomMembersListScreenViewStateBindings {
/// The current mode the screen is in. /// The current mode the screen is in.
var mode: RoomMembersListScreenMode = .members var mode: RoomMembersListScreenMode = .members
/// A selected member to kick, ban, promote etc. /// A selected member to kick, ban, promote etc.
var memberToManage: RoomMemberDetails? var memberToManage: RoomMembersListScreenManagementDetails?
/// Information describing the currently displayed alert. /// Information describing the currently displayed alert.
var alertInfo: AlertInfo<RoomMembersListScreenAlertType>? var alertInfo: AlertInfo<RoomMembersListScreenAlertType>?
} }
/// Information about managing a particular room member.
struct RoomMembersListScreenManagementDetails: Identifiable {
var id: String { member.id }
/// The member that is being managed.
let member: RoomMemberDetails
/// A management action that can be performed on the member.
enum Action { case kick, ban }
/// The management actions available for `member`.
let actions: [Action]
}
enum RoomMembersListScreenViewAction { enum RoomMembersListScreenViewAction {
case selectMember(RoomMemberDetails) case selectMember(RoomMemberDetails)
case showMemberDetails(RoomMemberDetails) case showMemberDetails(RoomMemberDetails)
@@ -94,13 +116,3 @@ enum RoomMembersListScreenViewAction {
enum RoomMembersListScreenAlertType: Hashable { enum RoomMembersListScreenAlertType: Hashable {
case unbanConfirmation(RoomMemberDetails) case unbanConfirmation(RoomMemberDetails)
} }
private extension RoomMemberDetails {
func matches(searchQuery: String) -> Bool {
guard !searchQuery.isEmpty else {
return true
}
return id.localizedCaseInsensitiveContains(searchQuery) || name?.localizedCaseInsensitiveContains(searchQuery) ?? false
}
}
@@ -149,18 +149,32 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
} }
private func selectMember(_ member: RoomMemberDetails) { private func selectMember(_ member: RoomMemberDetails) {
if appSettings.roomModerationEnabled, state.canKickUsers || state.canBanUsers { guard appSettings.roomModerationEnabled else {
if member.isBanned { showMemberDetails(member)
state.bindings.alertInfo = AlertInfo(id: .unbanConfirmation(member), return
title: L10n.screenRoomMemberListManageMemberUnbanTitle, }
message: L10n.screenRoomMemberListManageMemberUnbanMessage,
primaryButton: .init(title: L10n.screenRoomMemberListManageMemberUnbanAction) { [weak self] in if member.isBanned { // No need to check canBan here, banned users are only shown when it is true.
self?.context.send(viewAction: .unbanMember(member)) state.bindings.alertInfo = AlertInfo(id: .unbanConfirmation(member),
}, title: L10n.screenRoomMemberListManageMemberUnbanTitle,
secondaryButton: .init(title: L10n.actionCancel, role: .cancel) { }) message: L10n.screenRoomMemberListManageMemberUnbanMessage,
} else { primaryButton: .init(title: L10n.screenRoomMemberListManageMemberUnbanAction) { [weak self] in
state.bindings.memberToManage = member self?.context.send(viewAction: .unbanMember(member))
} },
secondaryButton: .init(title: L10n.actionCancel, role: .cancel) { })
return
}
var actions = [RoomMembersListScreenManagementDetails.Action]()
if state.canKickUsers, member.role != .administrator {
actions.append(.kick)
}
if state.canBanUsers, member.role != .administrator {
actions.append(.ban)
}
if !actions.isEmpty {
state.bindings.memberToManage = .init(member: member, actions: actions)
} else { } else {
showMemberDetails(member) showMemberDetails(member)
} }
@@ -19,6 +19,8 @@ import SwiftUI
struct RoomMembersListManageMemberSheet: View { struct RoomMembersListManageMemberSheet: View {
let member: RoomMemberDetails let member: RoomMemberDetails
let actions: [RoomMembersListScreenManagementDetails.Action]
@ObservedObject var context: RoomMembersListScreenViewModel.Context @ObservedObject var context: RoomMembersListScreenViewModel.Context
@State private var isPresentingBanConfirmation = false @State private var isPresentingBanConfirmation = false
@@ -38,7 +40,7 @@ struct RoomMembersListManageMemberSheet: View {
context.send(viewAction: .showMemberDetails(member)) context.send(viewAction: .showMemberDetails(member))
}) })
if context.viewState.canKickUsers, !member.isBanned { if actions.contains(.kick) {
ListRow(label: .default(title: L10n.screenRoomMemberListManageMemberRemove, ListRow(label: .default(title: L10n.screenRoomMemberListManageMemberRemove,
icon: \.close), icon: \.close),
kind: .button { kind: .button {
@@ -46,7 +48,7 @@ struct RoomMembersListManageMemberSheet: View {
}) })
} }
if context.viewState.canBanUsers, !member.isBanned { if actions.contains(.ban) {
ListRow(label: .default(title: L10n.screenRoomMemberListManageMemberBan, ListRow(label: .default(title: L10n.screenRoomMemberListManageMemberBan,
icon: \.block, icon: \.block,
role: .destructive), role: .destructive),
@@ -76,11 +78,13 @@ struct RoomMembersListManageMemberSheet_Previews: PreviewProvider, TestablePrevi
static var previews: some View { static var previews: some View {
RoomMembersListManageMemberSheet(member: .init(withProxy: RoomMemberProxyMock.mockDan), RoomMembersListManageMemberSheet(member: .init(withProxy: RoomMemberProxyMock.mockDan),
actions: [.kick, .ban],
context: viewModel.context) context: viewModel.context)
.previewDisplayName("Joined") .previewDisplayName("Joined")
.snapshot(delay: 0.2) .snapshot(delay: 0.2)
RoomMembersListManageMemberSheet(member: .init(withProxy: RoomMemberProxyMock.mockBanned[3]), RoomMembersListManageMemberSheet(member: .init(withProxy: RoomMemberProxyMock.mockBanned[3]),
actions: [],
context: viewModel.context) context: viewModel.context)
.previewDisplayName("Banned") .previewDisplayName("Banned")
.snapshot(delay: 0.2) .snapshot(delay: 0.2)
@@ -94,6 +98,7 @@ struct RoomMembersListManageMemberSheetLive_Previews: PreviewProvider {
Color.clear Color.clear
.sheet(isPresented: .constant(true)) { .sheet(isPresented: .constant(true)) {
RoomMembersListManageMemberSheet(member: .init(withProxy: RoomMemberProxyMock.mockDan), RoomMembersListManageMemberSheet(member: .init(withProxy: RoomMemberProxyMock.mockDan),
actions: [.kick, .ban],
context: viewModel.context) context: viewModel.context)
} }
.previewDisplayName("Sheet") .previewDisplayName("Sheet")
@@ -49,13 +49,15 @@ struct RoomMembersListScreen: View {
.background(.compound.bgCanvasDefault) .background(.compound.bgCanvasDefault)
} }
} }
.searchable(text: $context.searchQuery, placement: .navigationBarDrawer(displayMode: .always)) .searchable(text: $context.searchQuery,
placement: .navigationBarDrawer(displayMode: .always),
prompt: L10n.commonSearchForSomeone)
.compoundSearchField() .compoundSearchField()
.autocorrectionDisabled() .autocorrectionDisabled()
.background(Color.compound.bgCanvasDefault.ignoresSafeArea()) .background(Color.compound.bgCanvasDefault.ignoresSafeArea())
.navigationTitle(L10n.commonPeople) .navigationTitle(L10n.commonPeople)
.sheet(item: $context.memberToManage) { .sheet(item: $context.memberToManage) {
RoomMembersListManageMemberSheet(member: $0, context: context) RoomMembersListManageMemberSheet(member: $0.member, actions: $0.actions, context: context)
} }
.alert(item: $context.alertInfo) .alert(item: $context.alertInfo)
.toolbar { toolbar } .toolbar { toolbar }
@@ -94,8 +94,8 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM
// MARK: - Members // MARK: - Members
private func updateMembers(_ members: [RoomMemberProxyProtocol]) { private func updateMembers(_ members: [RoomMemberProxyProtocol]) {
state.administratorCount = members.filter { $0.role == .administrator }.count state.administratorCount = members.filter { $0.role == .administrator && $0.isActive }.count
state.moderatorCount = members.filter { $0.role == .moderator }.count state.moderatorCount = members.filter { $0.role == .moderator && $0.isActive }.count
} }
private func updateOwnRole(_ role: RoomMemberDetails.Role) async { private func updateOwnRole(_ role: RoomMemberDetails.Role) async {
@@ -23,11 +23,17 @@ struct RoomMemberDetails: Identifiable, Hashable {
let avatarURL: URL? let avatarURL: URL?
let permalink: URL? let permalink: URL?
var isInvited: Bool
var isIgnored: Bool var isIgnored: Bool
var isBanned: Bool var isBanned: Bool
enum Role { case administrator, moderator, user } enum Role { case administrator, moderator, user }
let role: Role let role: Role
func matches(searchQuery: String) -> Bool {
guard !searchQuery.isEmpty else { return true }
return id.localizedStandardContains(searchQuery) || name?.localizedStandardContains(searchQuery) == true
}
} }
extension RoomMemberDetails { extension RoomMemberDetails {
@@ -37,6 +43,7 @@ extension RoomMemberDetails {
avatarURL = proxy.avatarURL avatarURL = proxy.avatarURL
permalink = proxy.permalink permalink = proxy.permalink
isInvited = proxy.membership == .invite
isIgnored = proxy.isIgnored isIgnored = proxy.isIgnored
isBanned = proxy.membership == .ban isBanned = proxy.membership == .ban
role = .init(proxy.role) role = .init(proxy.role)
@@ -48,6 +55,7 @@ extension RoomMemberDetails {
avatarURL = nil avatarURL = nil
permalink = nil permalink = nil
isInvited = false
isIgnored = false isIgnored = false
isBanned = false isBanned = false
role = .user role = .user
@@ -31,6 +31,11 @@ protocol RoomMemberProxyProtocol: AnyObject {
} }
extension RoomMemberProxyProtocol { extension RoomMemberProxyProtocol {
/// The member is active in the room (joined or invited).
var isActive: Bool {
membership == .join || membership == .invite
}
var permalink: URL? { var permalink: URL? {
try? PermalinkBuilder.permalinkTo(userIdentifier: userID, try? PermalinkBuilder.permalinkTo(userIdentifier: userID,
baseURL: ServiceLocator.shared.settings.permalinkBaseURL) baseURL: ServiceLocator.shared.settings.permalinkBaseURL)
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:0c7d3ddb84e8a174c27229f31bf494b120974b261344fef7b5450d42cc2803e9 oid sha256:8f8a5ad7a853f08fa0624ca707c41b56f7fbc2075ac66631c32e0e3118ea9b9a
size 209697 size 207428
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:100cb0d5858afe432962fac47fc2d7c7fba630906641c62a966594aed7d2bd73 oid sha256:6c2bfebcfb57baa6582ca914e3380ca382b4017c758b7c7b2af90a9680e7ddbb
size 214948 size 206724
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:237abad46072702cfac2c1e6b4f02acb17d8aa7eb65a227646864bf6a5d2fa5a oid sha256:89d3928a5ae405e6f1b695044b54f94aedb3ddfce583b6ba2e2b667c4eeba157
size 229595 size 233785
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:d4ff8f0b3b8f3829d2c986d81c2074ecaf5737e7bc12373f6fc5378ef4f3625a oid sha256:2dd12a574b0287d8f3d4342fb207c76321fa2e8cf9b8f9773463ee1686f13211
size 236011 size 234107
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:7d46021802ef0edebab273fe747f567e2637b36e0e2637f596747d989256a388 oid sha256:143c3399a49fd85bedeee78a0f739189208a2500b339afc07cc5bc6cc99ed686
size 148810 size 156940
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:facb67a406d2932c70baf4873ff217d1267e29100bd9dcd11235f8d097b4291a oid sha256:92fe524e9a4fb9115fadc5e43f5f89d950573d6a0750fb83e87f61a729781a06
size 152563 size 156764
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:fa33656ad1cad730ed07709c2529a70931b9e8d8b26f43d947e4143e7034b6bc oid sha256:dc861092e7490dbd5fe06a541dbcd1dd46aa378dd41015e6e6619f0158f88843
size 166950 size 180421
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:56767f44df9037d6d089761ad9a1424e6859fc4095b5479c4726fda566871134 oid sha256:274993576e92cd103dced5b43595d4ae234811a49a0d20cf4fffc58fbf0bed7b
size 169884 size 179459
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:7fe77092cf19d88dd1e8da9aca24e6cfab3e9abacfa19574a6cdcd51b29a5f5c oid sha256:9d015139ff3d614fe612060872d9214c5215255f11363152232ffe75a5ed9a79
size 149249 size 171955
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:7fe77092cf19d88dd1e8da9aca24e6cfab3e9abacfa19574a6cdcd51b29a5f5c oid sha256:bf8bd2bedda7e59353aaf9bcd52fdd3c9cd247e530ceec7e629fcc1f930f23bc
size 149249 size 179850
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:caa5c7bd6c8781e700ba2ac1eafe1ecd12776892ad3a3f956e923085be35ce80 oid sha256:a5f00e0da1089b66c6cf8c256bec3adf8ccd11b51569cb19258c97eec3f7b5ba
size 94285 size 112584
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:caa5c7bd6c8781e700ba2ac1eafe1ecd12776892ad3a3f956e923085be35ce80 oid sha256:b9cd8c744acbd7733f3099ec865aa0d3205136f3f2bef09399a53d798af4409b
size 94285 size 116515
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:3b86cce3ab068ea93f4647ec5efb8762e2b92edb78c2d647d9621486c3afa7f5 oid sha256:f74610196ad9da46013aa7dcfd0f0a64051b3552872993a9bb54f64419eccdb7
size 136479 size 141774
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:20046627aca878cee89c1d1fb6d2370c9841312d909b5db3cb8f8d85866e4cc3 oid sha256:68034d3beee6bbd4089c9f19c19485356ff9e5bf23ade9e7838a57ae287b35ac
size 101950 size 107276
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e0104abccb916e6004eadbf6020f66ce504c168b9d03abb91e618a50b0c8b6a3 oid sha256:bf414829dcf5f6885632b608f2cceccc15ab8418f7fc93d0d56e1b488834e804
size 163769 size 169215
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:4fc85d0fcfecb4de8b49c6dff26c8baaf8371c6c7e467199c1749aff308184c1 oid sha256:d2f1d162b91fbf5f764e57bb79f843c21c65c5be2e488448011cf1d21921f219
size 172433 size 177644
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:2f957ed20a375c6f117c41efa9267b31b0a0ea35942dc6206b09641024e8df0b oid sha256:54c647dd14050cab70988eb4e3802ce23d2e04427a4144a441f82cf806291acb
size 155295 size 160550
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:b52ddbd7ffc13e7a519448fa478f61d8cea0bfc5e2852266d6e4c2352ff92122 oid sha256:3b2eb891016995b39e23cfc777fe531f3927039465983468ccaad42726a85f49
size 148556 size 159267
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:a527ddb8a3db4b7e4fa0b89a05b4f2a67ce6ecb65498eb21f828f2cffdc283ba oid sha256:357301e28ea588a5d38fc63d4eb781c580d96e629cbdde675dd35d788452183d
size 126125 size 136693
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:25bf9187d0523df76dff77b6b4d4a42d660a7c920a7cdf2df5681be8e811549f oid sha256:11cb56e13df8abe892264775ee64a89f389e022db0376899b24bd6fb42856a54
size 192470 size 203181
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:2eaef305bab6997df7f1d953d7a1d1a440c720fa4c53988012183d6fae52b708 oid sha256:bf75b5dac24845f7461e2ea5de0d2818470cfd5e779331129975204365e0ad6d
size 200534 size 211131
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:ea48a2079bd5adb7eda03aea9d1c5ccd2be39ae22ad93cf383495938cf27da5c oid sha256:bebf9d38ab727bfc5d206ebaef2e75c8ab6ad6d7546c22be7b0a0a19dbd4c160
size 179530 size 190293
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:dde8c1b0aac2644af48ab8d9a5c2eef5119580c79e7b1a7d8c3228c79c3a04d7 oid sha256:66ed2cdca4cc96d6aa3ed59081c1c42349f057fb30db951c0fff123db97e8f90
size 83855 size 87673
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:0798e87d35e3fc0610017b6829345f4e3446a6c6fa038f8c38919c4e4aee3cbc oid sha256:0129b9f9f9c770c9d78d73b0ec73ac65aaa2cce9cb30ef86e737b21a6ce40ca2
size 57564 size 60783
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:03f41bbbdefb7b6e58c79545a1bf718a8834f51c08589df5d779fb1bdd9814ec oid sha256:3b39de84f6fb238b7b959c78185c02c7c9e77f67eaf0ccad8918da10687da0f4
size 112153 size 115880
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:6e0761e72763b69108207802bd930f8fdbf0188d58de7c7617dab9ea095430b2 oid sha256:ea9be731b726d1ca46b07b7e45fed591471f4ebb69213e64008227aa65a7e45e
size 120230 size 124143
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:ced785329bc86c7c25e0f3d4e77be7cdf686a7334f0577c577aa76fbd8a01e16 oid sha256:a8c29737d601d4b70d46783fb28668fd80e1e3e4aeafa10b84b13708d2b4f003
size 106031 size 110383
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:ec65f2a73c8cd5ad03afc7bf189679044459d84e4aaecfed39e2a555f2b060e7 oid sha256:dc386629fb566cc3aa1e35af1d9001cceb9aab298077cc12625d1eadb729c5bb
size 95750 size 103518
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:1ae66cafa25f4b6bfc0bdca91d5ddaf2b6bec6ffbd2b7015c2f1cceed8dd3035 oid sha256:f37e99938197b30df7787756b70a8d4a927a922db3915dfb3f1f64cc91ba1365
size 79294 size 87405
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:83a65b1e1d6881fcdfdcc485e3ce4a986114789219f313b88702b0ce495808e9 oid sha256:8f0f9906b81e1ab0a278271f1a03985ec2b5cc357b7e55175fbf50b199d17e6a
size 136571 size 144193
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:0227511fe99e1b9bed944bb8a3d995de7bf9da25d5c2d194cc65eb3b0d2bf732 oid sha256:f7f31147866db8ee3903500b7f2d15e03052721a2adbbfbe0800ccea77cd7344
size 144931 size 152549
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:78e794288debdba5b8e6abaaf7ac3f6c482342d4f07e87c50bd1999582f85be0 oid sha256:d9e9a7f574bb4036e36a9a6e808e3da606924e62c879392c394d85fd1017d4a9
size 127540 size 136611
@@ -31,8 +31,10 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
setupViewModel(mode: .administrator) setupViewModel(mode: .administrator)
XCTAssertEqual(context.viewState.membersToPromote, []) XCTAssertEqual(context.viewState.membersToPromote, [])
XCTAssertEqual(context.viewState.membersToDemote, []) XCTAssertEqual(context.viewState.membersToDemote, [])
XCTAssertEqual(context.viewState.members, context.viewState.visibleMembers) XCTAssertEqual(context.viewState.administrators, context.viewState.visibleAdministrators)
XCTAssertEqual(context.viewState.membersWithRole.count, 1) XCTAssertEqual(context.viewState.moderators, context.viewState.visibleModerators)
XCTAssertEqual(context.viewState.users, context.viewState.visibleUsers)
XCTAssertEqual(context.viewState.membersWithRole.count, 2)
XCTAssertEqual(context.viewState.membersWithRole.first?.id, RoomMemberProxyMock.mockAdmin.userID) XCTAssertEqual(context.viewState.membersWithRole.first?.id, RoomMemberProxyMock.mockAdmin.userID)
XCTAssertFalse(context.viewState.hasChanges) XCTAssertFalse(context.viewState.hasChanges)
XCTAssertFalse(context.viewState.isSearching) XCTAssertFalse(context.viewState.isSearching)
@@ -42,7 +44,9 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
setupViewModel(mode: .moderator) setupViewModel(mode: .moderator)
XCTAssertEqual(context.viewState.membersToPromote, []) XCTAssertEqual(context.viewState.membersToPromote, [])
XCTAssertEqual(context.viewState.membersToDemote, []) XCTAssertEqual(context.viewState.membersToDemote, [])
XCTAssertEqual(context.viewState.members, context.viewState.visibleMembers) XCTAssertEqual(context.viewState.administrators, context.viewState.visibleAdministrators)
XCTAssertEqual(context.viewState.moderators, context.viewState.visibleModerators)
XCTAssertEqual(context.viewState.users, context.viewState.visibleUsers)
XCTAssertEqual(context.viewState.membersWithRole.count, 1) XCTAssertEqual(context.viewState.membersWithRole.count, 1)
XCTAssertEqual(context.viewState.membersWithRole.first?.id, RoomMemberProxyMock.mockModerator.userID) XCTAssertEqual(context.viewState.membersWithRole.first?.id, RoomMemberProxyMock.mockModerator.userID)
XCTAssertFalse(context.viewState.hasChanges) XCTAssertFalse(context.viewState.hasChanges)
@@ -51,7 +55,7 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
func testToggleUserOn() { func testToggleUserOn() {
testInitialStateModerators() testInitialStateModerators()
guard let firstUser = context.viewState.members.first(where: { !context.viewState.isMemberSelected($0) }) else { guard let firstUser = context.viewState.users.first(where: { !context.viewState.isMemberSelected($0) }) else {
XCTFail("There should be a regular user available to promote.") XCTFail("There should be a regular user available to promote.")
return return
} }
@@ -150,7 +154,7 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
// Given the change roles view model for moderators. // Given the change roles view model for moderators.
setupViewModel(mode: .moderator) setupViewModel(mode: .moderator)
guard let firstUser = context.viewState.members.first(where: { !context.viewState.isMemberSelected($0) }), guard let firstUser = context.viewState.users.first(where: { !context.viewState.isMemberSelected($0) }),
let existingModerator = context.viewState.membersWithRole.first else { let existingModerator = context.viewState.membersWithRole.first else {
XCTFail("There should be a regular user and a moderator to begin with.") XCTFail("There should be a regular user and a moderator to begin with.")
return return
@@ -175,7 +179,7 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
setupViewModel(mode: .administrator) setupViewModel(mode: .administrator)
XCTAssertNil(context.alertInfo) XCTAssertNil(context.alertInfo)
guard let firstUser = context.viewState.members.first(where: { !context.viewState.isMemberSelected($0) }) else { guard let firstUser = context.viewState.users.first(where: { !context.viewState.isMemberSelected($0) }) else {
XCTFail("There should be a regular user to begin with.") XCTFail("There should be a regular user to begin with.")
return return
} }
@@ -126,8 +126,128 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
XCTAssertEqual(viewModel.state.visibleJoinedMembers.count, 0) XCTAssertEqual(viewModel.state.visibleJoinedMembers.count, 0)
} }
func testKickMember() async throws { func testSelectUserAsUser() async throws {
// Given the room list viewed as a regular user.
setup(with: .allMembers) setup(with: .allMembers)
let deferred = deferFulfillment(context.$viewState) { !$0.visibleInvitedMembers.isEmpty }
try await deferred.fulfill()
// When tapping on another user in the list.
let memberDetailsAction = deferFulfillment(viewModel.actions) { $0.isSelectMember }
guard let user = viewModel.state.visibleJoinedMembers.first(where: { $0.role == .user && $0.id != RoomMemberProxyMock.mockMe.userID }) else {
XCTFail("Expected to find a regular user.")
return
}
context.send(viewAction: .selectMember(user))
// Then the member's details should be shown.
try await memberDetailsAction.fulfill()
XCTAssertNil(context.memberToManage)
}
func testSelectUserAsAdmin() async throws {
// Given the room list viewed as an admin.
setup(with: .allMembersAsAdmin)
var deferred = deferFulfillment(context.$viewState) { !$0.visibleInvitedMembers.isEmpty }
try await deferred.fulfill()
XCTAssertNil(context.memberToManage)
// When tapping on a user in the list.
deferred = deferFulfillment(context.$viewState) { $0.bindings.memberToManage != nil }
guard let user = viewModel.state.visibleJoinedMembers.first(where: { $0.role == .user && $0.id != RoomMemberProxyMock.mockMe.userID }) else {
XCTFail("Expected to find a regular user.")
return
}
context.send(viewAction: .selectMember(user))
try await deferred.fulfill()
// Then member management should be shown for that user.
XCTAssertEqual(context.memberToManage?.member, user)
XCTAssertEqual(context.memberToManage?.actions, [.kick, .ban])
}
func testSelectModeratorAsAdmin() async throws {
// Given the room list viewed as an admin.
setup(with: .allMembersAsAdmin)
var deferred = deferFulfillment(context.$viewState) { !$0.visibleInvitedMembers.isEmpty }
try await deferred.fulfill()
XCTAssertNil(context.memberToManage)
// When tapping on a moderator in the list.
deferred = deferFulfillment(context.$viewState) { $0.bindings.memberToManage != nil }
guard let moderator = viewModel.state.visibleJoinedMembers.first(where: { $0.role == .moderator }) else {
XCTFail("Expected to find a moderator.")
return
}
context.send(viewAction: .selectMember(moderator))
try await deferred.fulfill()
// Then member management should be shown for the moderator.
XCTAssertEqual(context.memberToManage?.member, moderator)
XCTAssertEqual(context.memberToManage?.actions, [.kick, .ban])
}
func testSelectAdminAsAdmin() async throws {
// Given the room list viewed as an admin.
setup(with: .allMembersAsAdmin)
let deferred = deferFulfillment(context.$viewState) { !$0.visibleInvitedMembers.isEmpty }
try await deferred.fulfill()
// When tapping on another administrator in the list.
let memberDetailsAction = deferFulfillment(viewModel.actions) { $0.isSelectMember }
guard let admin = viewModel.state.visibleJoinedMembers.first(where: { $0.role == .administrator && $0.id != RoomMemberProxyMock.mockMe.userID }) else {
XCTFail("Expected to find another admin.")
return
}
context.send(viewAction: .selectMember(admin))
// Then the administrator's details should be shown.
try await memberDetailsAction.fulfill()
XCTAssertNil(context.memberToManage)
}
func testSelectOwnMemberAsAdmin() async throws {
// Given the room list viewed as an admin.
setup(with: .allMembersAsAdmin)
let deferred = deferFulfillment(context.$viewState) { !$0.visibleInvitedMembers.isEmpty }
try await deferred.fulfill()
// When tapping on yourself in the list.
let memberDetailsAction = deferFulfillment(viewModel.actions) { $0.isSelectMember }
guard let ownMember = viewModel.state.visibleJoinedMembers.first(where: { $0.id == RoomMemberProxyMock.mockMe.userID }) else {
XCTFail("Expected to find own user admin.")
return
}
context.send(viewAction: .selectMember(ownMember))
// Then your member's details should be shown.
try await memberDetailsAction.fulfill()
XCTAssertNil(context.memberToManage)
}
func testSelectBannedMember() async throws {
// Given the room list viewed as an admin.
setup(with: .allMembersAsAdmin + RoomMemberProxyMock.mockBanned)
var deferred = deferFulfillment(context.$viewState) { !$0.visibleInvitedMembers.isEmpty }
try await deferred.fulfill()
XCTAssertNil(context.alertInfo)
// When tapping on a banned member in the list.
deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil }
guard let bannedMember = viewModel.state.visibleBannedMembers.first else {
XCTFail("Expected to find a banned user.")
return
}
context.send(viewAction: .selectMember(bannedMember))
// Then an alert should be shown to unban the user.
try await deferred.fulfill()
XCTAssertNil(context.memberToManage)
XCTAssertNotNil(context.alertInfo)
}
func testKickMember() async throws {
setup(with: .allMembersAsAdmin)
let deferred = deferFulfillment(context.$viewState) { !$0.visibleJoinedMembers.isEmpty } let deferred = deferFulfillment(context.$viewState) { !$0.visibleJoinedMembers.isEmpty }
try await deferred.fulfill() try await deferred.fulfill()
@@ -140,7 +260,7 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
} }
func testBanMember() async throws { func testBanMember() async throws {
setup(with: .allMembers) setup(with: .allMembersAsAdmin)
let deferred = deferFulfillment(context.$viewState) { !$0.visibleJoinedMembers.isEmpty } let deferred = deferFulfillment(context.$viewState) { !$0.visibleJoinedMembers.isEmpty }
try await deferred.fulfill() try await deferred.fulfill()
@@ -153,7 +273,7 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
} }
func testUnbanMember() async throws { func testUnbanMember() async throws {
setup(with: .allMembers) setup(with: .allMembersAsAdmin)
let deferred = deferFulfillment(context.$viewState) { !$0.visibleJoinedMembers.isEmpty } let deferred = deferFulfillment(context.$viewState) { !$0.visibleJoinedMembers.isEmpty }
try await deferred.fulfill() try await deferred.fulfill()
@@ -166,6 +286,9 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
} }
private func setup(with members: [RoomMemberProxyMock]) { private func setup(with members: [RoomMemberProxyMock]) {
AppSettings.resetAllSettings()
ServiceLocator.shared.settings.roomModerationEnabled = true
roomProxy = RoomProxyMock(with: .init(name: "test", members: members)) roomProxy = RoomProxyMock(with: .init(name: "test", members: members))
viewModel = .init(roomProxy: roomProxy, viewModel = .init(roomProxy: roomProxy,
mediaProvider: MockMediaProvider(), mediaProvider: MockMediaProvider(),
+1
View File
@@ -0,0 +1 @@
Bug fixes on the moderation feature.