mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Last Owner should edit admins, and not leave when is last (#4372)
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 77;
|
||||
objectVersion = 63;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXAggregateTarget section */
|
||||
@@ -905,6 +905,7 @@
|
||||
A6FFC4C5154C446BAD6B40D8 /* TimelineItemProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8520AFD6680CBAD388F6D927 /* TimelineItemProvider.swift */; };
|
||||
A722F426FD81FC67706BB1E0 /* CustomLayoutLabelStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42236480CF0431535EBE8387 /* CustomLayoutLabelStyle.swift */; };
|
||||
A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */; };
|
||||
A7AF83892E4388BE00ECD64C /* RoomRole.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7AF83882E4388BB00ECD64C /* RoomRole.swift */; };
|
||||
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; };
|
||||
A808DC3F72D15C6C5A52317E /* TimelineItemDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCDA016D05107DED3B9495CB /* TimelineItemDebugView.swift */; };
|
||||
A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6E30BB748F3F480F077969 /* RoomMemberDetailsScreenModels.swift */; };
|
||||
@@ -1459,7 +1460,7 @@
|
||||
044E501B8331B339874D1B96 /* CompoundIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompoundIcon.swift; sourceTree = "<group>"; };
|
||||
045253F9967A535EE5B16691 /* Label.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Label.swift; sourceTree = "<group>"; };
|
||||
046C0D3F53B0B5EF0A1F5BEA /* RoomSummaryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryTests.swift; sourceTree = "<group>"; };
|
||||
048A21188AB19349D026BECD /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
048A21188AB19349D026BECD /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
04BB8DDE245ED86C489BA983 /* AccessibilityIdentifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityIdentifiers.swift; sourceTree = "<group>"; };
|
||||
04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManager.swift; sourceTree = "<group>"; };
|
||||
0516C69708D5CBDE1A8E77EC /* RoomDirectorySearchProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchProxyProtocol.swift; sourceTree = "<group>"; };
|
||||
@@ -1534,7 +1535,7 @@
|
||||
128501375217576AF0FE3E92 /* RoomAttachmentPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomAttachmentPicker.swift; sourceTree = "<group>"; };
|
||||
12B09A94C519227264A41208 /* RoomMembershipDetailsProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembershipDetailsProxy.swift; sourceTree = "<group>"; };
|
||||
12FD5280AF55AB7F50F8E47D /* preview_avatar_room.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = preview_avatar_room.jpg; sourceTree = "<group>"; };
|
||||
1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; path = IntegrationTests.xctestplan; sourceTree = "<group>"; };
|
||||
1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = IntegrationTests.xctestplan; sourceTree = "<group>"; };
|
||||
130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNTextInputNotificationResponse+Creator.swift"; sourceTree = "<group>"; };
|
||||
136F80A613B55BDD071DCEA5 /* JoinRoomScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRoomScreenModels.swift; sourceTree = "<group>"; };
|
||||
13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
@@ -1642,7 +1643,7 @@
|
||||
25F7FE40EF7490A7E09D7BE6 /* NotificationItemProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationItemProxy.swift; sourceTree = "<group>"; };
|
||||
25F8664F1FB95AF3C4202478 /* PollFormScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollFormScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
260004737C573A56FA01E86E /* Encodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encodable.swift; sourceTree = "<group>"; };
|
||||
267BB1D5B08A9511F894CB57 /* PreviewTests.xctestplan */ = {isa = PBXFileReference; path = PreviewTests.xctestplan; sourceTree = "<group>"; };
|
||||
267BB1D5B08A9511F894CB57 /* PreviewTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = PreviewTests.xctestplan; sourceTree = "<group>"; };
|
||||
26B0A96B8FE4849227945067 /* VoiceMessageRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecorder.swift; sourceTree = "<group>"; };
|
||||
26EAAB54C6CE91D64B69A9F8 /* AppLockServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockServiceProtocol.swift; sourceTree = "<group>"; };
|
||||
2711E5996016ABD6EAAEB58A /* LogLevel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogLevel.swift; sourceTree = "<group>"; };
|
||||
@@ -1723,7 +1724,7 @@
|
||||
3558A15CFB934F9229301527 /* RestorationToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestorationToken.swift; sourceTree = "<group>"; };
|
||||
35A057BA9BE0F079784CD061 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
35AFCF4C05DEED04E3DB1A16 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
36DA824791172B9821EACBED /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
36DA824791172B9821EACBED /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
36FD673E24FBFCFDF398716A /* RoomMemberProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberProxyMock.swift; sourceTree = "<group>"; };
|
||||
37A63A59BFDDC494B1C20119 /* CallScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
37CA26F55123E36B50DB0B3A /* AttributedStringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringTests.swift; sourceTree = "<group>"; };
|
||||
@@ -2148,7 +2149,7 @@
|
||||
8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStateEventStringBuilder.swift; sourceTree = "<group>"; };
|
||||
8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
8DA1E8F287680C8ED25EDBAC /* NetworkMonitorMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitorMock.swift; sourceTree = "<group>"; };
|
||||
8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = "<group>"; };
|
||||
8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = "<group>"; };
|
||||
8E1584F8BCF407BB94F48F04 /* EncryptionResetPasswordScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreen.swift; sourceTree = "<group>"; };
|
||||
8EAF4A49F3ACD8BB8B0D2371 /* ClientSDKMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientSDKMock.swift; sourceTree = "<group>"; };
|
||||
8F062DD2CCD95DC33528A16F /* KnockRequestProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KnockRequestProxy.swift; sourceTree = "<group>"; };
|
||||
@@ -2270,6 +2271,7 @@
|
||||
A768CA51A59B8A5D8C8FD599 /* AuthenticationStartScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationStartScreen.swift; sourceTree = "<group>"; };
|
||||
A7978C9EFBDD7DE39BD86726 /* RestorationTokenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestorationTokenTests.swift; sourceTree = "<group>"; };
|
||||
A7A1B80FE6E3BA72F9C748AD /* AdvancedSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
A7AF83882E4388BB00ECD64C /* RoomRole.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomRole.swift; sourceTree = "<group>"; };
|
||||
A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = "<group>"; };
|
||||
A7D452AF7B5F7E3A0A7DB54C /* SessionVerificationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
A7E37072597F67C4DD8CC2DB /* ComposerDraftServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerDraftServiceProtocol.swift; sourceTree = "<group>"; };
|
||||
@@ -2290,7 +2292,7 @@
|
||||
AAD8234D0E9C9B12BF9F240B /* LocationAnnotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationAnnotation.swift; sourceTree = "<group>"; };
|
||||
AB07F03461023BC39C730922 /* PhishingDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhishingDetector.swift; sourceTree = "<group>"; };
|
||||
AB26D5444A4A7E095222DE8B /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||
AB389C38BD41EB3E47092CFB /* AccessibilityTests.xctestplan */ = {isa = PBXFileReference; path = AccessibilityTests.xctestplan; sourceTree = "<group>"; };
|
||||
AB389C38BD41EB3E47092CFB /* AccessibilityTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = AccessibilityTests.xctestplan; sourceTree = "<group>"; };
|
||||
ABA4CF2F5B4F68D02E412004 /* ServerConfirmationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
AC0275CEE9CA078B34028BDF /* AppLockScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||
AC1DA29A5A041CC0BACA7CB0 /* MockImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockImageCache.swift; sourceTree = "<group>"; };
|
||||
@@ -2355,7 +2357,7 @@
|
||||
B53AC78E49A297AC1D72A7CF /* AppMediator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppMediator.swift; sourceTree = "<group>"; };
|
||||
B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = "<group>"; };
|
||||
B5D829FD8958376614504B18 /* TargetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TargetConfiguration.swift; sourceTree = "<group>"; };
|
||||
B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = "<group>"; };
|
||||
B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = ConfettiScene.scn; sourceTree = "<group>"; };
|
||||
B6404166CBF5CC88673FF9E2 /* RoomDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetails.swift; sourceTree = "<group>"; };
|
||||
B65DDCF8E41759890355ACBC /* AuthenticationStartScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationStartScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
B68B31232312AFC844440BFE /* DeclineAndBlockScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeclineAndBlockScreenModels.swift; sourceTree = "<group>"; };
|
||||
@@ -2383,7 +2385,7 @@
|
||||
BA40B98B098B6F0371B750B3 /* TemplateScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenModels.swift; sourceTree = "<group>"; };
|
||||
BA919F521E9F0EE3638AFC85 /* BugReportScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreen.swift; sourceTree = "<group>"; };
|
||||
BB284643AF7AB131E307DCE0 /* AudioSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioSessionProtocol.swift; sourceTree = "<group>"; };
|
||||
BB576F4118C35E6B5124FA22 /* test_apple_image.heic */ = {isa = PBXFileReference; path = test_apple_image.heic; sourceTree = "<group>"; };
|
||||
BB576F4118C35E6B5124FA22 /* test_apple_image.heic */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_apple_image.heic; sourceTree = "<group>"; };
|
||||
BB5B00A014307CE37B2812CD /* TimelineViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
BB6ED50FE104992419310EEB /* NotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHandler.swift; sourceTree = "<group>"; };
|
||||
BB8BC4C791D0E88CFCF4E5DF /* ServerSelectionScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
@@ -2481,7 +2483,7 @@
|
||||
CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = "<group>"; };
|
||||
CDE3F3911FF7CC639BDE5844 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
CEE20623EB4A9B88FB29F2BA /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/SAS.strings; sourceTree = "<group>"; };
|
||||
CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = "<group>"; };
|
||||
CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = "<group>"; };
|
||||
CFFA5E881D281810AB428EA3 /* RoomPowerLevelsProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPowerLevelsProxy.swift; sourceTree = "<group>"; };
|
||||
D01FD1171FF40E34D707FD00 /* BigIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BigIcon.swift; sourceTree = "<group>"; };
|
||||
D03D7ECAC68C2FFB8CF01BCB /* DeactivateAccountScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeactivateAccountScreen.swift; sourceTree = "<group>"; };
|
||||
@@ -2545,7 +2547,7 @@
|
||||
DC0AEA686E425F86F6BA0404 /* UNNotification+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNNotification+Creator.swift"; sourceTree = "<group>"; };
|
||||
DC10CCC8D68B863E20660DBC /* MessageForwardingScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
DC528B3764E3CF7FCFEF40E7 /* PollInteractionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollInteractionHandler.swift; sourceTree = "<group>"; };
|
||||
DCA2D836BD10303F37FAAEED /* test_voice_message.m4a */ = {isa = PBXFileReference; path = test_voice_message.m4a; sourceTree = "<group>"; };
|
||||
DCA2D836BD10303F37FAAEED /* test_voice_message.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_voice_message.m4a; sourceTree = "<group>"; };
|
||||
DCAC01A97A43BE07B9E94E43 /* ShareExtensionModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareExtensionModels.swift; sourceTree = "<group>"; };
|
||||
DCDAB580109C09A6AA97AF7E /* PollFormScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollFormScreenTests.swift; sourceTree = "<group>"; };
|
||||
DCF239C619971FDE48132550 /* SecureBackupLogoutConfirmationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenModels.swift; sourceTree = "<group>"; };
|
||||
@@ -2591,7 +2593,7 @@
|
||||
E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = "<group>"; };
|
||||
E53BFB7E4F329621C844E8C3 /* AnalyticsPromptScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreen.swift; sourceTree = "<group>"; };
|
||||
E55B5EA766E89FF1F87C3ACB /* RoomNotificationSettingsProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsProxyProtocol.swift; sourceTree = "<group>"; };
|
||||
E5E7D4EE7CA295E5039FDA21 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; path = portrait_test_video.mp4; sourceTree = "<group>"; };
|
||||
E5E7D4EE7CA295E5039FDA21 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = portrait_test_video.mp4; sourceTree = "<group>"; };
|
||||
E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerProtocol.swift; sourceTree = "<group>"; };
|
||||
E5F2B6443D1ED8602F328539 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ru.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
E5FDFAA04174CC99FB66391C /* EditRoomAddressScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditRoomAddressScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
@@ -2635,7 +2637,7 @@
|
||||
ED0CBEAB5F796BEFBAF7BB6A /* VideoRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; };
|
||||
ED33988DA4FD4FC666800106 /* SessionVerificationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = "<group>"; };
|
||||
ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = message.caf; sourceTree = "<group>"; };
|
||||
ED49073BB1C1FC649DAC2CCD /* LocationRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
ED60E4D2CD678E1EBF16F77A /* BlockedUsersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreen.swift; sourceTree = "<group>"; };
|
||||
EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = "<group>"; };
|
||||
@@ -3755,6 +3757,7 @@
|
||||
40E6246F03D1FE377BC5D963 /* Room */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A7AF83882E4388BB00ECD64C /* RoomRole.swift */,
|
||||
C30F45308428A4D9FFDB2FB8 /* BannedRoomProxy.swift */,
|
||||
0E95B3BDB80531C85CD50AE6 /* InvitedRoomProxy.swift */,
|
||||
07C6B0B087FE6601C3F77816 /* JoinedRoomProxy.swift */,
|
||||
@@ -6699,7 +6702,6 @@
|
||||
EC6D0C817B1C21D9D096505A /* XCRemoteSwiftPackageReference "Version" */,
|
||||
EE40B0E16A55BD23ECBFFD22 /* XCRemoteSwiftPackageReference "matrix-rich-text-editor-swift" */,
|
||||
);
|
||||
preferredProjectObjectVersion = 77;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
@@ -7899,6 +7901,7 @@
|
||||
617624A97BDBB75ED3DD8156 /* RoomScreenViewModelProtocol.swift in Sources */,
|
||||
66832DE7B5C2E861045265DC /* RoomSelectionScreen.swift in Sources */,
|
||||
18FDE4ED6D83B0771452B43D /* RoomSelectionScreenCoordinator.swift in Sources */,
|
||||
A7AF83892E4388BE00ECD64C /* RoomRole.swift in Sources */,
|
||||
27FEF0F40750465195C9D6D6 /* RoomSelectionScreenModels.swift in Sources */,
|
||||
8DCA1F05C3BA6ED826F1599D /* RoomSelectionScreenViewModel.swift in Sources */,
|
||||
39DFC4B9EB6A8757210BDEC6 /* RoomSelectionScreenViewModelProtocol.swift in Sources */,
|
||||
@@ -8446,9 +8449,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = (
|
||||
"-DRELEASE",
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "-DRELEASE";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.accessibility.tests";
|
||||
PRODUCT_NAME = AccessibilityTests;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -8467,9 +8468,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = (
|
||||
"-DDEBUG",
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "-DDEBUG";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.accessibility.tests";
|
||||
PRODUCT_NAME = AccessibilityTests;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -8491,9 +8490,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = "$(MARKETING_VERSION)";
|
||||
OTHER_SWIFT_FLAGS = (
|
||||
"-DIS_NSE",
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "-DIS_NSE";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.nse";
|
||||
PRODUCT_DISPLAY_NAME = "$(APP_DISPLAY_NAME)";
|
||||
PRODUCT_NAME = NSE;
|
||||
@@ -8542,9 +8539,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = "$(MARKETING_VERSION)";
|
||||
OTHER_SWIFT_FLAGS = (
|
||||
"-DIS_MAIN_APP",
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "-DIS_MAIN_APP";
|
||||
PILLS_UT_TYPE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).pills";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(APP_NAME)";
|
||||
@@ -8570,9 +8565,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = "$(MARKETING_VERSION)";
|
||||
OTHER_SWIFT_FLAGS = (
|
||||
"-DIS_MAIN_APP",
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "-DIS_MAIN_APP";
|
||||
PILLS_UT_TYPE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).pills";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(APP_NAME)";
|
||||
@@ -8797,9 +8790,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = (
|
||||
"-DDEBUG",
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "-DDEBUG";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.ui.tests";
|
||||
PRODUCT_NAME = UITests;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -8818,9 +8809,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = (
|
||||
"-DRELEASE",
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "-DRELEASE";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.ui.tests";
|
||||
PRODUCT_NAME = UITests;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -8842,9 +8831,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = "$(MARKETING_VERSION)";
|
||||
OTHER_SWIFT_FLAGS = (
|
||||
"-DIS_NSE",
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "-DIS_NSE";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.nse";
|
||||
PRODUCT_DISPLAY_NAME = "$(APP_DISPLAY_NAME)";
|
||||
PRODUCT_NAME = NSE;
|
||||
|
||||
@@ -242,6 +242,10 @@
|
||||
"common_poll_undisclosed_text" = "Results will show after the poll has ended";
|
||||
"common_preparing" = "Preparing…";
|
||||
"common_privacy_policy" = "Privacy policy";
|
||||
"common_private_room" = "Private room";
|
||||
"common_private_space" = "Private space";
|
||||
"common_public_room" = "Public room";
|
||||
"common_public_space" = "Public space";
|
||||
"common_reaction" = "Reaction";
|
||||
"common_reactions" = "Reactions";
|
||||
"common_reason" = "Reason";
|
||||
@@ -556,6 +560,7 @@
|
||||
"screen_room_pinned_banner_view_all_button_title" = "View All";
|
||||
"screen_room_single_knock_request_title" = "%1$@ wants to join this room";
|
||||
"screen_room_single_knock_request_view_button_title" = "View";
|
||||
"screen_room_change_role_administrators_or_owners_title" = "Edit Admins or Owners";
|
||||
"screen_room_details_pinned_events_row_title" = "Pinned messages";
|
||||
"screen_room_details_profile_row_title" = "Profile";
|
||||
"screen_room_details_requests_to_join_title" = "Requests to join";
|
||||
@@ -714,7 +719,6 @@
|
||||
"screen_create_room_action_create_room" = "New room";
|
||||
"screen_create_room_error_creating_room" = "An error occurred when creating the room";
|
||||
"screen_create_room_private_option_description" = "Only people invited can access this room. All messages are end-to-end encrypted.";
|
||||
"screen_create_room_private_option_title" = "Private room";
|
||||
"screen_create_room_public_option_description" = "Anyone can find this room.\nYou can change this anytime in room settings.";
|
||||
"screen_create_room_topic_label" = "Topic (optional)";
|
||||
"screen_deactivate_account_confirmation_dialog_content" = "Please confirm that you want to deactivate your account. This action cannot be undone.";
|
||||
@@ -952,7 +956,6 @@
|
||||
"screen_room_details_already_invited" = "Already invited";
|
||||
"screen_room_details_badge_encrypted" = "Encrypted";
|
||||
"screen_room_details_badge_not_encrypted" = "Not encrypted";
|
||||
"screen_room_details_badge_public" = "Public room";
|
||||
"screen_room_details_edit_room_title" = "Edit Room";
|
||||
"screen_room_details_edition_error" = "There was an unknown error and the information couldn't be changed.";
|
||||
"screen_room_details_edition_error_title" = "Unable to update room";
|
||||
@@ -1295,6 +1298,7 @@
|
||||
"screen_create_account_title" = "Create account";
|
||||
"screen_create_poll_cancel_confirmation_content_ios" = "Your changes won’t be saved";
|
||||
"screen_create_room_add_people_title" = "Invite people";
|
||||
"screen_create_room_private_option_title" = "Private room";
|
||||
"screen_create_room_public_option_title" = "Public room";
|
||||
"screen_create_room_room_name_label" = "Room name";
|
||||
"screen_create_room_title" = "Create a room";
|
||||
@@ -1324,6 +1328,7 @@
|
||||
"screen_room_change_role_section_moderators" = "Moderators";
|
||||
"screen_room_change_role_section_users" = "Members";
|
||||
"screen_room_change_role_unsaved_changes_title" = "Save changes?";
|
||||
"screen_room_details_badge_public" = "Public room";
|
||||
"screen_room_details_invite_people_title" = "Invite people";
|
||||
"screen_room_details_leave_conversation_title" = "Leave conversation";
|
||||
"screen_room_details_leave_room_title" = "Leave room";
|
||||
|
||||
@@ -77,9 +77,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%1$d member</string>
|
||||
<string>%1$d Member</string>
|
||||
<key>other</key>
|
||||
<string>%1$d members</string>
|
||||
<string>%1$d Members</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>common_poll_votes_count</key>
|
||||
|
||||
@@ -77,9 +77,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%1$d membro</string>
|
||||
<string>%1$d Membro</string>
|
||||
<key>other</key>
|
||||
<string>%1$d membri</string>
|
||||
<string>%1$d Membri</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>common_poll_votes_count</key>
|
||||
|
||||
@@ -48,6 +48,8 @@ enum AppRoute: Hashable {
|
||||
case chatBackupSettings
|
||||
/// An external share request e.g. from the ShareExtension
|
||||
case share(ShareExtensionPayload)
|
||||
/// The change roles screen of a room with the transfer ownership setting
|
||||
case transferOwnership(roomID: String)
|
||||
|
||||
/// Whether or not the route should be handled by the authentication flow.
|
||||
var isAuthenticationRoute: Bool {
|
||||
|
||||
@@ -202,6 +202,12 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
}
|
||||
case .accountProvisioningLink:
|
||||
break // We always ignore this flow when logged in.
|
||||
case .transferOwnership(let roomID):
|
||||
if stateMachine.state.roomListSelectedRoomID == roomID {
|
||||
roomFlowCoordinator?.handleAppRoute(appRoute, animated: animated)
|
||||
} else {
|
||||
stateMachine.processEvent(.selectRoom(roomID: roomID, via: [], entryPoint: .transferOwnership))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,6 +238,7 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
case .roomDetails: .roomDetails(roomID: roomID)
|
||||
case .eventID(let eventID): .event(eventID: eventID, roomID: roomID, via: via) // ignored.
|
||||
case .share(let payload): .share(payload)
|
||||
case .transferOwnership: .transferOwnership(roomID: roomID)
|
||||
}
|
||||
roomFlowCoordinator.handleAppRoute(route, animated: animated)
|
||||
} else {
|
||||
@@ -449,6 +456,8 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
actionsSubject.send(.logout)
|
||||
case .presentDeclineAndBlock(let userID, let roomID):
|
||||
stateMachine.processEvent(.presentDeclineAndBlockScreen(userID: userID, roomID: roomID))
|
||||
case .transferOwnership(let roomIdentifier):
|
||||
handleAppRoute(.transferOwnership(roomID: roomIdentifier), animated: true)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
@@ -555,6 +564,8 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
coordinator.handleAppRoute(.roomDetails(roomID: roomID), animated: animated)
|
||||
case .share(let payload):
|
||||
coordinator.handleAppRoute(.share(payload), animated: animated)
|
||||
case .transferOwnership:
|
||||
coordinator.handleAppRoute(.transferOwnership(roomID: roomID), animated: animated)
|
||||
}
|
||||
|
||||
Task {
|
||||
|
||||
@@ -82,7 +82,7 @@ class EncryptionSettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
case .roomList, .room, .roomAlias, .childRoom, .childRoomAlias,
|
||||
.roomDetails, .roomMemberDetails, .userProfile,
|
||||
.event, .eventOnRoomAlias, .childEvent, .childEventOnRoomAlias,
|
||||
.call, .genericCallLink, .settings, .share:
|
||||
.call, .genericCallLink, .settings, .share, .transferOwnership:
|
||||
// These routes aren't in this flow so clear the entire stack.
|
||||
clearRoute(animated: animated)
|
||||
case .chatBackupSettings:
|
||||
|
||||
@@ -37,6 +37,8 @@ enum RoomFlowCoordinatorEntryPoint: Hashable {
|
||||
case roomDetails
|
||||
/// An external media share request
|
||||
case share(ShareExtensionPayload)
|
||||
/// The flow to change the the owner of the room
|
||||
case transferOwnership
|
||||
|
||||
var isEventID: Bool {
|
||||
guard case .eventID = self else { return false }
|
||||
@@ -196,9 +198,44 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
break // These are converted to a room ID route one level above.
|
||||
case .accountProvisioningLink, .roomList, .userProfile, .call, .genericCallLink, .settings, .chatBackupSettings:
|
||||
break // These routes can't be handled.
|
||||
case .transferOwnership(let roomID):
|
||||
guard self.roomID == roomID else { fatalError("Navigation route doesn't belong to this room flow.") }
|
||||
|
||||
Task {
|
||||
if roomProxy == nil {
|
||||
guard case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(roomID) else {
|
||||
return
|
||||
}
|
||||
|
||||
await storeAndSubscribeToRoomProxy(roomProxy)
|
||||
}
|
||||
|
||||
presentTransferOwnershipScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func presentTransferOwnershipScreen() {
|
||||
let parameters = RoomChangeRolesScreenCoordinatorParameters(mode: .owner,
|
||||
roomProxy: roomProxy,
|
||||
mediaProvider: userSession.mediaProvider,
|
||||
userIndicatorController: userIndicatorController,
|
||||
analytics: analytics)
|
||||
let stackCoordinator = NavigationStackCoordinator()
|
||||
let coordinator = RoomChangeRolesScreenCoordinator(parameters: parameters)
|
||||
coordinator.actionsPublisher.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .complete:
|
||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
stackCoordinator.setRootCoordinator(coordinator)
|
||||
navigationStackCoordinator.setSheetCoordinator(stackCoordinator, animated: true)
|
||||
}
|
||||
|
||||
private func presentCallScreen(roomID: String) async {
|
||||
guard case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(roomID) else {
|
||||
return
|
||||
@@ -798,6 +835,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
stateMachine.tryEvent(.presentRoomMemberDetails(userID: userID))
|
||||
case .presentReportRoomScreen:
|
||||
stateMachine.tryEvent(.presentReportRoomScreen)
|
||||
case .transferOwnership:
|
||||
presentTransferOwnershipScreen()
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
@@ -1555,6 +1594,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
coordinator.handleAppRoute(.roomDetails(roomID: roomID), animated: true)
|
||||
case .share(let payload):
|
||||
coordinator.handleAppRoute(.share(payload), animated: true)
|
||||
case .transferOwnership:
|
||||
coordinator.handleAppRoute(.transferOwnership(roomID: roomID), animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,13 @@ class RoomRolesAndPermissionsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
stateMachine.addRoutes(event: .changeRoles, transitions: [.rolesAndPermissionsScreen => .changingRoles]) { [weak self] context in
|
||||
guard let role = context.userInfo as? RoomRolesAndPermissionsScreenRole else { fatalError("Expected a role") }
|
||||
self?.presentChangeRolesScreen(role: role)
|
||||
let mode: RoomRole = switch role {
|
||||
case .administrators:
|
||||
.administrator
|
||||
case .moderators:
|
||||
.moderator
|
||||
}
|
||||
self?.presentChangeRolesScreen(mode: mode)
|
||||
}
|
||||
stateMachine.addRoutes(event: .finishedChangingRoles, transitions: [.changingRoles => .rolesAndPermissionsScreen])
|
||||
|
||||
@@ -150,12 +156,7 @@ class RoomRolesAndPermissionsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
private func presentChangeRolesScreen(role: RoomRolesAndPermissionsScreenRole) {
|
||||
let mode = switch role {
|
||||
case .administrators: RoomMemberDetails.Role.administrator
|
||||
case .moderators: RoomMemberDetails.Role.moderator
|
||||
}
|
||||
|
||||
private func presentChangeRolesScreen(mode: RoomRole) {
|
||||
let parameters = RoomChangeRolesScreenCoordinatorParameters(mode: mode,
|
||||
roomProxy: roomProxy,
|
||||
mediaProvider: mediaProvider,
|
||||
|
||||
@@ -542,6 +542,14 @@ internal enum L10n {
|
||||
internal static var commonPreparing: String { return L10n.tr("Localizable", "common_preparing") }
|
||||
/// Privacy policy
|
||||
internal static var commonPrivacyPolicy: String { return L10n.tr("Localizable", "common_privacy_policy") }
|
||||
/// Private room
|
||||
internal static var commonPrivateRoom: String { return L10n.tr("Localizable", "common_private_room") }
|
||||
/// Private space
|
||||
internal static var commonPrivateSpace: String { return L10n.tr("Localizable", "common_private_space") }
|
||||
/// Public room
|
||||
internal static var commonPublicRoom: String { return L10n.tr("Localizable", "common_public_room") }
|
||||
/// Public space
|
||||
internal static var commonPublicSpace: String { return L10n.tr("Localizable", "common_public_space") }
|
||||
/// Reaction
|
||||
internal static var commonReaction: String { return L10n.tr("Localizable", "common_reaction") }
|
||||
/// Reactions
|
||||
@@ -2150,6 +2158,8 @@ internal enum L10n {
|
||||
internal static var screenRoomChangePermissionsRoomTopic: String { return L10n.tr("Localizable", "screen_room_change_permissions_room_topic") }
|
||||
/// Send messages
|
||||
internal static var screenRoomChangePermissionsSendMessages: String { return L10n.tr("Localizable", "screen_room_change_permissions_send_messages") }
|
||||
/// Edit Admins or Owners
|
||||
internal static var screenRoomChangeRoleAdministratorsOrOwnersTitle: String { return L10n.tr("Localizable", "screen_room_change_role_administrators_or_owners_title") }
|
||||
/// Edit Admins
|
||||
internal static var screenRoomChangeRoleAdministratorsTitle: String { return L10n.tr("Localizable", "screen_room_change_role_administrators_title") }
|
||||
/// You will not be able to undo this action. You are promoting the user to have the same power level as you.
|
||||
|
||||
@@ -13334,11 +13334,6 @@ class RoomMemberProxyMock: RoomMemberProxyProtocol, @unchecked Sendable {
|
||||
set(value) { underlyingPowerLevel = value }
|
||||
}
|
||||
var underlyingPowerLevel: RoomPowerLevel!
|
||||
var role: RoomMemberRole {
|
||||
get { return underlyingRole }
|
||||
set(value) { underlyingRole = value }
|
||||
}
|
||||
var underlyingRole: RoomMemberRole!
|
||||
|
||||
}
|
||||
class RoomMembershipDetailsProxyMock: RoomMembershipDetailsProxyProtocol, @unchecked Sendable {
|
||||
|
||||
@@ -82,7 +82,7 @@ private extension RoomMember {
|
||||
powerLevel: proxy.powerLevel.rustPowerLevel,
|
||||
normalizedPowerLevel: proxy.powerLevel.rustPowerLevel,
|
||||
isIgnored: proxy.isIgnored,
|
||||
suggestedRoleForPowerLevel: proxy.role,
|
||||
suggestedRoleForPowerLevel: proxy.role.rustRole,
|
||||
membershipChangeReason: proxy.membershipChangeReason)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ extension JoinedRoomProxyMock {
|
||||
guard case .success(let member) = await self?.getMember(userID: userID) else {
|
||||
return .failure(.sdkError(RoomProxyMockError.generic))
|
||||
}
|
||||
return .success(member.role)
|
||||
return .success(member.role.rustRole)
|
||||
}
|
||||
updatePowerLevelsForUsersReturnValue = .success(())
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ struct RoomMemberProxyMockConfiguration {
|
||||
var isIgnored = false
|
||||
|
||||
var powerLevel = RoomPowerLevel(value: 0)
|
||||
var role = RoomMemberRole.user
|
||||
}
|
||||
|
||||
extension RoomMemberProxyMock {
|
||||
@@ -36,7 +35,6 @@ extension RoomMemberProxyMock {
|
||||
isIgnored = configuration.isIgnored
|
||||
|
||||
powerLevel = configuration.powerLevel
|
||||
role = configuration.role
|
||||
}
|
||||
|
||||
// Mocks
|
||||
@@ -52,8 +50,15 @@ extension RoomMemberProxyMock {
|
||||
displayName: "Me",
|
||||
avatarURL: .mockMXCUserAvatar,
|
||||
membership: .join,
|
||||
powerLevel: .init(value: 100),
|
||||
role: .administrator))
|
||||
powerLevel: .init(value: 100)))
|
||||
}
|
||||
|
||||
static var mockMeCreator: RoomMemberProxyMock {
|
||||
RoomMemberProxyMock(with: .init(userID: "@me:matrix.org",
|
||||
displayName: "Me",
|
||||
avatarURL: .mockMXCUserAvatar,
|
||||
membership: .join,
|
||||
powerLevel: .infinite))
|
||||
}
|
||||
|
||||
static var mockAlice: RoomMemberProxyMock {
|
||||
@@ -116,32 +121,28 @@ extension RoomMemberProxyMock {
|
||||
RoomMemberProxyMock(with: .init(userID: "@admin:matrix.org",
|
||||
displayName: "Arthur",
|
||||
membership: .join,
|
||||
powerLevel: .init(value: 100),
|
||||
role: .administrator))
|
||||
powerLevel: .init(value: 100)))
|
||||
}
|
||||
|
||||
static var mockCreator: RoomMemberProxyMock {
|
||||
RoomMemberProxyMock(with: .init(userID: "@creator:matrix.org",
|
||||
displayName: "God",
|
||||
membership: .join,
|
||||
powerLevel: .infinite,
|
||||
role: .creator))
|
||||
powerLevel: .infinite))
|
||||
}
|
||||
|
||||
static var mockOwner: RoomMemberProxyMock {
|
||||
RoomMemberProxyMock(with: .init(userID: "@owner:matrix.org",
|
||||
displayName: "Guinevere",
|
||||
membership: .join,
|
||||
powerLevel: .value(150),
|
||||
role: .administrator))
|
||||
powerLevel: .value(150)))
|
||||
}
|
||||
|
||||
static var mockModerator: RoomMemberProxyMock {
|
||||
RoomMemberProxyMock(with: .init(userID: "@mod:matrix.org",
|
||||
displayName: "Merlin",
|
||||
membership: .join,
|
||||
powerLevel: .init(value: 50),
|
||||
role: .moderator))
|
||||
powerLevel: .init(value: 50)))
|
||||
}
|
||||
|
||||
static var mockBanned: [RoomMemberProxyMock] {
|
||||
@@ -181,4 +182,33 @@ extension Array where Element == RoomMemberProxyMock {
|
||||
.mockAdmin,
|
||||
.mockModerator
|
||||
]
|
||||
|
||||
/// This also includes the creator and the owner role.
|
||||
static let allMembersAsAdminV2: [RoomMemberProxyMock] = [
|
||||
.mockMeAdmin,
|
||||
.mockAlice,
|
||||
.mockBob,
|
||||
.mockCharlie,
|
||||
.mockDan,
|
||||
.mockInvited,
|
||||
.mockIgnored,
|
||||
.mockAdmin,
|
||||
.mockModerator,
|
||||
.mockOwner,
|
||||
.mockCreator
|
||||
]
|
||||
|
||||
static let allMembersAsCreator: [RoomMemberProxyMock] = [
|
||||
.mockAdmin,
|
||||
.mockAlice,
|
||||
.mockBob,
|
||||
.mockCharlie,
|
||||
.mockDan,
|
||||
.mockInvited,
|
||||
.mockIgnored,
|
||||
.mockModerator,
|
||||
.mockCreator,
|
||||
.mockMeCreator,
|
||||
.mockOwner
|
||||
]
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ enum HomeScreenCoordinatorAction {
|
||||
case presentReportRoom(roomIdentifier: String)
|
||||
case presentDeclineAndBlock(userID: String, roomID: String)
|
||||
case roomLeft(roomIdentifier: String)
|
||||
case transferOwnership(roomIdentifier: String)
|
||||
case presentSettingsScreen
|
||||
case presentFeedbackScreen
|
||||
case presentSecureBackupSettings
|
||||
@@ -86,6 +87,8 @@ final class HomeScreenCoordinator: CoordinatorProtocol {
|
||||
actionsSubject.send(.logout)
|
||||
case .presentDeclineAndBlock(let userID, let roomID):
|
||||
actionsSubject.send(.presentDeclineAndBlock(userID: userID, roomID: roomID))
|
||||
case .transferOwnership(let roomIdentifier):
|
||||
actionsSubject.send(.transferOwnership(roomIdentifier: roomIdentifier))
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
@@ -15,6 +15,7 @@ enum HomeScreenViewModelAction: Equatable {
|
||||
case presentReportRoom(roomIdentifier: String)
|
||||
case presentDeclineAndBlock(userID: String, roomID: String)
|
||||
case roomLeft(roomIdentifier: String)
|
||||
case transferOwnership(roomIdentifier: String)
|
||||
case presentSecureBackupSettings
|
||||
case presentRecoveryKeyScreen
|
||||
case presentEncryptionResetScreen
|
||||
|
||||
@@ -342,15 +342,37 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
return
|
||||
}
|
||||
|
||||
if !(roomProxy.infoPublisher.value.isPrivate ?? true) {
|
||||
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(roomID: roomID, isDM: roomProxy.isDirectOneToOneRoom, state: .public)
|
||||
} else {
|
||||
state.bindings.leaveRoomAlertItem = if roomProxy.infoPublisher.value.joinedMembersCount > 1 {
|
||||
LeaveRoomAlertItem(roomID: roomID, isDM: roomProxy.isDirectOneToOneRoom, state: .private)
|
||||
} else {
|
||||
LeaveRoomAlertItem(roomID: roomID, isDM: roomProxy.isDirectOneToOneRoom, state: .empty)
|
||||
guard roomProxy.infoPublisher.value.joinedMembersCount > 1 else {
|
||||
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(roomID: roomID,
|
||||
isDM: roomProxy.isDirectOneToOneRoom,
|
||||
state: roomProxy.infoPublisher.value.isPrivate ?? true ? .empty : .public)
|
||||
return
|
||||
}
|
||||
|
||||
if !roomProxy.isDirectOneToOneRoom {
|
||||
if case let .success(ownMember) = await roomProxy.getMember(userID: roomProxy.ownUserID),
|
||||
ownMember.role.isOwner {
|
||||
await roomProxy.updateMembers()
|
||||
var isLastOwner = true
|
||||
for member in roomProxy.membersPublisher.value where member.userID != roomProxy.ownUserID {
|
||||
if member.role.isOwner {
|
||||
isLastOwner = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if isLastOwner {
|
||||
state.bindings.alertInfo = .init(id: UUID(),
|
||||
title: L10n.leaveRoomAlertSelectNewOwnerTitle,
|
||||
message: L10n.leaveRoomAlertSelectNewOwnerSubtitle,
|
||||
primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil),
|
||||
secondaryButton: .init(title: L10n.leaveRoomAlertSelectNewOwnerAction, role: .destructive, action: { [weak self] in self?.actionsSubject.send(.transferOwnership(roomIdentifier: roomID)) }))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(roomID: roomID, isDM: roomProxy.isDirectOneToOneRoom, state: roomProxy.infoPublisher.value.isPrivate ?? true ? .private : .public)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import Combine
|
||||
import SwiftUI
|
||||
|
||||
struct RoomChangeRolesScreenCoordinatorParameters {
|
||||
let mode: RoomMemberDetails.Role
|
||||
let mode: RoomRole
|
||||
let roomProxy: JoinedRoomProxyProtocol
|
||||
let mediaProvider: MediaProviderProtocol
|
||||
let userIndicatorController: UserIndicatorControllerProtocol
|
||||
|
||||
@@ -14,15 +14,19 @@ enum RoomChangeRolesScreenViewModelAction {
|
||||
|
||||
struct RoomChangeRolesScreenViewState: BindableState {
|
||||
/// The screen's current mode (which role we are promoting/demoting users to/from.
|
||||
let mode: RoomMemberDetails.Role
|
||||
let mode: RoomRole
|
||||
/// The current user's role.
|
||||
var ownRole: RoomRole
|
||||
/// All of the room's members who are currently owners or creators.
|
||||
var owners: [RoomMemberDetails] = []
|
||||
/// All of the room's members who are currently admins.
|
||||
var administrators: [RoomMemberDetails]
|
||||
var administrators: [RoomMemberDetails] = []
|
||||
/// All of the room's members who are currently moderators.
|
||||
var moderators: [RoomMemberDetails]
|
||||
var moderators: [RoomMemberDetails] = []
|
||||
/// All of the room's members who are currently neither an admin or moderator.
|
||||
var users: [RoomMemberDetails]
|
||||
var users: [RoomMemberDetails] = []
|
||||
|
||||
var bindings: RoomChangeRolesScreenViewStateBindings
|
||||
var bindings = RoomChangeRolesScreenViewStateBindings()
|
||||
|
||||
/// The members selected for promotion to the current role.
|
||||
var membersToPromote: Set<RoomMemberDetails> = []
|
||||
@@ -35,9 +39,17 @@ struct RoomChangeRolesScreenViewState: BindableState {
|
||||
/// The screen's title.
|
||||
var title: String {
|
||||
switch mode {
|
||||
case .creator, .owner, .administrator:
|
||||
// TODO: Handle the creator permissions change
|
||||
L10n.screenRoomChangeRoleAdministratorsTitle
|
||||
case .creator:
|
||||
"" // The screen can't be configured with this role.
|
||||
case .owner:
|
||||
L10n.screenRoomChangeRoleOwnersTitle
|
||||
case .administrator:
|
||||
switch ownRole {
|
||||
case .creator:
|
||||
L10n.screenRoomChangeRoleAdministratorsOrOwnersTitle
|
||||
default:
|
||||
L10n.screenRoomChangeRoleAdministratorsTitle
|
||||
}
|
||||
case .moderator:
|
||||
L10n.screenRoomChangeRoleModeratorsTitle
|
||||
case .user:
|
||||
@@ -45,6 +57,10 @@ struct RoomChangeRolesScreenViewState: BindableState {
|
||||
}
|
||||
}
|
||||
|
||||
var visibleOwners: [RoomMemberDetails] {
|
||||
owners.filter { $0.matches(searchQuery: bindings.searchQuery) }
|
||||
}
|
||||
|
||||
/// The visible admins in the screen (after searching).
|
||||
var visibleAdministrators: [RoomMemberDetails] {
|
||||
administrators.filter { $0.matches(searchQuery: bindings.searchQuery) }
|
||||
@@ -62,7 +78,8 @@ struct RoomChangeRolesScreenViewState: BindableState {
|
||||
|
||||
/// All of the members who will gain/keep this screen's role after saving any changes.
|
||||
var membersWithRole: [RoomMemberDetails] {
|
||||
administrators.filter(isMemberSelected) + moderators.filter(isMemberSelected) + users.filter(isMemberSelected)
|
||||
let members = owners + administrators + moderators + users
|
||||
return members.filter(isMemberSelected)
|
||||
}
|
||||
|
||||
/// Whether or not any changes have been made to the members.
|
||||
@@ -78,7 +95,31 @@ struct RoomChangeRolesScreenViewState: BindableState {
|
||||
/// Whether or not a specific member has this screen's role.
|
||||
func isMemberSelected(_ member: RoomMemberDetails) -> Bool {
|
||||
guard !membersToDemote.contains(member) else { return false }
|
||||
return member.role == mode || membersToPromote.contains(member)
|
||||
return member.role >= mode || membersToPromote.contains(member)
|
||||
}
|
||||
|
||||
func isMemberDisabled(_ member: RoomMemberDetails) -> Bool {
|
||||
member.role > maxDemotableRole
|
||||
}
|
||||
|
||||
var maxDemotableRole: RoomRole {
|
||||
switch mode {
|
||||
case .owner:
|
||||
return .owner
|
||||
case .administrator:
|
||||
switch ownRole {
|
||||
case .owner:
|
||||
return .administrator
|
||||
case .creator:
|
||||
return .owner
|
||||
default:
|
||||
return .moderator
|
||||
}
|
||||
case .moderator:
|
||||
return .moderator
|
||||
default:
|
||||
return .user
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +132,8 @@ struct RoomChangeRolesScreenViewStateBindings {
|
||||
enum RoomChangeRolesScreenAlertType {
|
||||
/// A warning that a particular promotion can't be undone.
|
||||
case promotionWarning
|
||||
/// A warning that ownership transfer is final when the room is left.
|
||||
case transferOwnershipWarning
|
||||
/// A confirmation that the user would like to discard any unsaved changes.
|
||||
case discardChanges
|
||||
/// The generic error message.
|
||||
|
||||
+35
-16
@@ -20,22 +20,19 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(mode: RoomMemberDetails.Role,
|
||||
init(mode: RoomRole,
|
||||
roomProxy: JoinedRoomProxyProtocol,
|
||||
mediaProvider: MediaProviderProtocol,
|
||||
userIndicatorController: UserIndicatorControllerProtocol,
|
||||
analytics: AnalyticsService) {
|
||||
guard mode != .user else { fatalError("Invalid screen configuration: \(mode)") }
|
||||
|
||||
guard mode != .user || mode != .creator else { fatalError("Invalid screen configuration: \(mode)") }
|
||||
|
||||
self.roomProxy = roomProxy
|
||||
self.userIndicatorController = userIndicatorController
|
||||
self.analytics = analytics
|
||||
|
||||
super.init(initialViewState: RoomChangeRolesScreenViewState(mode: mode,
|
||||
administrators: [],
|
||||
moderators: [],
|
||||
users: [],
|
||||
bindings: .init()),
|
||||
ownRole: roomProxy.membersPublisher.value.first { $0.userID == roomProxy.id }?.role ?? .administrator),
|
||||
mediaProvider: mediaProvider)
|
||||
|
||||
roomProxy.membersPublisher
|
||||
@@ -64,11 +61,16 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh
|
||||
case .demoteMember(let member):
|
||||
demoteMember(member)
|
||||
case .save:
|
||||
if state.mode.isAdminOrHigher, !state.membersToPromote.isEmpty {
|
||||
showPromotionWarning()
|
||||
} else {
|
||||
Task { await save() }
|
||||
if !state.membersToPromote.isEmpty {
|
||||
if state.mode == .administrator, state.ownRole == .administrator {
|
||||
showPromotionWarning()
|
||||
return
|
||||
} else if state.mode == .owner {
|
||||
showTransferOwnershipWarning()
|
||||
return
|
||||
}
|
||||
}
|
||||
Task { await save() }
|
||||
case .cancel:
|
||||
confirmDiscardChanges()
|
||||
}
|
||||
@@ -77,17 +79,23 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh
|
||||
// MARK: - Private
|
||||
|
||||
private func updateMembers(_ members: [RoomMemberProxyProtocol]) {
|
||||
var owners = [RoomMemberDetails]()
|
||||
var administrators = [RoomMemberDetails]()
|
||||
var moderators = [RoomMemberDetails]()
|
||||
var users = [RoomMemberDetails]()
|
||||
|
||||
for member in members.sorted() {
|
||||
if member.userID == roomProxy.ownUserID {
|
||||
state.ownRole = member.role
|
||||
}
|
||||
|
||||
guard member.isActive else { continue }
|
||||
let memberDetails = RoomMemberDetails(withProxy: member)
|
||||
|
||||
switch member.role {
|
||||
// TODO: Create a owner list
|
||||
case .administrator, .creator:
|
||||
switch memberDetails.role {
|
||||
case .creator, .owner:
|
||||
owners.append(memberDetails)
|
||||
case .administrator:
|
||||
administrators.append(memberDetails)
|
||||
case .moderator:
|
||||
moderators.append(memberDetails)
|
||||
@@ -96,6 +104,7 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh
|
||||
}
|
||||
}
|
||||
|
||||
state.owners = owners
|
||||
state.administrators = administrators
|
||||
state.moderators = moderators
|
||||
state.users = users
|
||||
@@ -107,9 +116,9 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh
|
||||
} else if state.membersToDemote.contains(member) {
|
||||
state.membersToDemote.remove(member)
|
||||
state.lastPromotedMember = member
|
||||
} else if member.role == state.mode {
|
||||
} else if member.role >= state.mode, member.role <= state.maxDemotableRole {
|
||||
state.membersToDemote.insert(member)
|
||||
} else {
|
||||
} else if member.role < state.mode {
|
||||
state.membersToPromote.insert(member)
|
||||
state.lastPromotedMember = member
|
||||
}
|
||||
@@ -133,6 +142,16 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh
|
||||
secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil))
|
||||
}
|
||||
|
||||
private func showTransferOwnershipWarning() {
|
||||
context.alertInfo = AlertInfo(id: .transferOwnershipWarning,
|
||||
title: L10n.screenRoomChangeRoleConfirmChangeOwnersTitle,
|
||||
message: L10n.screenRoomChangeRoleConfirmChangeOwnersDescription,
|
||||
primaryButton: .init(title: L10n.actionContinue, role: .destructive) {
|
||||
Task { await self.save() }
|
||||
},
|
||||
secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil))
|
||||
}
|
||||
|
||||
private func save() async {
|
||||
showSavingIndicator()
|
||||
|
||||
|
||||
@@ -45,15 +45,18 @@ struct RoomChangeRolesScreen: View {
|
||||
}
|
||||
}
|
||||
|
||||
RoomChangeRolesScreenSection(members: context.viewState.visibleOwners,
|
||||
role: .owner,
|
||||
context: context)
|
||||
|
||||
RoomChangeRolesScreenSection(members: context.viewState.visibleAdministrators,
|
||||
title: L10n.screenRoomChangeRoleSectionAdministrators,
|
||||
isAdministratorsSection: true,
|
||||
role: .administrator,
|
||||
context: context)
|
||||
RoomChangeRolesScreenSection(members: context.viewState.visibleModerators,
|
||||
title: L10n.screenRoomChangeRoleSectionModerators,
|
||||
role: .moderator,
|
||||
context: context)
|
||||
RoomChangeRolesScreenSection(members: context.viewState.visibleUsers,
|
||||
title: L10n.screenRoomChangeRoleSectionUsers,
|
||||
role: .user,
|
||||
context: context)
|
||||
}
|
||||
}
|
||||
@@ -65,10 +68,12 @@ struct RoomChangeRolesScreen: View {
|
||||
ScrollViewReader { scrollView in
|
||||
HStack(spacing: 16) {
|
||||
ForEach(context.viewState.membersWithRole, id: \.id) { member in
|
||||
RoomChangeRolesScreenSelectedItem(member: member, mediaProvider: context.mediaProvider) {
|
||||
let dismissAction = context.viewState.isMemberDisabled(member) ? nil : {
|
||||
context.send(viewAction: .demoteMember(member))
|
||||
}
|
||||
.frame(width: cellWidth)
|
||||
RoomChangeRolesScreenSelectedItem(member: member, mediaProvider: context.mediaProvider,
|
||||
dismissAction: dismissAction)
|
||||
.frame(width: cellWidth)
|
||||
}
|
||||
}
|
||||
.onChange(of: context.viewState.lastPromotedMember) { _, newValue in
|
||||
@@ -104,10 +109,22 @@ struct RoomChangeRolesScreen: View {
|
||||
// MARK: - Previews
|
||||
|
||||
struct RoomChangeRolesScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static let administratorViewModel = makeViewModel(mode: .administrator)
|
||||
static let moderatorViewModel = makeViewModel(mode: .moderator)
|
||||
static let ownerViewModel = makeViewModel(mode: .owner, ownRole: .creator)
|
||||
static let administratorOrOwnerViewModel = makeViewModel(mode: .administrator, ownRole: .creator)
|
||||
static let administratorViewModel = makeViewModel(mode: .administrator, ownRole: .administrator)
|
||||
static let moderatorViewModel = makeViewModel(mode: .moderator, ownRole: .administrator)
|
||||
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
RoomChangeRolesScreen(context: ownerViewModel.context)
|
||||
}
|
||||
.previewDisplayName("Owners")
|
||||
|
||||
NavigationStack {
|
||||
RoomChangeRolesScreen(context: administratorOrOwnerViewModel.context)
|
||||
}
|
||||
.previewDisplayName("Administrator or Owners")
|
||||
|
||||
NavigationStack {
|
||||
RoomChangeRolesScreen(context: administratorViewModel.context)
|
||||
}
|
||||
@@ -119,11 +136,18 @@ struct RoomChangeRolesScreen_Previews: PreviewProvider, TestablePreview {
|
||||
.previewDisplayName("Moderators")
|
||||
}
|
||||
|
||||
static func makeViewModel(mode: RoomMemberDetails.Role) -> RoomChangeRolesScreenViewModel {
|
||||
RoomChangeRolesScreenViewModel(mode: mode,
|
||||
roomProxy: JoinedRoomProxyMock(.init(members: .allMembersAsAdmin)),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
userIndicatorController: UserIndicatorControllerMock(),
|
||||
analytics: ServiceLocator.shared.analytics)
|
||||
static func makeViewModel(mode: RoomRole, ownRole: RoomRole) -> RoomChangeRolesScreenViewModel {
|
||||
let members: [RoomMemberProxyMock] = switch ownRole {
|
||||
case .creator:
|
||||
.allMembersAsCreator
|
||||
default:
|
||||
.allMembersAsAdminV2
|
||||
}
|
||||
|
||||
return RoomChangeRolesScreenViewModel(mode: mode,
|
||||
roomProxy: JoinedRoomProxyMock(.init(members: members)),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
userIndicatorController: UserIndicatorControllerMock(),
|
||||
analytics: ServiceLocator.shared.analytics)
|
||||
}
|
||||
}
|
||||
|
||||
+20
-10
@@ -10,36 +10,46 @@ import SwiftUI
|
||||
|
||||
struct RoomChangeRolesScreenSection: View {
|
||||
let members: [RoomMemberDetails]
|
||||
let title: String
|
||||
var isAdministratorsSection = false
|
||||
let role: RoomRole
|
||||
|
||||
let context: RoomChangeRolesScreenViewModel.Context
|
||||
|
||||
var title: String {
|
||||
switch role {
|
||||
case .creator, .owner:
|
||||
L10n.screenRoomRolesAndPermissionsOwners
|
||||
case .administrator:
|
||||
L10n.screenRoomChangeRoleSectionAdministrators
|
||||
case .moderator:
|
||||
L10n.screenRoomChangeRoleSectionModerators
|
||||
case .user:
|
||||
L10n.screenRoomChangeRoleSectionUsers
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if !members.isEmpty {
|
||||
Section {
|
||||
ForEach(members, id: \.id) { member in
|
||||
RoomChangeRolesScreenRow(member: member,
|
||||
mediaProvider: context.mediaProvider,
|
||||
isSelected: isMemberSelected(member)) {
|
||||
isSelected: context.viewState.isMemberSelected(member)) {
|
||||
context.send(viewAction: .toggleMember(member))
|
||||
}
|
||||
.disabled(member.role.isAdminOrHigher)
|
||||
.disabled(context.viewState.isMemberDisabled(member))
|
||||
}
|
||||
} header: {
|
||||
Text(title)
|
||||
.compoundListSectionHeader()
|
||||
} footer: {
|
||||
if isAdministratorsSection, context.viewState.mode == .moderator {
|
||||
if role == .administrator, context.viewState.mode == .moderator {
|
||||
Text(L10n.screenRoomChangeRoleModeratorsAdminSectionFooter)
|
||||
.compoundListSectionFooter()
|
||||
} else if role.isOwner, context.viewState.mode != .owner {
|
||||
Text(L10n.screenRoomChangeRoleModeratorsOwnerSectionFooter)
|
||||
.compoundListSectionFooter()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func isMemberSelected(_ member: RoomMemberDetails) -> Bool {
|
||||
// We always show administrators as selected, even on the moderators screen.
|
||||
member.role.isAdminOrHigher || context.viewState.isMemberSelected(member)
|
||||
}
|
||||
}
|
||||
|
||||
+15
-7
@@ -10,9 +10,22 @@ import SwiftUI
|
||||
struct RoomChangeRolesScreenSelectedItem: View {
|
||||
let member: RoomMemberDetails
|
||||
let mediaProvider: MediaProviderProtocol?
|
||||
let dismissAction: () -> Void
|
||||
let dismissAction: (() -> Void)?
|
||||
|
||||
var body: some View {
|
||||
mainContent
|
||||
.accessibilityActions {
|
||||
if let dismissAction {
|
||||
Button(L10n.actionDismiss) {
|
||||
dismissAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private var mainContent: some View {
|
||||
VStack(spacing: 4) {
|
||||
avatar
|
||||
|
||||
@@ -22,13 +35,8 @@ struct RoomChangeRolesScreenSelectedItem: View {
|
||||
.lineLimit(1)
|
||||
}
|
||||
.accessibilityElement(children: .combine)
|
||||
.accessibilityAction(named: L10n.actionDismiss) {
|
||||
dismissAction()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
var avatar: some View {
|
||||
LoadableAvatarImage(url: member.avatarURL,
|
||||
name: member.name,
|
||||
@@ -37,7 +45,7 @@ struct RoomChangeRolesScreenSelectedItem: View {
|
||||
mediaProvider: mediaProvider)
|
||||
.accessibilityHidden(true)
|
||||
.overlay(alignment: .topTrailing) {
|
||||
if member.role != .administrator {
|
||||
if let dismissAction {
|
||||
Button(action: dismissAction) {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.resizable()
|
||||
|
||||
@@ -34,6 +34,7 @@ enum RoomDetailsScreenCoordinatorAction {
|
||||
case presentKnockingRequestsListScreen
|
||||
case presentSecurityAndPrivacyScreen
|
||||
case presentReportRoomScreen
|
||||
case transferOwnership
|
||||
}
|
||||
|
||||
final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
|
||||
@@ -94,6 +95,8 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
|
||||
actionsSubject.send(.presentRecipientDetails(userID: userID))
|
||||
case .displayReportRoom:
|
||||
actionsSubject.send(.presentReportRoomScreen)
|
||||
case .transferOwnership:
|
||||
actionsSubject.send(.transferOwnership)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
@@ -27,6 +27,7 @@ enum RoomDetailsScreenViewModelAction: Equatable {
|
||||
case displayKnockingRequests
|
||||
case displaySecurityAndPrivacy
|
||||
case displayReportRoom
|
||||
case transferOwnership
|
||||
}
|
||||
|
||||
// MARK: View
|
||||
@@ -288,6 +289,8 @@ enum RoomDetailsScreenErrorType: Hashable {
|
||||
case alert
|
||||
/// Leaving room has failed..
|
||||
case unknown
|
||||
/// Last owner
|
||||
case lastOwner
|
||||
}
|
||||
|
||||
enum RoomDetailsScreenPinnedEventsActionState {
|
||||
|
||||
@@ -119,13 +119,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
||||
case .processTapInvite:
|
||||
actionsSubject.send(.requestInvitePeoplePresentation)
|
||||
case .processTapLeave:
|
||||
guard state.joinedMembersCount > 1 else {
|
||||
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(roomID: roomProxy.id, isDM: roomProxy.isDirectOneToOneRoom, state: .empty)
|
||||
return
|
||||
}
|
||||
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(roomID: roomProxy.id,
|
||||
isDM: roomProxy.isDirectOneToOneRoom,
|
||||
state: roomProxy.infoPublisher.value.isPrivate ?? true ? .private : .public)
|
||||
processTapToLeave()
|
||||
case .confirmLeave:
|
||||
Task { await leaveRoom() }
|
||||
case .processTapIgnore:
|
||||
@@ -177,6 +171,38 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func processTapToLeave() {
|
||||
guard state.joinedMembersCount > 1 else {
|
||||
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(roomID: roomProxy.id,
|
||||
isDM: roomProxy.isDirectOneToOneRoom,
|
||||
state: roomProxy.infoPublisher.value.isPrivate ?? true ? .empty : .public)
|
||||
return
|
||||
}
|
||||
|
||||
if !roomProxy.isDirectOneToOneRoom, state.accountOwner?.role.isOwner == true {
|
||||
var isLastOwner = true
|
||||
for member in roomProxy.membersPublisher.value where member.userID != roomProxy.ownUserID {
|
||||
if member.role.isOwner {
|
||||
isLastOwner = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if isLastOwner {
|
||||
state.bindings.alertInfo = .init(id: .lastOwner,
|
||||
title: L10n.leaveRoomAlertSelectNewOwnerTitle,
|
||||
message: L10n.leaveRoomAlertSelectNewOwnerSubtitle,
|
||||
primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil),
|
||||
secondaryButton: .init(title: L10n.leaveRoomAlertSelectNewOwnerAction, role: .destructive, action: { [weak self] in self?.actionsSubject.send(.transferOwnership) }))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(roomID: roomProxy.id,
|
||||
isDM: roomProxy.isDirectOneToOneRoom,
|
||||
state: roomProxy.infoPublisher.value.isPrivate ?? true ? .private : .public)
|
||||
}
|
||||
|
||||
private func setupRoomSubscription() {
|
||||
roomProxy.infoPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
@@ -237,11 +263,6 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
||||
}
|
||||
|
||||
private func fetchMembersIfNeeded() async {
|
||||
// We need to fetch members just in 1-to-1 chat to get the member object for the other person
|
||||
guard roomProxy.isDirectOneToOneRoom else {
|
||||
return
|
||||
}
|
||||
|
||||
roomProxy.membersPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self, ownUserID = roomProxy.ownUserID] members in
|
||||
@@ -251,6 +272,10 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
||||
self.state.accountOwner = .init(withProxy: accountOwner)
|
||||
}
|
||||
|
||||
guard roomProxy.isDirectOneToOneRoom else {
|
||||
return
|
||||
}
|
||||
|
||||
if let dmRecipient = members.first(where: { $0.userID != ownUserID }) {
|
||||
self.state.dmRecipientInfo = .init(member: .init(withProxy: dmRecipient))
|
||||
|
||||
|
||||
+5
-1
@@ -17,12 +17,16 @@ enum RoomRolesAndPermissionsScreenViewModelAction {
|
||||
}
|
||||
|
||||
struct RoomRolesAndPermissionsScreenViewState: BindableState {
|
||||
var ownRole: RoomRole
|
||||
|
||||
var administratorsAndOwnersCount: Int?
|
||||
/// The number of administrators in the room.
|
||||
var administratorCount: Int?
|
||||
/// The number of moderators in the room.
|
||||
var moderatorCount: Int?
|
||||
/// The permissions of the room when loaded.
|
||||
var permissions: RoomPermissions?
|
||||
|
||||
var bindings = RoomRolesAndPermissionsScreenViewStateBindings()
|
||||
}
|
||||
|
||||
@@ -46,7 +50,7 @@ enum RoomRolesAndPermissionsScreenViewAction {
|
||||
case reset
|
||||
}
|
||||
|
||||
enum RoomRolesAndPermissionsScreenRole {
|
||||
enum RoomRolesAndPermissionsScreenRole: Hashable {
|
||||
case administrators
|
||||
case moderators
|
||||
}
|
||||
|
||||
+11
-6
@@ -14,6 +14,7 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM
|
||||
private let roomProxy: JoinedRoomProxyProtocol
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
private let analytics: AnalyticsService
|
||||
private var ownUser: RoomMemberDetails?
|
||||
|
||||
private var actionsSubject: PassthroughSubject<RoomRolesAndPermissionsScreenViewModelAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<RoomRolesAndPermissionsScreenViewModelAction, Never> {
|
||||
@@ -24,7 +25,8 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM
|
||||
self.roomProxy = roomProxy
|
||||
self.userIndicatorController = userIndicatorController
|
||||
self.analytics = analytics
|
||||
super.init(initialViewState: RoomRolesAndPermissionsScreenViewState(permissions: initialPermissions))
|
||||
super.init(initialViewState: RoomRolesAndPermissionsScreenViewState(ownRole: roomProxy.membersPublisher.value.first(where: { $0.userID == roomProxy.ownUserID })?.role ?? .administrator,
|
||||
permissions: initialPermissions))
|
||||
|
||||
// Automatically update the admin/moderator counts.
|
||||
roomProxy.membersPublisher
|
||||
@@ -80,16 +82,19 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM
|
||||
secondaryButton: .init(title: L10n.actionCancel, role: .cancel) { })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Members
|
||||
|
||||
private func updateMembers(_ members: [RoomMemberProxyProtocol]) {
|
||||
// TODO: Will probably be changed when we implement the owners list
|
||||
state.administratorCount = members.filter { $0.role.isAdminOrHigher && $0.isActive }.count
|
||||
state.administratorsAndOwnersCount = members.filter { $0.role.isAdminOrHigher && $0.isActive }.count
|
||||
state.administratorCount = members.filter { $0.role == .administrator && $0.isActive }.count
|
||||
state.moderatorCount = members.filter { $0.role == .moderator && $0.isActive }.count
|
||||
if let ownUser = members.first(where: { $0.userID == roomProxy.ownUserID }) {
|
||||
state.ownRole = ownUser.role
|
||||
}
|
||||
}
|
||||
|
||||
private func updateOwnRole(_ role: RoomMemberDetails.Role) async {
|
||||
private func updateOwnRole(_ role: RoomRole) async {
|
||||
showSavingIndicator()
|
||||
|
||||
// A task we can await until the room's info gets modified with the new power levels.
|
||||
@@ -142,7 +147,7 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM
|
||||
|
||||
hideSavingIndicator()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Loading indicator
|
||||
|
||||
private static let savingIndicatorID = "RolesAndPermissionsSaving"
|
||||
|
||||
+43
-12
@@ -26,13 +26,23 @@ struct RoomRolesAndPermissionsScreen: View {
|
||||
|
||||
private var rolesSection: some View {
|
||||
Section {
|
||||
ListRow(label: .default(title: L10n.screenRoomRolesAndPermissionsAdmins,
|
||||
icon: \.admin),
|
||||
details: administratorDetails,
|
||||
kind: .navigationLink {
|
||||
context.send(viewAction: .editRoles(.administrators))
|
||||
})
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomRolesAndPermissionsScreen.administrators)
|
||||
if context.viewState.ownRole == .creator {
|
||||
ListRow(label: .default(title: L10n.screenRoomRolesAndPermissionsAdminsAndOwners,
|
||||
icon: \.admin),
|
||||
details: administratorOrOwnersDetails,
|
||||
kind: .navigationLink {
|
||||
context.send(viewAction: .editRoles(.administrators))
|
||||
})
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomRolesAndPermissionsScreen.administrators)
|
||||
} else {
|
||||
ListRow(label: .default(title: L10n.screenRoomRolesAndPermissionsAdmins,
|
||||
icon: \.admin),
|
||||
details: administratorDetails,
|
||||
kind: .navigationLink {
|
||||
context.send(viewAction: .editRoles(.administrators))
|
||||
})
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomRolesAndPermissionsScreen.administrators)
|
||||
}
|
||||
|
||||
ListRow(label: .default(title: L10n.screenRoomRolesAndPermissionsModerators,
|
||||
icon: \.chatProblem),
|
||||
@@ -42,17 +52,27 @@ struct RoomRolesAndPermissionsScreen: View {
|
||||
})
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomRolesAndPermissionsScreen.moderators)
|
||||
|
||||
ListRow(label: .default(title: L10n.screenRoomRolesAndPermissionsChangeMyRole,
|
||||
icon: \.edit),
|
||||
kind: .button {
|
||||
context.send(viewAction: .editOwnUserRole)
|
||||
})
|
||||
if context.viewState.ownRole != .creator {
|
||||
ListRow(label: .default(title: L10n.screenRoomRolesAndPermissionsChangeMyRole,
|
||||
icon: \.edit),
|
||||
kind: .button {
|
||||
context.send(viewAction: .editOwnUserRole)
|
||||
})
|
||||
}
|
||||
} header: {
|
||||
Text(L10n.screenRoomRolesAndPermissionsRolesHeader)
|
||||
.compoundListSectionHeader()
|
||||
}
|
||||
}
|
||||
|
||||
private var administratorOrOwnersDetails: ListRowDetails<Image> {
|
||||
if let administratorCount = context.viewState.administratorsAndOwnersCount {
|
||||
.title("\(administratorCount)")
|
||||
} else {
|
||||
.isWaiting(true)
|
||||
}
|
||||
}
|
||||
|
||||
private var administratorDetails: ListRowDetails<Image> {
|
||||
if let administratorCount = context.viewState.administratorCount {
|
||||
.title("\(administratorCount)")
|
||||
@@ -121,9 +141,20 @@ struct RoomRolesAndPermissionsScreen_Previews: PreviewProvider, TestablePreview
|
||||
roomProxy: JoinedRoomProxyMock(.init(members: .allMembersAsAdmin)),
|
||||
userIndicatorController: UserIndicatorControllerMock(),
|
||||
analytics: ServiceLocator.shared.analytics)
|
||||
|
||||
static let creatorViewModel = RoomRolesAndPermissionsScreenViewModel(initialPermissions: RoomPermissions(powerLevels: .mock),
|
||||
roomProxy: JoinedRoomProxyMock(.init(members: .allMembersAsCreator)),
|
||||
userIndicatorController: UserIndicatorControllerMock(),
|
||||
analytics: ServiceLocator.shared.analytics)
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
RoomRolesAndPermissionsScreen(context: viewModel.context)
|
||||
}
|
||||
.previewDisplayName("Admin")
|
||||
|
||||
NavigationStack {
|
||||
RoomRolesAndPermissionsScreen(context: creatorViewModel.context)
|
||||
}
|
||||
.previewDisplayName("Creator")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ extension AnalyticsService {
|
||||
}
|
||||
|
||||
/// Track a room moderation action.
|
||||
func trackRoomModeration(action: AnalyticsEvent.RoomModeration.Action, role: RoomMemberDetails.Role?) {
|
||||
func trackRoomModeration(action: AnalyticsEvent.RoomModeration.Action, role: RoomRole?) {
|
||||
let role = role.map(AnalyticsEvent.RoomModeration.Role.init)
|
||||
capture(event: AnalyticsEvent.RoomModeration(action: action, role: role))
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import AnalyticsEvents
|
||||
|
||||
extension AnalyticsEvent.RoomModeration.Role {
|
||||
init(role: RoomMemberDetails.Role) {
|
||||
init(role: RoomRole) {
|
||||
switch role {
|
||||
case .administrator, .creator, .owner:
|
||||
// This probably needs to be updates
|
||||
|
||||
@@ -18,21 +18,8 @@ struct RoomMemberDetails: Identifiable, Hashable {
|
||||
var isIgnored: Bool
|
||||
var isBanned: Bool
|
||||
var isActive: Bool
|
||||
|
||||
enum Role {
|
||||
/// Creator of the room, PL infinite
|
||||
case creator
|
||||
/// Same power of an admin, but they can also upgrade the room, PL 150 onwards
|
||||
case owner
|
||||
/// Able to edit room settings and perform any action aside from room upgrading PL 100...149
|
||||
case administrator
|
||||
/// Able to perform room moderation actions PL 50...99
|
||||
case moderator
|
||||
/// Default role PL 0...49
|
||||
case user
|
||||
}
|
||||
|
||||
let role: Role
|
||||
let role: RoomRole
|
||||
let powerLevel: RoomPowerLevel
|
||||
|
||||
func matches(searchQuery: String) -> Bool {
|
||||
@@ -51,47 +38,7 @@ extension RoomMemberDetails {
|
||||
isInvited = proxy.membership == .invite
|
||||
isIgnored = proxy.isIgnored
|
||||
isBanned = proxy.membership == .ban
|
||||
role = .init(proxy.role, powerLevel: proxy.powerLevel)
|
||||
role = proxy.role
|
||||
powerLevel = proxy.powerLevel
|
||||
}
|
||||
}
|
||||
|
||||
extension RoomMemberDetails.Role {
|
||||
init(_ role: RoomMemberRole, powerLevel: RoomPowerLevel) {
|
||||
switch role {
|
||||
case .creator:
|
||||
self = .creator
|
||||
case .administrator:
|
||||
switch powerLevel {
|
||||
case .value(let value):
|
||||
self = value >= 150 ? .owner : .administrator
|
||||
default:
|
||||
fatalError("Impossible")
|
||||
}
|
||||
case .moderator:
|
||||
self = .moderator
|
||||
case .user:
|
||||
self = .user
|
||||
}
|
||||
}
|
||||
|
||||
var isAdminOrHigher: Bool {
|
||||
switch self {
|
||||
case .administrator, .creator, .owner:
|
||||
return true
|
||||
case .moderator, .user:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension RoomMemberRole {
|
||||
var isAdminOrHigher: Bool {
|
||||
switch self {
|
||||
case .administrator, .creator:
|
||||
return true
|
||||
case .moderator, .user:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,4 @@ final class RoomMemberProxy: RoomMemberProxyProtocol {
|
||||
var isIgnored: Bool { member.isIgnored }
|
||||
|
||||
var powerLevel: RoomPowerLevel { .init(rustPowerLevel: member.powerLevel) }
|
||||
|
||||
var role: RoomMemberRole { member.suggestedRoleForPowerLevel }
|
||||
}
|
||||
|
||||
@@ -24,8 +24,6 @@ protocol RoomMemberProxyProtocol: AnyObject {
|
||||
var isIgnored: Bool { get }
|
||||
|
||||
var powerLevel: RoomPowerLevel { get }
|
||||
|
||||
var role: RoomMemberRole { get }
|
||||
}
|
||||
|
||||
extension RoomMemberProxyProtocol {
|
||||
@@ -58,3 +56,7 @@ extension [RoomMemberProxyProtocol] {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension RoomMemberProxyProtocol {
|
||||
var role: RoomRole { .init(powerLevel: powerLevel) }
|
||||
}
|
||||
|
||||
@@ -9,15 +9,15 @@ import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
struct RoomPermissionsSetting: Identifiable {
|
||||
var id: KeyPath<RoomPermissions, RoomMemberDetails.Role> { keyPath }
|
||||
var id: KeyPath<RoomPermissions, RoomRole> { keyPath }
|
||||
|
||||
/// The title of this setting.
|
||||
let title: String
|
||||
|
||||
/// The selected role of this setting.
|
||||
var value: RoomMemberDetails.Role
|
||||
var value: RoomRole
|
||||
/// All of the available roles that this setting can be configured with.
|
||||
var allValues: [(title: String, tag: RoomMemberDetails.Role)] {
|
||||
var allValues: [(title: String, tag: RoomRole)] {
|
||||
[
|
||||
(title: L10n.screenRoomChangePermissionsAdministrators, tag: .administrator),
|
||||
(title: L10n.screenRoomChangePermissionsModerators, tag: .moderator),
|
||||
@@ -26,7 +26,7 @@ struct RoomPermissionsSetting: Identifiable {
|
||||
}
|
||||
|
||||
/// The `RoomPermissions` property that this setting is for.
|
||||
let keyPath: KeyPath<RoomPermissions, RoomMemberDetails.Role>
|
||||
let keyPath: KeyPath<RoomPermissions, RoomRole>
|
||||
/// The `RoomPowerLevelChanges` property that this setting is saved into.
|
||||
var rustKeyPath: WritableKeyPath<RoomPowerLevelChanges, Int64?> {
|
||||
switch keyPath {
|
||||
@@ -47,99 +47,39 @@ struct RoomPermissionsSetting: Identifiable {
|
||||
|
||||
struct RoomPermissions {
|
||||
/// The level required to ban a user.
|
||||
var ban: RoomMemberDetails.Role
|
||||
var ban: RoomRole
|
||||
/// The level required to invite a user.
|
||||
var invite: RoomMemberDetails.Role
|
||||
var invite: RoomRole
|
||||
/// The level required to kick a user.
|
||||
var kick: RoomMemberDetails.Role
|
||||
var kick: RoomRole
|
||||
/// The level required to redact an event.
|
||||
var redact: RoomMemberDetails.Role
|
||||
var redact: RoomRole
|
||||
/// The default level required to send message events.
|
||||
var eventsDefault: RoomMemberDetails.Role
|
||||
var eventsDefault: RoomRole
|
||||
/// The default level required to send state events.
|
||||
var stateDefault: RoomMemberDetails.Role
|
||||
var stateDefault: RoomRole
|
||||
/// The default power level for every user in the room.
|
||||
var usersDefault: RoomMemberDetails.Role
|
||||
var usersDefault: RoomRole
|
||||
/// The level required to change the room's name.
|
||||
var roomName: RoomMemberDetails.Role
|
||||
var roomName: RoomRole
|
||||
/// The level required to change the room's avatar.
|
||||
var roomAvatar: RoomMemberDetails.Role
|
||||
var roomAvatar: RoomRole
|
||||
/// The level required to change the room's topic.
|
||||
var roomTopic: RoomMemberDetails.Role
|
||||
var roomTopic: RoomRole
|
||||
}
|
||||
|
||||
extension RoomPermissions {
|
||||
/// Create permissions from the room's power levels.
|
||||
init(powerLevels: RoomPowerLevelsValues) {
|
||||
ban = RoomMemberDetails.Role(powerLevelValue: powerLevels.ban)
|
||||
invite = RoomMemberDetails.Role(powerLevelValue: powerLevels.invite)
|
||||
kick = RoomMemberDetails.Role(powerLevelValue: powerLevels.kick)
|
||||
redact = RoomMemberDetails.Role(powerLevelValue: powerLevels.redact)
|
||||
eventsDefault = RoomMemberDetails.Role(powerLevelValue: powerLevels.eventsDefault)
|
||||
stateDefault = RoomMemberDetails.Role(powerLevelValue: powerLevels.stateDefault)
|
||||
usersDefault = RoomMemberDetails.Role(powerLevelValue: powerLevels.usersDefault)
|
||||
roomName = RoomMemberDetails.Role(powerLevelValue: powerLevels.roomName)
|
||||
roomAvatar = RoomMemberDetails.Role(powerLevelValue: powerLevels.roomAvatar)
|
||||
roomTopic = RoomMemberDetails.Role(powerLevelValue: powerLevels.roomTopic)
|
||||
}
|
||||
}
|
||||
|
||||
extension RoomMemberDetails.Role {
|
||||
init(powerLevelValue: Int64) {
|
||||
// Also this is not great, and should be handled by a `suggestedRoleForPowerLevelValue` function from the SDK
|
||||
guard powerLevelValue < 150 else {
|
||||
self = .owner
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
switch try suggestedRoleForPowerLevel(powerLevel: .value(value: powerLevelValue)) {
|
||||
case .administrator:
|
||||
self = .administrator
|
||||
case .creator:
|
||||
fatalError("Impossible")
|
||||
case .moderator:
|
||||
self = .moderator
|
||||
case .user:
|
||||
self = .user
|
||||
}
|
||||
} catch {
|
||||
MXLog.error("Falied to convert power level value to role: \(error)")
|
||||
self = .user
|
||||
}
|
||||
}
|
||||
|
||||
var rustRole: RoomMemberRole {
|
||||
switch self {
|
||||
case .creator:
|
||||
.creator
|
||||
case .administrator, .owner:
|
||||
.administrator
|
||||
case .moderator:
|
||||
.moderator
|
||||
case .user:
|
||||
.user
|
||||
}
|
||||
}
|
||||
|
||||
/// To be used when setting the power level of a user to get the suggested equivalent power level value for that specific role
|
||||
/// NOTE: Do not use for comparison, use the true power level instead.
|
||||
var powerLevelValue: Int64 {
|
||||
guard self != .owner else {
|
||||
// Would be better if the SDK would return this, maybe a `suggestedPowerLevelValueForRole` function would solve the problem
|
||||
return 150
|
||||
}
|
||||
|
||||
do {
|
||||
switch try suggestedPowerLevelForRole(role: rustRole) {
|
||||
case .infinite:
|
||||
fatalError("Impossible")
|
||||
case .value(let value):
|
||||
return value
|
||||
}
|
||||
} catch {
|
||||
MXLog.error("Falied to convert role to power level value: \(error)")
|
||||
return 0
|
||||
}
|
||||
ban = RoomRole(powerLevelValue: powerLevels.ban)
|
||||
invite = RoomRole(powerLevelValue: powerLevels.invite)
|
||||
kick = RoomRole(powerLevelValue: powerLevels.kick)
|
||||
redact = RoomRole(powerLevelValue: powerLevels.redact)
|
||||
eventsDefault = RoomRole(powerLevelValue: powerLevels.eventsDefault)
|
||||
stateDefault = RoomRole(powerLevelValue: powerLevels.stateDefault)
|
||||
usersDefault = RoomRole(powerLevelValue: powerLevels.usersDefault)
|
||||
roomName = RoomRole(powerLevelValue: powerLevels.roomName)
|
||||
roomAvatar = RoomRole(powerLevelValue: powerLevels.roomAvatar)
|
||||
roomTopic = RoomRole(powerLevelValue: powerLevels.roomTopic)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
//
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import MatrixRustSDK
|
||||
|
||||
enum RoomRole: Comparable {
|
||||
/// Default role PL 0...49
|
||||
case user
|
||||
/// Able to perform room moderation actions PL 50...99
|
||||
case moderator
|
||||
/// Able to edit room settings and perform any action aside from room upgrading PL 100...149
|
||||
case administrator
|
||||
/// Same power of an admin, but they can also upgrade the room, PL 150 onwards
|
||||
case owner
|
||||
/// Creator of the room, PL infinite
|
||||
case creator
|
||||
}
|
||||
|
||||
extension RoomRole {
|
||||
init(powerLevel: RoomPowerLevel) {
|
||||
do {
|
||||
let role = try suggestedRoleForPowerLevel(powerLevel: powerLevel.rustPowerLevel)
|
||||
self.init(role, powerLevel: powerLevel)
|
||||
} catch {
|
||||
MXLog.error("Failled to get suggested role for power level \(powerLevel): \(error)")
|
||||
self = .user
|
||||
}
|
||||
}
|
||||
|
||||
init(_ role: RoomMemberRole, powerLevel: RoomPowerLevel) {
|
||||
switch role {
|
||||
case .creator:
|
||||
self = .creator
|
||||
case .administrator:
|
||||
switch powerLevel {
|
||||
case .value(let value):
|
||||
self = value >= 150 ? .owner : .administrator
|
||||
default:
|
||||
fatalError("Impossible")
|
||||
}
|
||||
case .moderator:
|
||||
self = .moderator
|
||||
case .user:
|
||||
self = .user
|
||||
}
|
||||
}
|
||||
|
||||
var isAdminOrHigher: Bool {
|
||||
switch self {
|
||||
case .administrator, .creator, .owner:
|
||||
return true
|
||||
case .moderator, .user:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var isOwner: Bool {
|
||||
switch self {
|
||||
case .creator, .owner:
|
||||
return true
|
||||
case .administrator, .moderator, .user:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension RoomRole {
|
||||
init(powerLevelValue: Int64) {
|
||||
// Also this is not great, and should be handled by a `suggestedRoleForPowerLevelValue` function from the SDK
|
||||
guard powerLevelValue < 150 else {
|
||||
self = .owner
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
switch try suggestedRoleForPowerLevel(powerLevel: .value(value: powerLevelValue)) {
|
||||
case .administrator:
|
||||
self = .administrator
|
||||
case .creator:
|
||||
fatalError("Impossible")
|
||||
case .moderator:
|
||||
self = .moderator
|
||||
case .user:
|
||||
self = .user
|
||||
}
|
||||
} catch {
|
||||
MXLog.error("Falied to convert power level value to role: \(error)")
|
||||
self = .user
|
||||
}
|
||||
}
|
||||
|
||||
var rustRole: RoomMemberRole {
|
||||
switch self {
|
||||
case .creator:
|
||||
.creator
|
||||
case .administrator, .owner:
|
||||
.administrator
|
||||
case .moderator:
|
||||
.moderator
|
||||
case .user:
|
||||
.user
|
||||
}
|
||||
}
|
||||
|
||||
/// To be used when setting the power level of a user to get the suggested equivalent power level value for that specific role
|
||||
/// NOTE: Do not use for comparison, use the true power level instead.
|
||||
var powerLevelValue: Int64 {
|
||||
guard self != .owner else {
|
||||
// Would be better if the SDK would return this, maybe a `suggestedPowerLevelValueForRole` function would solve the problem
|
||||
return 150
|
||||
}
|
||||
|
||||
do {
|
||||
switch try suggestedPowerLevelForRole(role: rustRole) {
|
||||
case .infinite:
|
||||
fatalError("Impossible")
|
||||
case .value(let value):
|
||||
return value
|
||||
}
|
||||
} catch {
|
||||
MXLog.error("Falied to convert role to power level value: \(error)")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:df90344bb87a1b2f65e85cc65c749054dacac02c0c634501484bc02a7a9e6429
|
||||
size 226433
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:caa50aaa716140d4613e6682fa1facb12cc57f6bf6ee8e29e801ac5a5ab03db9
|
||||
size 238373
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9d40cb24ec76d87e4f8b0c4ead60a4fab50d8cc24ee0c903e527ddfaa5896b60
|
||||
size 170864
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7639e4fa100e48262f35e996caa31cf9a67c0f10cb73a4be8c2fc64bf70ee8bf
|
||||
size 180516
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:45bfc56242e51815e312057b7603d19c3f2ae488f81145f6e8c45bdd405bafd4
|
||||
size 219778
|
||||
oid sha256:356954ba65b0fe34441df8440182ab743df30aa9f6c6932bb9b5107ae87cb839
|
||||
size 221488
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ef779c260c378cb37d32e19aa9fb58c73818aca7153cdb762078487e6245b8cb
|
||||
size 226361
|
||||
oid sha256:7ce7e57ce7b90e91395dd3e0c9b3a73d944ac43a8f36feb77fb106e9f86add67
|
||||
size 231973
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:08b1ed25327b18ce7be7831bf62644d25269b7204d79fb1058466893923f3559
|
||||
size 172593
|
||||
oid sha256:28dd538e435dc4125d34c8cbf45c5403a4dc1b4759d3d16cb0e56f4b1bb5a63f
|
||||
size 167867
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:22561fa01da4cff09515ed5bf9c70beb55b2c691bf86c911aedc0090bef1396c
|
||||
size 178152
|
||||
oid sha256:d56fb49526da5e418e3311a8ab121efcde6cc5dd2234c3662997884e0f2e6991
|
||||
size 176485
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:06194fbeb1d19db9882525340b7a73d6cbae649c9a13f1b527e1d26306946c40
|
||||
size 184357
|
||||
oid sha256:5d7ee9d49212820a88171df680a31ad658ac62ae7fd3474a24403c130a4630d8
|
||||
size 231534
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1a14dd1411077dd6f20e46c67a8856169f04bc186bf7015ebe5fa37cdceb8981
|
||||
size 195057
|
||||
oid sha256:312f9f0d95ae3dd31c2c8ab4ce37a9de51febdd79a9d65b60205241e5a18f1cb
|
||||
size 247092
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6382218fc0c357a9b4cabcbfb737e4678d2f52f89949a9ddab5301f3c70023fc
|
||||
size 144983
|
||||
oid sha256:ddc42a0c0de638440189c23ec7ef85c9b84ea2fdbc8e222994adbc5f4ab88d5a
|
||||
size 172583
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1a2014f024ade0cb7901089e7637f7136474d057eb8dbb84577deecc7d3d16bb
|
||||
size 154055
|
||||
oid sha256:47fa5b57b70a18e75a9b690cc96e5bb3c7db26ccd06eae05f3d3e6f8bbd92647
|
||||
size 188261
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:dde88bcec2a8cc78858a4513f23a9b73aeab2478e52d730823e451e486e6d082
|
||||
size 217524
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:37a45dbaf05d8dd1ad69d625aadcd9291efd5fecc490f267b72a60f903ec5986
|
||||
size 224360
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a8e6022a3382c727169489bb965bf7ba2681417f4fb3123b4043745ca8887046
|
||||
size 161674
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:484250de28a8f3804c89cf551cba3903675c6e2fd28ce80e4b25145a674b430b
|
||||
size 168264
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:673f640cc1b7368d7c4bb70d5b5ac325941723cbf740507a0611606eee50d373
|
||||
size 62443
|
||||
oid sha256:60efb65986ae484b7f7da9648db252ab4aa11e0047020574244e070972813f0a
|
||||
size 62712
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:673f640cc1b7368d7c4bb70d5b5ac325941723cbf740507a0611606eee50d373
|
||||
size 62443
|
||||
oid sha256:60efb65986ae484b7f7da9648db252ab4aa11e0047020574244e070972813f0a
|
||||
size 62712
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3d07e63d1d9dd2a676e05c95c3562e7e2ef397d9cb2ac07f8c5ca0c3eb510024
|
||||
size 52234
|
||||
oid sha256:e9f33cdbe4d65566be99949b00a54439b90d4d010ec9fb9af505fbc043271177
|
||||
size 52462
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3d07e63d1d9dd2a676e05c95c3562e7e2ef397d9cb2ac07f8c5ca0c3eb510024
|
||||
size 52234
|
||||
oid sha256:e9f33cdbe4d65566be99949b00a54439b90d4d010ec9fb9af505fbc043271177
|
||||
size 52462
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3c89484df5502aa6935e8c6babdcceb49b6701fbb4099437046bcffc22764ec1
|
||||
size 129589
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:206708dbbb48e6f68d21b98103fd8600ca595395b0f3695a28560bf48a4a7ff8
|
||||
size 139089
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:42f5682c78a9786db0ca354c284b369d666cec7136682e82edc01922d48a2d68
|
||||
size 79075
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:df0d7583c6493dd81930c92f23c74e60467743a94bada42ecf6fb17121ccb117
|
||||
size 100746
|
||||
@@ -49,21 +49,14 @@ class LocalizationTests: XCTestCase {
|
||||
// set app language to English
|
||||
Bundle.overrideLocalizations = ["en"]
|
||||
|
||||
XCTAssertEqual(L10n.commonMemberCount(1), "1 member")
|
||||
XCTAssertEqual(L10n.commonMemberCount(2), "2 members")
|
||||
XCTAssertEqual(L10n.commonMemberCount(1), "1 Member")
|
||||
XCTAssertEqual(L10n.commonMemberCount(2), "2 Members")
|
||||
|
||||
// set app language to Italian
|
||||
Bundle.overrideLocalizations = ["it"]
|
||||
|
||||
XCTAssertEqual(L10n.commonMemberCount(1), "1 membro")
|
||||
XCTAssertEqual(L10n.commonMemberCount(2), "2 membri")
|
||||
|
||||
// // set app language to Polish
|
||||
// Bundle.overrideLocalizations = ["pl"]
|
||||
//
|
||||
// XCTAssertEqual(L10n.commonMemberCount(1), "1 sekunda") // one
|
||||
// XCTAssertEqual(L10n.commonMemberCount(2), "2 sekundy") // few
|
||||
// XCTAssertEqual(L10n.commonMemberCount(3), "5 sekund") // many, other
|
||||
XCTAssertEqual(L10n.commonMemberCount(1), "1 Membro")
|
||||
XCTAssertEqual(L10n.commonMemberCount(2), "2 Membri")
|
||||
}
|
||||
|
||||
/// Test plurals fallback language for a language not supported at all
|
||||
@@ -71,8 +64,8 @@ class LocalizationTests: XCTestCase {
|
||||
// set app language to something Element don't support at all ("invalid identifier")
|
||||
Bundle.overrideLocalizations = ["xx"]
|
||||
|
||||
XCTAssertEqual(L10n.commonMemberCount(1), "1 member")
|
||||
XCTAssertEqual(L10n.commonMemberCount(2), "2 members")
|
||||
XCTAssertEqual(L10n.commonMemberCount(1), "1 Member")
|
||||
XCTAssertEqual(L10n.commonMemberCount(2), "2 Members")
|
||||
}
|
||||
|
||||
/// Test untranslated strings
|
||||
|
||||
@@ -38,8 +38,8 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
|
||||
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.first?.id, RoomMemberProxyMock.mockModerator.userID)
|
||||
XCTAssertEqual(context.viewState.membersWithRole.count, 3)
|
||||
XCTAssert(context.viewState.membersWithRole.first(where: { $0.id == RoomMemberProxyMock.mockModerator.userID }) != nil)
|
||||
XCTAssertFalse(context.viewState.hasChanges)
|
||||
XCTAssertFalse(context.viewState.isSearching)
|
||||
}
|
||||
@@ -55,7 +55,7 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(context.viewState.membersToPromote, [firstUser])
|
||||
XCTAssertEqual(context.viewState.membersToDemote, [])
|
||||
XCTAssertEqual(context.viewState.membersWithRole.count, 2)
|
||||
XCTAssertEqual(context.viewState.membersWithRole.count, 4)
|
||||
XCTAssertTrue(context.viewState.membersWithRole.contains(firstUser))
|
||||
XCTAssertTrue(context.viewState.hasChanges)
|
||||
}
|
||||
@@ -71,7 +71,7 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(context.viewState.membersToPromote, [])
|
||||
XCTAssertEqual(context.viewState.membersToDemote, [])
|
||||
XCTAssertEqual(context.viewState.membersWithRole.count, 1)
|
||||
XCTAssertEqual(context.viewState.membersWithRole.count, 3)
|
||||
XCTAssertFalse(context.viewState.membersWithRole.contains(firstUser))
|
||||
XCTAssertFalse(context.viewState.hasChanges)
|
||||
}
|
||||
@@ -87,14 +87,14 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(context.viewState.membersToPromote, [])
|
||||
XCTAssertEqual(context.viewState.membersToDemote, [])
|
||||
XCTAssertEqual(context.viewState.membersWithRole.count, 1)
|
||||
XCTAssertEqual(context.viewState.membersWithRole.count, 3)
|
||||
XCTAssertFalse(context.viewState.membersWithRole.contains(firstUser))
|
||||
XCTAssertFalse(context.viewState.hasChanges)
|
||||
}
|
||||
|
||||
func testToggleModeratorOff() {
|
||||
testInitialStateModerators()
|
||||
guard let existingModerator = context.viewState.membersWithRole.first else {
|
||||
guard let existingModerator = context.viewState.membersWithRole.first(where: { $0.role == .moderator }) else {
|
||||
XCTFail("There should be a member with the role before we begin.")
|
||||
return
|
||||
}
|
||||
@@ -103,7 +103,7 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(context.viewState.membersToPromote, [])
|
||||
XCTAssertEqual(context.viewState.membersToDemote, [existingModerator])
|
||||
XCTAssertEqual(context.viewState.membersWithRole.count, 0)
|
||||
XCTAssertEqual(context.viewState.membersWithRole.count, 2)
|
||||
XCTAssertFalse(context.viewState.membersWithRole.contains(existingModerator))
|
||||
XCTAssertTrue(context.viewState.hasChanges)
|
||||
}
|
||||
@@ -120,14 +120,14 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(context.viewState.membersToPromote, [])
|
||||
XCTAssertEqual(context.viewState.membersToDemote, [])
|
||||
XCTAssertEqual(context.viewState.membersWithRole.count, 1)
|
||||
XCTAssertEqual(context.viewState.membersWithRole.count, 3)
|
||||
XCTAssertTrue(context.viewState.membersWithRole.contains(demotedMember))
|
||||
XCTAssertFalse(context.viewState.hasChanges)
|
||||
}
|
||||
|
||||
func testDemoteModerator() {
|
||||
testInitialStateModerators()
|
||||
guard let existingModerator = context.viewState.membersWithRole.first else {
|
||||
guard let existingModerator = context.viewState.membersWithRole.first(where: { $0.role == .moderator }) else {
|
||||
XCTFail("There should be a member with the role before we begin.")
|
||||
return
|
||||
}
|
||||
@@ -136,7 +136,7 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(context.viewState.membersToPromote, [])
|
||||
XCTAssertEqual(context.viewState.membersToDemote, [existingModerator])
|
||||
XCTAssertEqual(context.viewState.membersWithRole.count, 0)
|
||||
XCTAssertEqual(context.viewState.membersWithRole.count, 2)
|
||||
XCTAssertFalse(context.viewState.membersWithRole.contains(existingModerator))
|
||||
XCTAssertTrue(context.viewState.hasChanges)
|
||||
}
|
||||
@@ -146,7 +146,7 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
|
||||
setupViewModel(mode: .moderator)
|
||||
|
||||
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(where: { $0.role == .moderator }) else {
|
||||
XCTFail("There should be a regular user and a moderator to begin with.")
|
||||
return
|
||||
}
|
||||
@@ -192,7 +192,7 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
|
||||
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.contains { $0.userID == firstUser.id && $0.powerLevel == 100 }, true)
|
||||
}
|
||||
|
||||
private func setupViewModel(mode: RoomMemberDetails.Role) {
|
||||
private func setupViewModel(mode: RoomRole) {
|
||||
roomProxy = JoinedRoomProxyMock(.init(members: .allMembersAsAdmin))
|
||||
viewModel = RoomChangeRolesScreenViewModel(mode: mode,
|
||||
roomProxy: roomProxy,
|
||||
|
||||
@@ -55,7 +55,7 @@ class RoomRolesAndPermissionsScreenViewModelTests: XCTestCase {
|
||||
|
||||
XCTAssertTrue(roomProxy.updatePowerLevelsForUsersCalled)
|
||||
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.first?.powerLevel,
|
||||
RoomMemberDetails.Role.moderator.powerLevelValue)
|
||||
RoomRole.moderator.powerLevelValue)
|
||||
}
|
||||
|
||||
func testDemoteToMember() async throws {
|
||||
@@ -70,7 +70,7 @@ class RoomRolesAndPermissionsScreenViewModelTests: XCTestCase {
|
||||
|
||||
XCTAssertTrue(roomProxy.updatePowerLevelsForUsersCalled)
|
||||
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.first?.powerLevel,
|
||||
RoomMemberDetails.Role.user.powerLevelValue)
|
||||
RoomRole.user.powerLevelValue)
|
||||
}
|
||||
|
||||
private func setupViewModel(members: [RoomMemberProxyMock]) {
|
||||
|
||||
Reference in New Issue
Block a user