mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 10:33:45 +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;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 77;
|
objectVersion = 63;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXAggregateTarget section */
|
/* Begin PBXAggregateTarget section */
|
||||||
@@ -905,6 +905,7 @@
|
|||||||
A6FFC4C5154C446BAD6B40D8 /* TimelineItemProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8520AFD6680CBAD388F6D927 /* TimelineItemProvider.swift */; };
|
A6FFC4C5154C446BAD6B40D8 /* TimelineItemProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8520AFD6680CBAD388F6D927 /* TimelineItemProvider.swift */; };
|
||||||
A722F426FD81FC67706BB1E0 /* CustomLayoutLabelStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42236480CF0431535EBE8387 /* CustomLayoutLabelStyle.swift */; };
|
A722F426FD81FC67706BB1E0 /* CustomLayoutLabelStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42236480CF0431535EBE8387 /* CustomLayoutLabelStyle.swift */; };
|
||||||
A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.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 */; };
|
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; };
|
||||||
A808DC3F72D15C6C5A52317E /* TimelineItemDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCDA016D05107DED3B9495CB /* TimelineItemDebugView.swift */; };
|
A808DC3F72D15C6C5A52317E /* TimelineItemDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCDA016D05107DED3B9495CB /* TimelineItemDebugView.swift */; };
|
||||||
A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6E30BB748F3F480F077969 /* RoomMemberDetailsScreenModels.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = "<group>"; };
|
||||||
@@ -3755,6 +3757,7 @@
|
|||||||
40E6246F03D1FE377BC5D963 /* Room */ = {
|
40E6246F03D1FE377BC5D963 /* Room */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
A7AF83882E4388BB00ECD64C /* RoomRole.swift */,
|
||||||
C30F45308428A4D9FFDB2FB8 /* BannedRoomProxy.swift */,
|
C30F45308428A4D9FFDB2FB8 /* BannedRoomProxy.swift */,
|
||||||
0E95B3BDB80531C85CD50AE6 /* InvitedRoomProxy.swift */,
|
0E95B3BDB80531C85CD50AE6 /* InvitedRoomProxy.swift */,
|
||||||
07C6B0B087FE6601C3F77816 /* JoinedRoomProxy.swift */,
|
07C6B0B087FE6601C3F77816 /* JoinedRoomProxy.swift */,
|
||||||
@@ -6699,7 +6702,6 @@
|
|||||||
EC6D0C817B1C21D9D096505A /* XCRemoteSwiftPackageReference "Version" */,
|
EC6D0C817B1C21D9D096505A /* XCRemoteSwiftPackageReference "Version" */,
|
||||||
EE40B0E16A55BD23ECBFFD22 /* XCRemoteSwiftPackageReference "matrix-rich-text-editor-swift" */,
|
EE40B0E16A55BD23ECBFFD22 /* XCRemoteSwiftPackageReference "matrix-rich-text-editor-swift" */,
|
||||||
);
|
);
|
||||||
preferredProjectObjectVersion = 77;
|
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
@@ -7899,6 +7901,7 @@
|
|||||||
617624A97BDBB75ED3DD8156 /* RoomScreenViewModelProtocol.swift in Sources */,
|
617624A97BDBB75ED3DD8156 /* RoomScreenViewModelProtocol.swift in Sources */,
|
||||||
66832DE7B5C2E861045265DC /* RoomSelectionScreen.swift in Sources */,
|
66832DE7B5C2E861045265DC /* RoomSelectionScreen.swift in Sources */,
|
||||||
18FDE4ED6D83B0771452B43D /* RoomSelectionScreenCoordinator.swift in Sources */,
|
18FDE4ED6D83B0771452B43D /* RoomSelectionScreenCoordinator.swift in Sources */,
|
||||||
|
A7AF83892E4388BE00ECD64C /* RoomRole.swift in Sources */,
|
||||||
27FEF0F40750465195C9D6D6 /* RoomSelectionScreenModels.swift in Sources */,
|
27FEF0F40750465195C9D6D6 /* RoomSelectionScreenModels.swift in Sources */,
|
||||||
8DCA1F05C3BA6ED826F1599D /* RoomSelectionScreenViewModel.swift in Sources */,
|
8DCA1F05C3BA6ED826F1599D /* RoomSelectionScreenViewModel.swift in Sources */,
|
||||||
39DFC4B9EB6A8757210BDEC6 /* RoomSelectionScreenViewModelProtocol.swift in Sources */,
|
39DFC4B9EB6A8757210BDEC6 /* RoomSelectionScreenViewModelProtocol.swift in Sources */,
|
||||||
@@ -8446,9 +8449,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@loader_path/Frameworks",
|
"@loader_path/Frameworks",
|
||||||
);
|
);
|
||||||
OTHER_SWIFT_FLAGS = (
|
OTHER_SWIFT_FLAGS = "-DRELEASE";
|
||||||
"-DRELEASE",
|
|
||||||
);
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.accessibility.tests";
|
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.accessibility.tests";
|
||||||
PRODUCT_NAME = AccessibilityTests;
|
PRODUCT_NAME = AccessibilityTests;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -8467,9 +8468,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@loader_path/Frameworks",
|
"@loader_path/Frameworks",
|
||||||
);
|
);
|
||||||
OTHER_SWIFT_FLAGS = (
|
OTHER_SWIFT_FLAGS = "-DDEBUG";
|
||||||
"-DDEBUG",
|
|
||||||
);
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.accessibility.tests";
|
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.accessibility.tests";
|
||||||
PRODUCT_NAME = AccessibilityTests;
|
PRODUCT_NAME = AccessibilityTests;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -8491,9 +8490,7 @@
|
|||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = "$(MARKETING_VERSION)";
|
MARKETING_VERSION = "$(MARKETING_VERSION)";
|
||||||
OTHER_SWIFT_FLAGS = (
|
OTHER_SWIFT_FLAGS = "-DIS_NSE";
|
||||||
"-DIS_NSE",
|
|
||||||
);
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.nse";
|
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.nse";
|
||||||
PRODUCT_DISPLAY_NAME = "$(APP_DISPLAY_NAME)";
|
PRODUCT_DISPLAY_NAME = "$(APP_DISPLAY_NAME)";
|
||||||
PRODUCT_NAME = NSE;
|
PRODUCT_NAME = NSE;
|
||||||
@@ -8542,9 +8539,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = "$(MARKETING_VERSION)";
|
MARKETING_VERSION = "$(MARKETING_VERSION)";
|
||||||
OTHER_SWIFT_FLAGS = (
|
OTHER_SWIFT_FLAGS = "-DIS_MAIN_APP";
|
||||||
"-DIS_MAIN_APP",
|
|
||||||
);
|
|
||||||
PILLS_UT_TYPE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).pills";
|
PILLS_UT_TYPE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).pills";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)";
|
||||||
PRODUCT_NAME = "$(APP_NAME)";
|
PRODUCT_NAME = "$(APP_NAME)";
|
||||||
@@ -8570,9 +8565,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = "$(MARKETING_VERSION)";
|
MARKETING_VERSION = "$(MARKETING_VERSION)";
|
||||||
OTHER_SWIFT_FLAGS = (
|
OTHER_SWIFT_FLAGS = "-DIS_MAIN_APP";
|
||||||
"-DIS_MAIN_APP",
|
|
||||||
);
|
|
||||||
PILLS_UT_TYPE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).pills";
|
PILLS_UT_TYPE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).pills";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)";
|
||||||
PRODUCT_NAME = "$(APP_NAME)";
|
PRODUCT_NAME = "$(APP_NAME)";
|
||||||
@@ -8797,9 +8790,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@loader_path/Frameworks",
|
"@loader_path/Frameworks",
|
||||||
);
|
);
|
||||||
OTHER_SWIFT_FLAGS = (
|
OTHER_SWIFT_FLAGS = "-DDEBUG";
|
||||||
"-DDEBUG",
|
|
||||||
);
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.ui.tests";
|
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.ui.tests";
|
||||||
PRODUCT_NAME = UITests;
|
PRODUCT_NAME = UITests;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -8818,9 +8809,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@loader_path/Frameworks",
|
"@loader_path/Frameworks",
|
||||||
);
|
);
|
||||||
OTHER_SWIFT_FLAGS = (
|
OTHER_SWIFT_FLAGS = "-DRELEASE";
|
||||||
"-DRELEASE",
|
|
||||||
);
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.ui.tests";
|
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.ui.tests";
|
||||||
PRODUCT_NAME = UITests;
|
PRODUCT_NAME = UITests;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -8842,9 +8831,7 @@
|
|||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = "$(MARKETING_VERSION)";
|
MARKETING_VERSION = "$(MARKETING_VERSION)";
|
||||||
OTHER_SWIFT_FLAGS = (
|
OTHER_SWIFT_FLAGS = "-DIS_NSE";
|
||||||
"-DIS_NSE",
|
|
||||||
);
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.nse";
|
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.nse";
|
||||||
PRODUCT_DISPLAY_NAME = "$(APP_DISPLAY_NAME)";
|
PRODUCT_DISPLAY_NAME = "$(APP_DISPLAY_NAME)";
|
||||||
PRODUCT_NAME = NSE;
|
PRODUCT_NAME = NSE;
|
||||||
|
|||||||
@@ -242,6 +242,10 @@
|
|||||||
"common_poll_undisclosed_text" = "Results will show after the poll has ended";
|
"common_poll_undisclosed_text" = "Results will show after the poll has ended";
|
||||||
"common_preparing" = "Preparing…";
|
"common_preparing" = "Preparing…";
|
||||||
"common_privacy_policy" = "Privacy policy";
|
"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_reaction" = "Reaction";
|
||||||
"common_reactions" = "Reactions";
|
"common_reactions" = "Reactions";
|
||||||
"common_reason" = "Reason";
|
"common_reason" = "Reason";
|
||||||
@@ -556,6 +560,7 @@
|
|||||||
"screen_room_pinned_banner_view_all_button_title" = "View All";
|
"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_title" = "%1$@ wants to join this room";
|
||||||
"screen_room_single_knock_request_view_button_title" = "View";
|
"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_pinned_events_row_title" = "Pinned messages";
|
||||||
"screen_room_details_profile_row_title" = "Profile";
|
"screen_room_details_profile_row_title" = "Profile";
|
||||||
"screen_room_details_requests_to_join_title" = "Requests to join";
|
"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_action_create_room" = "New room";
|
||||||
"screen_create_room_error_creating_room" = "An error occurred when creating the 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_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_public_option_description" = "Anyone can find this room.\nYou can change this anytime in room settings.";
|
||||||
"screen_create_room_topic_label" = "Topic (optional)";
|
"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.";
|
"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_already_invited" = "Already invited";
|
||||||
"screen_room_details_badge_encrypted" = "Encrypted";
|
"screen_room_details_badge_encrypted" = "Encrypted";
|
||||||
"screen_room_details_badge_not_encrypted" = "Not 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_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" = "There was an unknown error and the information couldn't be changed.";
|
||||||
"screen_room_details_edition_error_title" = "Unable to update room";
|
"screen_room_details_edition_error_title" = "Unable to update room";
|
||||||
@@ -1295,6 +1298,7 @@
|
|||||||
"screen_create_account_title" = "Create account";
|
"screen_create_account_title" = "Create account";
|
||||||
"screen_create_poll_cancel_confirmation_content_ios" = "Your changes won’t be saved";
|
"screen_create_poll_cancel_confirmation_content_ios" = "Your changes won’t be saved";
|
||||||
"screen_create_room_add_people_title" = "Invite people";
|
"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_public_option_title" = "Public room";
|
||||||
"screen_create_room_room_name_label" = "Room name";
|
"screen_create_room_room_name_label" = "Room name";
|
||||||
"screen_create_room_title" = "Create a room";
|
"screen_create_room_title" = "Create a room";
|
||||||
@@ -1324,6 +1328,7 @@
|
|||||||
"screen_room_change_role_section_moderators" = "Moderators";
|
"screen_room_change_role_section_moderators" = "Moderators";
|
||||||
"screen_room_change_role_section_users" = "Members";
|
"screen_room_change_role_section_users" = "Members";
|
||||||
"screen_room_change_role_unsaved_changes_title" = "Save changes?";
|
"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_invite_people_title" = "Invite people";
|
||||||
"screen_room_details_leave_conversation_title" = "Leave conversation";
|
"screen_room_details_leave_conversation_title" = "Leave conversation";
|
||||||
"screen_room_details_leave_room_title" = "Leave room";
|
"screen_room_details_leave_room_title" = "Leave room";
|
||||||
|
|||||||
@@ -77,9 +77,9 @@
|
|||||||
<key>NSStringFormatValueTypeKey</key>
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
<string>d</string>
|
<string>d</string>
|
||||||
<key>one</key>
|
<key>one</key>
|
||||||
<string>%1$d member</string>
|
<string>%1$d Member</string>
|
||||||
<key>other</key>
|
<key>other</key>
|
||||||
<string>%1$d members</string>
|
<string>%1$d Members</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>common_poll_votes_count</key>
|
<key>common_poll_votes_count</key>
|
||||||
|
|||||||
@@ -77,9 +77,9 @@
|
|||||||
<key>NSStringFormatValueTypeKey</key>
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
<string>d</string>
|
<string>d</string>
|
||||||
<key>one</key>
|
<key>one</key>
|
||||||
<string>%1$d membro</string>
|
<string>%1$d Membro</string>
|
||||||
<key>other</key>
|
<key>other</key>
|
||||||
<string>%1$d membri</string>
|
<string>%1$d Membri</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>common_poll_votes_count</key>
|
<key>common_poll_votes_count</key>
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ enum AppRoute: Hashable {
|
|||||||
case chatBackupSettings
|
case chatBackupSettings
|
||||||
/// An external share request e.g. from the ShareExtension
|
/// An external share request e.g. from the ShareExtension
|
||||||
case share(ShareExtensionPayload)
|
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.
|
/// Whether or not the route should be handled by the authentication flow.
|
||||||
var isAuthenticationRoute: Bool {
|
var isAuthenticationRoute: Bool {
|
||||||
|
|||||||
@@ -202,6 +202,12 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
case .accountProvisioningLink:
|
case .accountProvisioningLink:
|
||||||
break // We always ignore this flow when logged in.
|
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 .roomDetails: .roomDetails(roomID: roomID)
|
||||||
case .eventID(let eventID): .event(eventID: eventID, roomID: roomID, via: via) // ignored.
|
case .eventID(let eventID): .event(eventID: eventID, roomID: roomID, via: via) // ignored.
|
||||||
case .share(let payload): .share(payload)
|
case .share(let payload): .share(payload)
|
||||||
|
case .transferOwnership: .transferOwnership(roomID: roomID)
|
||||||
}
|
}
|
||||||
roomFlowCoordinator.handleAppRoute(route, animated: animated)
|
roomFlowCoordinator.handleAppRoute(route, animated: animated)
|
||||||
} else {
|
} else {
|
||||||
@@ -449,6 +456,8 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
actionsSubject.send(.logout)
|
actionsSubject.send(.logout)
|
||||||
case .presentDeclineAndBlock(let userID, let roomID):
|
case .presentDeclineAndBlock(let userID, let roomID):
|
||||||
stateMachine.processEvent(.presentDeclineAndBlockScreen(userID: userID, roomID: roomID))
|
stateMachine.processEvent(.presentDeclineAndBlockScreen(userID: userID, roomID: roomID))
|
||||||
|
case .transferOwnership(let roomIdentifier):
|
||||||
|
handleAppRoute(.transferOwnership(roomID: roomIdentifier), animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
@@ -555,6 +564,8 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
coordinator.handleAppRoute(.roomDetails(roomID: roomID), animated: animated)
|
coordinator.handleAppRoute(.roomDetails(roomID: roomID), animated: animated)
|
||||||
case .share(let payload):
|
case .share(let payload):
|
||||||
coordinator.handleAppRoute(.share(payload), animated: animated)
|
coordinator.handleAppRoute(.share(payload), animated: animated)
|
||||||
|
case .transferOwnership:
|
||||||
|
coordinator.handleAppRoute(.transferOwnership(roomID: roomID), animated: animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class EncryptionSettingsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case .roomList, .room, .roomAlias, .childRoom, .childRoomAlias,
|
case .roomList, .room, .roomAlias, .childRoom, .childRoomAlias,
|
||||||
.roomDetails, .roomMemberDetails, .userProfile,
|
.roomDetails, .roomMemberDetails, .userProfile,
|
||||||
.event, .eventOnRoomAlias, .childEvent, .childEventOnRoomAlias,
|
.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.
|
// These routes aren't in this flow so clear the entire stack.
|
||||||
clearRoute(animated: animated)
|
clearRoute(animated: animated)
|
||||||
case .chatBackupSettings:
|
case .chatBackupSettings:
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ enum RoomFlowCoordinatorEntryPoint: Hashable {
|
|||||||
case roomDetails
|
case roomDetails
|
||||||
/// An external media share request
|
/// An external media share request
|
||||||
case share(ShareExtensionPayload)
|
case share(ShareExtensionPayload)
|
||||||
|
/// The flow to change the the owner of the room
|
||||||
|
case transferOwnership
|
||||||
|
|
||||||
var isEventID: Bool {
|
var isEventID: Bool {
|
||||||
guard case .eventID = self else { return false }
|
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.
|
break // These are converted to a room ID route one level above.
|
||||||
case .accountProvisioningLink, .roomList, .userProfile, .call, .genericCallLink, .settings, .chatBackupSettings:
|
case .accountProvisioningLink, .roomList, .userProfile, .call, .genericCallLink, .settings, .chatBackupSettings:
|
||||||
break // These routes can't be handled.
|
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 {
|
private func presentCallScreen(roomID: String) async {
|
||||||
guard case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(roomID) else {
|
guard case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(roomID) else {
|
||||||
return
|
return
|
||||||
@@ -798,6 +835,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
stateMachine.tryEvent(.presentRoomMemberDetails(userID: userID))
|
stateMachine.tryEvent(.presentRoomMemberDetails(userID: userID))
|
||||||
case .presentReportRoomScreen:
|
case .presentReportRoomScreen:
|
||||||
stateMachine.tryEvent(.presentReportRoomScreen)
|
stateMachine.tryEvent(.presentReportRoomScreen)
|
||||||
|
case .transferOwnership:
|
||||||
|
presentTransferOwnershipScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
@@ -1555,6 +1594,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
coordinator.handleAppRoute(.roomDetails(roomID: roomID), animated: true)
|
coordinator.handleAppRoute(.roomDetails(roomID: roomID), animated: true)
|
||||||
case .share(let payload):
|
case .share(let payload):
|
||||||
coordinator.handleAppRoute(.share(payload), animated: true)
|
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
|
stateMachine.addRoutes(event: .changeRoles, transitions: [.rolesAndPermissionsScreen => .changingRoles]) { [weak self] context in
|
||||||
guard let role = context.userInfo as? RoomRolesAndPermissionsScreenRole else { fatalError("Expected a role") }
|
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])
|
stateMachine.addRoutes(event: .finishedChangingRoles, transitions: [.changingRoles => .rolesAndPermissionsScreen])
|
||||||
|
|
||||||
@@ -150,12 +156,7 @@ class RoomRolesAndPermissionsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentChangeRolesScreen(role: RoomRolesAndPermissionsScreenRole) {
|
private func presentChangeRolesScreen(mode: RoomRole) {
|
||||||
let mode = switch role {
|
|
||||||
case .administrators: RoomMemberDetails.Role.administrator
|
|
||||||
case .moderators: RoomMemberDetails.Role.moderator
|
|
||||||
}
|
|
||||||
|
|
||||||
let parameters = RoomChangeRolesScreenCoordinatorParameters(mode: mode,
|
let parameters = RoomChangeRolesScreenCoordinatorParameters(mode: mode,
|
||||||
roomProxy: roomProxy,
|
roomProxy: roomProxy,
|
||||||
mediaProvider: mediaProvider,
|
mediaProvider: mediaProvider,
|
||||||
|
|||||||
@@ -542,6 +542,14 @@ internal enum L10n {
|
|||||||
internal static var commonPreparing: String { return L10n.tr("Localizable", "common_preparing") }
|
internal static var commonPreparing: String { return L10n.tr("Localizable", "common_preparing") }
|
||||||
/// Privacy policy
|
/// Privacy policy
|
||||||
internal static var commonPrivacyPolicy: String { return L10n.tr("Localizable", "common_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
|
/// Reaction
|
||||||
internal static var commonReaction: String { return L10n.tr("Localizable", "common_reaction") }
|
internal static var commonReaction: String { return L10n.tr("Localizable", "common_reaction") }
|
||||||
/// Reactions
|
/// Reactions
|
||||||
@@ -2150,6 +2158,8 @@ internal enum L10n {
|
|||||||
internal static var screenRoomChangePermissionsRoomTopic: String { return L10n.tr("Localizable", "screen_room_change_permissions_room_topic") }
|
internal static var screenRoomChangePermissionsRoomTopic: String { return L10n.tr("Localizable", "screen_room_change_permissions_room_topic") }
|
||||||
/// Send messages
|
/// Send messages
|
||||||
internal static var screenRoomChangePermissionsSendMessages: String { return L10n.tr("Localizable", "screen_room_change_permissions_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
|
/// Edit Admins
|
||||||
internal static var screenRoomChangeRoleAdministratorsTitle: String { return L10n.tr("Localizable", "screen_room_change_role_administrators_title") }
|
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.
|
/// 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 }
|
set(value) { underlyingPowerLevel = value }
|
||||||
}
|
}
|
||||||
var underlyingPowerLevel: RoomPowerLevel!
|
var underlyingPowerLevel: RoomPowerLevel!
|
||||||
var role: RoomMemberRole {
|
|
||||||
get { return underlyingRole }
|
|
||||||
set(value) { underlyingRole = value }
|
|
||||||
}
|
|
||||||
var underlyingRole: RoomMemberRole!
|
|
||||||
|
|
||||||
}
|
}
|
||||||
class RoomMembershipDetailsProxyMock: RoomMembershipDetailsProxyProtocol, @unchecked Sendable {
|
class RoomMembershipDetailsProxyMock: RoomMembershipDetailsProxyProtocol, @unchecked Sendable {
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ private extension RoomMember {
|
|||||||
powerLevel: proxy.powerLevel.rustPowerLevel,
|
powerLevel: proxy.powerLevel.rustPowerLevel,
|
||||||
normalizedPowerLevel: proxy.powerLevel.rustPowerLevel,
|
normalizedPowerLevel: proxy.powerLevel.rustPowerLevel,
|
||||||
isIgnored: proxy.isIgnored,
|
isIgnored: proxy.isIgnored,
|
||||||
suggestedRoleForPowerLevel: proxy.role,
|
suggestedRoleForPowerLevel: proxy.role.rustRole,
|
||||||
membershipChangeReason: proxy.membershipChangeReason)
|
membershipChangeReason: proxy.membershipChangeReason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ extension JoinedRoomProxyMock {
|
|||||||
guard case .success(let member) = await self?.getMember(userID: userID) else {
|
guard case .success(let member) = await self?.getMember(userID: userID) else {
|
||||||
return .failure(.sdkError(RoomProxyMockError.generic))
|
return .failure(.sdkError(RoomProxyMockError.generic))
|
||||||
}
|
}
|
||||||
return .success(member.role)
|
return .success(member.role.rustRole)
|
||||||
}
|
}
|
||||||
updatePowerLevelsForUsersReturnValue = .success(())
|
updatePowerLevelsForUsersReturnValue = .success(())
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ struct RoomMemberProxyMockConfiguration {
|
|||||||
var isIgnored = false
|
var isIgnored = false
|
||||||
|
|
||||||
var powerLevel = RoomPowerLevel(value: 0)
|
var powerLevel = RoomPowerLevel(value: 0)
|
||||||
var role = RoomMemberRole.user
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension RoomMemberProxyMock {
|
extension RoomMemberProxyMock {
|
||||||
@@ -36,7 +35,6 @@ extension RoomMemberProxyMock {
|
|||||||
isIgnored = configuration.isIgnored
|
isIgnored = configuration.isIgnored
|
||||||
|
|
||||||
powerLevel = configuration.powerLevel
|
powerLevel = configuration.powerLevel
|
||||||
role = configuration.role
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mocks
|
// Mocks
|
||||||
@@ -52,8 +50,15 @@ extension RoomMemberProxyMock {
|
|||||||
displayName: "Me",
|
displayName: "Me",
|
||||||
avatarURL: .mockMXCUserAvatar,
|
avatarURL: .mockMXCUserAvatar,
|
||||||
membership: .join,
|
membership: .join,
|
||||||
powerLevel: .init(value: 100),
|
powerLevel: .init(value: 100)))
|
||||||
role: .administrator))
|
}
|
||||||
|
|
||||||
|
static var mockMeCreator: RoomMemberProxyMock {
|
||||||
|
RoomMemberProxyMock(with: .init(userID: "@me:matrix.org",
|
||||||
|
displayName: "Me",
|
||||||
|
avatarURL: .mockMXCUserAvatar,
|
||||||
|
membership: .join,
|
||||||
|
powerLevel: .infinite))
|
||||||
}
|
}
|
||||||
|
|
||||||
static var mockAlice: RoomMemberProxyMock {
|
static var mockAlice: RoomMemberProxyMock {
|
||||||
@@ -116,32 +121,28 @@ extension RoomMemberProxyMock {
|
|||||||
RoomMemberProxyMock(with: .init(userID: "@admin:matrix.org",
|
RoomMemberProxyMock(with: .init(userID: "@admin:matrix.org",
|
||||||
displayName: "Arthur",
|
displayName: "Arthur",
|
||||||
membership: .join,
|
membership: .join,
|
||||||
powerLevel: .init(value: 100),
|
powerLevel: .init(value: 100)))
|
||||||
role: .administrator))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static var mockCreator: RoomMemberProxyMock {
|
static var mockCreator: RoomMemberProxyMock {
|
||||||
RoomMemberProxyMock(with: .init(userID: "@creator:matrix.org",
|
RoomMemberProxyMock(with: .init(userID: "@creator:matrix.org",
|
||||||
displayName: "God",
|
displayName: "God",
|
||||||
membership: .join,
|
membership: .join,
|
||||||
powerLevel: .infinite,
|
powerLevel: .infinite))
|
||||||
role: .creator))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static var mockOwner: RoomMemberProxyMock {
|
static var mockOwner: RoomMemberProxyMock {
|
||||||
RoomMemberProxyMock(with: .init(userID: "@owner:matrix.org",
|
RoomMemberProxyMock(with: .init(userID: "@owner:matrix.org",
|
||||||
displayName: "Guinevere",
|
displayName: "Guinevere",
|
||||||
membership: .join,
|
membership: .join,
|
||||||
powerLevel: .value(150),
|
powerLevel: .value(150)))
|
||||||
role: .administrator))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static var mockModerator: RoomMemberProxyMock {
|
static var mockModerator: RoomMemberProxyMock {
|
||||||
RoomMemberProxyMock(with: .init(userID: "@mod:matrix.org",
|
RoomMemberProxyMock(with: .init(userID: "@mod:matrix.org",
|
||||||
displayName: "Merlin",
|
displayName: "Merlin",
|
||||||
membership: .join,
|
membership: .join,
|
||||||
powerLevel: .init(value: 50),
|
powerLevel: .init(value: 50)))
|
||||||
role: .moderator))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static var mockBanned: [RoomMemberProxyMock] {
|
static var mockBanned: [RoomMemberProxyMock] {
|
||||||
@@ -181,4 +182,33 @@ extension Array where Element == RoomMemberProxyMock {
|
|||||||
.mockAdmin,
|
.mockAdmin,
|
||||||
.mockModerator
|
.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 presentReportRoom(roomIdentifier: String)
|
||||||
case presentDeclineAndBlock(userID: String, roomID: String)
|
case presentDeclineAndBlock(userID: String, roomID: String)
|
||||||
case roomLeft(roomIdentifier: String)
|
case roomLeft(roomIdentifier: String)
|
||||||
|
case transferOwnership(roomIdentifier: String)
|
||||||
case presentSettingsScreen
|
case presentSettingsScreen
|
||||||
case presentFeedbackScreen
|
case presentFeedbackScreen
|
||||||
case presentSecureBackupSettings
|
case presentSecureBackupSettings
|
||||||
@@ -86,6 +87,8 @@ final class HomeScreenCoordinator: CoordinatorProtocol {
|
|||||||
actionsSubject.send(.logout)
|
actionsSubject.send(.logout)
|
||||||
case .presentDeclineAndBlock(let userID, let roomID):
|
case .presentDeclineAndBlock(let userID, let roomID):
|
||||||
actionsSubject.send(.presentDeclineAndBlock(userID: userID, roomID: roomID))
|
actionsSubject.send(.presentDeclineAndBlock(userID: userID, roomID: roomID))
|
||||||
|
case .transferOwnership(let roomIdentifier):
|
||||||
|
actionsSubject.send(.transferOwnership(roomIdentifier: roomIdentifier))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ enum HomeScreenViewModelAction: Equatable {
|
|||||||
case presentReportRoom(roomIdentifier: String)
|
case presentReportRoom(roomIdentifier: String)
|
||||||
case presentDeclineAndBlock(userID: String, roomID: String)
|
case presentDeclineAndBlock(userID: String, roomID: String)
|
||||||
case roomLeft(roomIdentifier: String)
|
case roomLeft(roomIdentifier: String)
|
||||||
|
case transferOwnership(roomIdentifier: String)
|
||||||
case presentSecureBackupSettings
|
case presentSecureBackupSettings
|
||||||
case presentRecoveryKeyScreen
|
case presentRecoveryKeyScreen
|
||||||
case presentEncryptionResetScreen
|
case presentEncryptionResetScreen
|
||||||
|
|||||||
@@ -342,15 +342,37 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !(roomProxy.infoPublisher.value.isPrivate ?? true) {
|
guard roomProxy.infoPublisher.value.joinedMembersCount > 1 else {
|
||||||
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(roomID: roomID, isDM: roomProxy.isDirectOneToOneRoom, state: .public)
|
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(roomID: roomID,
|
||||||
} else {
|
isDM: roomProxy.isDirectOneToOneRoom,
|
||||||
state.bindings.leaveRoomAlertItem = if roomProxy.infoPublisher.value.joinedMembersCount > 1 {
|
state: roomProxy.infoPublisher.value.isPrivate ?? true ? .empty : .public)
|
||||||
LeaveRoomAlertItem(roomID: roomID, isDM: roomProxy.isDirectOneToOneRoom, state: .private)
|
return
|
||||||
} else {
|
}
|
||||||
LeaveRoomAlertItem(roomID: roomID, isDM: roomProxy.isDirectOneToOneRoom, state: .empty)
|
|
||||||
|
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
|
import SwiftUI
|
||||||
|
|
||||||
struct RoomChangeRolesScreenCoordinatorParameters {
|
struct RoomChangeRolesScreenCoordinatorParameters {
|
||||||
let mode: RoomMemberDetails.Role
|
let mode: RoomRole
|
||||||
let roomProxy: JoinedRoomProxyProtocol
|
let roomProxy: JoinedRoomProxyProtocol
|
||||||
let mediaProvider: MediaProviderProtocol
|
let mediaProvider: MediaProviderProtocol
|
||||||
let userIndicatorController: UserIndicatorControllerProtocol
|
let userIndicatorController: UserIndicatorControllerProtocol
|
||||||
|
|||||||
@@ -14,15 +14,19 @@ enum RoomChangeRolesScreenViewModelAction {
|
|||||||
|
|
||||||
struct RoomChangeRolesScreenViewState: BindableState {
|
struct RoomChangeRolesScreenViewState: BindableState {
|
||||||
/// The screen's current mode (which role we are promoting/demoting users to/from.
|
/// The screen's current mode (which role we are promoting/demoting users to/from.
|
||||||
let mode: RoomMemberDetails.Role
|
let mode: 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.
|
/// 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.
|
/// 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.
|
/// 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.
|
/// The members selected for promotion to the current role.
|
||||||
var membersToPromote: Set<RoomMemberDetails> = []
|
var membersToPromote: Set<RoomMemberDetails> = []
|
||||||
@@ -35,9 +39,17 @@ struct RoomChangeRolesScreenViewState: BindableState {
|
|||||||
/// The screen's title.
|
/// The screen's title.
|
||||||
var title: String {
|
var title: String {
|
||||||
switch mode {
|
switch mode {
|
||||||
case .creator, .owner, .administrator:
|
case .creator:
|
||||||
// TODO: Handle the creator permissions change
|
"" // The screen can't be configured with this role.
|
||||||
L10n.screenRoomChangeRoleAdministratorsTitle
|
case .owner:
|
||||||
|
L10n.screenRoomChangeRoleOwnersTitle
|
||||||
|
case .administrator:
|
||||||
|
switch ownRole {
|
||||||
|
case .creator:
|
||||||
|
L10n.screenRoomChangeRoleAdministratorsOrOwnersTitle
|
||||||
|
default:
|
||||||
|
L10n.screenRoomChangeRoleAdministratorsTitle
|
||||||
|
}
|
||||||
case .moderator:
|
case .moderator:
|
||||||
L10n.screenRoomChangeRoleModeratorsTitle
|
L10n.screenRoomChangeRoleModeratorsTitle
|
||||||
case .user:
|
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).
|
/// The visible admins in the screen (after searching).
|
||||||
var visibleAdministrators: [RoomMemberDetails] {
|
var visibleAdministrators: [RoomMemberDetails] {
|
||||||
administrators.filter { $0.matches(searchQuery: bindings.searchQuery) }
|
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.
|
/// All of the members who will gain/keep this screen's role after saving any changes.
|
||||||
var membersWithRole: [RoomMemberDetails] {
|
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.
|
/// 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.
|
/// Whether or not a specific member has this screen's role.
|
||||||
func isMemberSelected(_ member: RoomMemberDetails) -> Bool {
|
func isMemberSelected(_ member: RoomMemberDetails) -> Bool {
|
||||||
guard !membersToDemote.contains(member) else { return false }
|
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 {
|
enum RoomChangeRolesScreenAlertType {
|
||||||
/// A warning that a particular promotion can't be undone.
|
/// A warning that a particular promotion can't be undone.
|
||||||
case promotionWarning
|
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.
|
/// A confirmation that the user would like to discard any unsaved changes.
|
||||||
case discardChanges
|
case discardChanges
|
||||||
/// The generic error message.
|
/// The generic error message.
|
||||||
|
|||||||
+34
-15
@@ -20,22 +20,19 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh
|
|||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(mode: RoomMemberDetails.Role,
|
init(mode: RoomRole,
|
||||||
roomProxy: JoinedRoomProxyProtocol,
|
roomProxy: JoinedRoomProxyProtocol,
|
||||||
mediaProvider: MediaProviderProtocol,
|
mediaProvider: MediaProviderProtocol,
|
||||||
userIndicatorController: UserIndicatorControllerProtocol,
|
userIndicatorController: UserIndicatorControllerProtocol,
|
||||||
analytics: AnalyticsService) {
|
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.roomProxy = roomProxy
|
||||||
self.userIndicatorController = userIndicatorController
|
self.userIndicatorController = userIndicatorController
|
||||||
self.analytics = analytics
|
self.analytics = analytics
|
||||||
|
|
||||||
super.init(initialViewState: RoomChangeRolesScreenViewState(mode: mode,
|
super.init(initialViewState: RoomChangeRolesScreenViewState(mode: mode,
|
||||||
administrators: [],
|
ownRole: roomProxy.membersPublisher.value.first { $0.userID == roomProxy.id }?.role ?? .administrator),
|
||||||
moderators: [],
|
|
||||||
users: [],
|
|
||||||
bindings: .init()),
|
|
||||||
mediaProvider: mediaProvider)
|
mediaProvider: mediaProvider)
|
||||||
|
|
||||||
roomProxy.membersPublisher
|
roomProxy.membersPublisher
|
||||||
@@ -64,11 +61,16 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh
|
|||||||
case .demoteMember(let member):
|
case .demoteMember(let member):
|
||||||
demoteMember(member)
|
demoteMember(member)
|
||||||
case .save:
|
case .save:
|
||||||
if state.mode.isAdminOrHigher, !state.membersToPromote.isEmpty {
|
if !state.membersToPromote.isEmpty {
|
||||||
showPromotionWarning()
|
if state.mode == .administrator, state.ownRole == .administrator {
|
||||||
} else {
|
showPromotionWarning()
|
||||||
Task { await save() }
|
return
|
||||||
|
} else if state.mode == .owner {
|
||||||
|
showTransferOwnershipWarning()
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Task { await save() }
|
||||||
case .cancel:
|
case .cancel:
|
||||||
confirmDiscardChanges()
|
confirmDiscardChanges()
|
||||||
}
|
}
|
||||||
@@ -77,17 +79,23 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh
|
|||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func updateMembers(_ members: [RoomMemberProxyProtocol]) {
|
private func updateMembers(_ members: [RoomMemberProxyProtocol]) {
|
||||||
|
var owners = [RoomMemberDetails]()
|
||||||
var administrators = [RoomMemberDetails]()
|
var administrators = [RoomMemberDetails]()
|
||||||
var moderators = [RoomMemberDetails]()
|
var moderators = [RoomMemberDetails]()
|
||||||
var users = [RoomMemberDetails]()
|
var users = [RoomMemberDetails]()
|
||||||
|
|
||||||
for member in members.sorted() {
|
for member in members.sorted() {
|
||||||
|
if member.userID == roomProxy.ownUserID {
|
||||||
|
state.ownRole = member.role
|
||||||
|
}
|
||||||
|
|
||||||
guard member.isActive else { continue }
|
guard member.isActive else { continue }
|
||||||
let memberDetails = RoomMemberDetails(withProxy: member)
|
let memberDetails = RoomMemberDetails(withProxy: member)
|
||||||
|
|
||||||
switch member.role {
|
switch memberDetails.role {
|
||||||
// TODO: Create a owner list
|
case .creator, .owner:
|
||||||
case .administrator, .creator:
|
owners.append(memberDetails)
|
||||||
|
case .administrator:
|
||||||
administrators.append(memberDetails)
|
administrators.append(memberDetails)
|
||||||
case .moderator:
|
case .moderator:
|
||||||
moderators.append(memberDetails)
|
moderators.append(memberDetails)
|
||||||
@@ -96,6 +104,7 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.owners = owners
|
||||||
state.administrators = administrators
|
state.administrators = administrators
|
||||||
state.moderators = moderators
|
state.moderators = moderators
|
||||||
state.users = users
|
state.users = users
|
||||||
@@ -107,9 +116,9 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh
|
|||||||
} else if state.membersToDemote.contains(member) {
|
} else if state.membersToDemote.contains(member) {
|
||||||
state.membersToDemote.remove(member)
|
state.membersToDemote.remove(member)
|
||||||
state.lastPromotedMember = member
|
state.lastPromotedMember = member
|
||||||
} else if member.role == state.mode {
|
} else if member.role >= state.mode, member.role <= state.maxDemotableRole {
|
||||||
state.membersToDemote.insert(member)
|
state.membersToDemote.insert(member)
|
||||||
} else {
|
} else if member.role < state.mode {
|
||||||
state.membersToPromote.insert(member)
|
state.membersToPromote.insert(member)
|
||||||
state.lastPromotedMember = member
|
state.lastPromotedMember = member
|
||||||
}
|
}
|
||||||
@@ -133,6 +142,16 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh
|
|||||||
secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil))
|
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 {
|
private func save() async {
|
||||||
showSavingIndicator()
|
showSavingIndicator()
|
||||||
|
|
||||||
|
|||||||
@@ -45,15 +45,18 @@ struct RoomChangeRolesScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RoomChangeRolesScreenSection(members: context.viewState.visibleOwners,
|
||||||
|
role: .owner,
|
||||||
|
context: context)
|
||||||
|
|
||||||
RoomChangeRolesScreenSection(members: context.viewState.visibleAdministrators,
|
RoomChangeRolesScreenSection(members: context.viewState.visibleAdministrators,
|
||||||
title: L10n.screenRoomChangeRoleSectionAdministrators,
|
role: .administrator,
|
||||||
isAdministratorsSection: true,
|
|
||||||
context: context)
|
context: context)
|
||||||
RoomChangeRolesScreenSection(members: context.viewState.visibleModerators,
|
RoomChangeRolesScreenSection(members: context.viewState.visibleModerators,
|
||||||
title: L10n.screenRoomChangeRoleSectionModerators,
|
role: .moderator,
|
||||||
context: context)
|
context: context)
|
||||||
RoomChangeRolesScreenSection(members: context.viewState.visibleUsers,
|
RoomChangeRolesScreenSection(members: context.viewState.visibleUsers,
|
||||||
title: L10n.screenRoomChangeRoleSectionUsers,
|
role: .user,
|
||||||
context: context)
|
context: context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,10 +68,12 @@ struct RoomChangeRolesScreen: View {
|
|||||||
ScrollViewReader { scrollView in
|
ScrollViewReader { scrollView in
|
||||||
HStack(spacing: 16) {
|
HStack(spacing: 16) {
|
||||||
ForEach(context.viewState.membersWithRole, id: \.id) { member in
|
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))
|
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
|
.onChange(of: context.viewState.lastPromotedMember) { _, newValue in
|
||||||
@@ -104,10 +109,22 @@ struct RoomChangeRolesScreen: View {
|
|||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
struct RoomChangeRolesScreen_Previews: PreviewProvider, TestablePreview {
|
struct RoomChangeRolesScreen_Previews: PreviewProvider, TestablePreview {
|
||||||
static let administratorViewModel = makeViewModel(mode: .administrator)
|
static let ownerViewModel = makeViewModel(mode: .owner, ownRole: .creator)
|
||||||
static let moderatorViewModel = makeViewModel(mode: .moderator)
|
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 {
|
static var previews: some View {
|
||||||
|
NavigationStack {
|
||||||
|
RoomChangeRolesScreen(context: ownerViewModel.context)
|
||||||
|
}
|
||||||
|
.previewDisplayName("Owners")
|
||||||
|
|
||||||
|
NavigationStack {
|
||||||
|
RoomChangeRolesScreen(context: administratorOrOwnerViewModel.context)
|
||||||
|
}
|
||||||
|
.previewDisplayName("Administrator or Owners")
|
||||||
|
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
RoomChangeRolesScreen(context: administratorViewModel.context)
|
RoomChangeRolesScreen(context: administratorViewModel.context)
|
||||||
}
|
}
|
||||||
@@ -119,11 +136,18 @@ struct RoomChangeRolesScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
.previewDisplayName("Moderators")
|
.previewDisplayName("Moderators")
|
||||||
}
|
}
|
||||||
|
|
||||||
static func makeViewModel(mode: RoomMemberDetails.Role) -> RoomChangeRolesScreenViewModel {
|
static func makeViewModel(mode: RoomRole, ownRole: RoomRole) -> RoomChangeRolesScreenViewModel {
|
||||||
RoomChangeRolesScreenViewModel(mode: mode,
|
let members: [RoomMemberProxyMock] = switch ownRole {
|
||||||
roomProxy: JoinedRoomProxyMock(.init(members: .allMembersAsAdmin)),
|
case .creator:
|
||||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
.allMembersAsCreator
|
||||||
userIndicatorController: UserIndicatorControllerMock(),
|
default:
|
||||||
analytics: ServiceLocator.shared.analytics)
|
.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 {
|
struct RoomChangeRolesScreenSection: View {
|
||||||
let members: [RoomMemberDetails]
|
let members: [RoomMemberDetails]
|
||||||
let title: String
|
let role: RoomRole
|
||||||
var isAdministratorsSection = false
|
|
||||||
|
|
||||||
let context: RoomChangeRolesScreenViewModel.Context
|
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 {
|
var body: some View {
|
||||||
if !members.isEmpty {
|
if !members.isEmpty {
|
||||||
Section {
|
Section {
|
||||||
ForEach(members, id: \.id) { member in
|
ForEach(members, id: \.id) { member in
|
||||||
RoomChangeRolesScreenRow(member: member,
|
RoomChangeRolesScreenRow(member: member,
|
||||||
mediaProvider: context.mediaProvider,
|
mediaProvider: context.mediaProvider,
|
||||||
isSelected: isMemberSelected(member)) {
|
isSelected: context.viewState.isMemberSelected(member)) {
|
||||||
context.send(viewAction: .toggleMember(member))
|
context.send(viewAction: .toggleMember(member))
|
||||||
}
|
}
|
||||||
.disabled(member.role.isAdminOrHigher)
|
.disabled(context.viewState.isMemberDisabled(member))
|
||||||
}
|
}
|
||||||
} header: {
|
} header: {
|
||||||
Text(title)
|
Text(title)
|
||||||
.compoundListSectionHeader()
|
.compoundListSectionHeader()
|
||||||
} footer: {
|
} footer: {
|
||||||
if isAdministratorsSection, context.viewState.mode == .moderator {
|
if role == .administrator, context.viewState.mode == .moderator {
|
||||||
Text(L10n.screenRoomChangeRoleModeratorsAdminSectionFooter)
|
Text(L10n.screenRoomChangeRoleModeratorsAdminSectionFooter)
|
||||||
.compoundListSectionFooter()
|
.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 {
|
struct RoomChangeRolesScreenSelectedItem: View {
|
||||||
let member: RoomMemberDetails
|
let member: RoomMemberDetails
|
||||||
let mediaProvider: MediaProviderProtocol?
|
let mediaProvider: MediaProviderProtocol?
|
||||||
let dismissAction: () -> Void
|
let dismissAction: (() -> Void)?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
mainContent
|
||||||
|
.accessibilityActions {
|
||||||
|
if let dismissAction {
|
||||||
|
Button(L10n.actionDismiss) {
|
||||||
|
dismissAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private var mainContent: some View {
|
||||||
VStack(spacing: 4) {
|
VStack(spacing: 4) {
|
||||||
avatar
|
avatar
|
||||||
|
|
||||||
@@ -22,13 +35,8 @@ struct RoomChangeRolesScreenSelectedItem: View {
|
|||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
}
|
}
|
||||||
.accessibilityElement(children: .combine)
|
.accessibilityElement(children: .combine)
|
||||||
.accessibilityAction(named: L10n.actionDismiss) {
|
|
||||||
dismissAction()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
|
||||||
|
|
||||||
var avatar: some View {
|
var avatar: some View {
|
||||||
LoadableAvatarImage(url: member.avatarURL,
|
LoadableAvatarImage(url: member.avatarURL,
|
||||||
name: member.name,
|
name: member.name,
|
||||||
@@ -37,7 +45,7 @@ struct RoomChangeRolesScreenSelectedItem: View {
|
|||||||
mediaProvider: mediaProvider)
|
mediaProvider: mediaProvider)
|
||||||
.accessibilityHidden(true)
|
.accessibilityHidden(true)
|
||||||
.overlay(alignment: .topTrailing) {
|
.overlay(alignment: .topTrailing) {
|
||||||
if member.role != .administrator {
|
if let dismissAction {
|
||||||
Button(action: dismissAction) {
|
Button(action: dismissAction) {
|
||||||
Image(systemName: "xmark.circle.fill")
|
Image(systemName: "xmark.circle.fill")
|
||||||
.resizable()
|
.resizable()
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ enum RoomDetailsScreenCoordinatorAction {
|
|||||||
case presentKnockingRequestsListScreen
|
case presentKnockingRequestsListScreen
|
||||||
case presentSecurityAndPrivacyScreen
|
case presentSecurityAndPrivacyScreen
|
||||||
case presentReportRoomScreen
|
case presentReportRoomScreen
|
||||||
|
case transferOwnership
|
||||||
}
|
}
|
||||||
|
|
||||||
final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
|
final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
|
||||||
@@ -94,6 +95,8 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
|
|||||||
actionsSubject.send(.presentRecipientDetails(userID: userID))
|
actionsSubject.send(.presentRecipientDetails(userID: userID))
|
||||||
case .displayReportRoom:
|
case .displayReportRoom:
|
||||||
actionsSubject.send(.presentReportRoomScreen)
|
actionsSubject.send(.presentReportRoomScreen)
|
||||||
|
case .transferOwnership:
|
||||||
|
actionsSubject.send(.transferOwnership)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ enum RoomDetailsScreenViewModelAction: Equatable {
|
|||||||
case displayKnockingRequests
|
case displayKnockingRequests
|
||||||
case displaySecurityAndPrivacy
|
case displaySecurityAndPrivacy
|
||||||
case displayReportRoom
|
case displayReportRoom
|
||||||
|
case transferOwnership
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: View
|
// MARK: View
|
||||||
@@ -288,6 +289,8 @@ enum RoomDetailsScreenErrorType: Hashable {
|
|||||||
case alert
|
case alert
|
||||||
/// Leaving room has failed..
|
/// Leaving room has failed..
|
||||||
case unknown
|
case unknown
|
||||||
|
/// Last owner
|
||||||
|
case lastOwner
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RoomDetailsScreenPinnedEventsActionState {
|
enum RoomDetailsScreenPinnedEventsActionState {
|
||||||
|
|||||||
@@ -119,13 +119,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
|||||||
case .processTapInvite:
|
case .processTapInvite:
|
||||||
actionsSubject.send(.requestInvitePeoplePresentation)
|
actionsSubject.send(.requestInvitePeoplePresentation)
|
||||||
case .processTapLeave:
|
case .processTapLeave:
|
||||||
guard state.joinedMembersCount > 1 else {
|
processTapToLeave()
|
||||||
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)
|
|
||||||
case .confirmLeave:
|
case .confirmLeave:
|
||||||
Task { await leaveRoom() }
|
Task { await leaveRoom() }
|
||||||
case .processTapIgnore:
|
case .processTapIgnore:
|
||||||
@@ -177,6 +171,38 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
|||||||
|
|
||||||
// MARK: - Private
|
// 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() {
|
private func setupRoomSubscription() {
|
||||||
roomProxy.infoPublisher
|
roomProxy.infoPublisher
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
@@ -237,11 +263,6 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func fetchMembersIfNeeded() async {
|
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
|
roomProxy.membersPublisher
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self, ownUserID = roomProxy.ownUserID] members in
|
.sink { [weak self, ownUserID = roomProxy.ownUserID] members in
|
||||||
@@ -251,6 +272,10 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
|||||||
self.state.accountOwner = .init(withProxy: accountOwner)
|
self.state.accountOwner = .init(withProxy: accountOwner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard roomProxy.isDirectOneToOneRoom else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if let dmRecipient = members.first(where: { $0.userID != ownUserID }) {
|
if let dmRecipient = members.first(where: { $0.userID != ownUserID }) {
|
||||||
self.state.dmRecipientInfo = .init(member: .init(withProxy: dmRecipient))
|
self.state.dmRecipientInfo = .init(member: .init(withProxy: dmRecipient))
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -17,12 +17,16 @@ enum RoomRolesAndPermissionsScreenViewModelAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct RoomRolesAndPermissionsScreenViewState: BindableState {
|
struct RoomRolesAndPermissionsScreenViewState: BindableState {
|
||||||
|
var ownRole: RoomRole
|
||||||
|
|
||||||
|
var administratorsAndOwnersCount: Int?
|
||||||
/// The number of administrators in the room.
|
/// The number of administrators in the room.
|
||||||
var administratorCount: Int?
|
var administratorCount: Int?
|
||||||
/// The number of moderators in the room.
|
/// The number of moderators in the room.
|
||||||
var moderatorCount: Int?
|
var moderatorCount: Int?
|
||||||
/// The permissions of the room when loaded.
|
/// The permissions of the room when loaded.
|
||||||
var permissions: RoomPermissions?
|
var permissions: RoomPermissions?
|
||||||
|
|
||||||
var bindings = RoomRolesAndPermissionsScreenViewStateBindings()
|
var bindings = RoomRolesAndPermissionsScreenViewStateBindings()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +50,7 @@ enum RoomRolesAndPermissionsScreenViewAction {
|
|||||||
case reset
|
case reset
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RoomRolesAndPermissionsScreenRole {
|
enum RoomRolesAndPermissionsScreenRole: Hashable {
|
||||||
case administrators
|
case administrators
|
||||||
case moderators
|
case moderators
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-4
@@ -14,6 +14,7 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM
|
|||||||
private let roomProxy: JoinedRoomProxyProtocol
|
private let roomProxy: JoinedRoomProxyProtocol
|
||||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||||
private let analytics: AnalyticsService
|
private let analytics: AnalyticsService
|
||||||
|
private var ownUser: RoomMemberDetails?
|
||||||
|
|
||||||
private var actionsSubject: PassthroughSubject<RoomRolesAndPermissionsScreenViewModelAction, Never> = .init()
|
private var actionsSubject: PassthroughSubject<RoomRolesAndPermissionsScreenViewModelAction, Never> = .init()
|
||||||
var actionsPublisher: AnyPublisher<RoomRolesAndPermissionsScreenViewModelAction, Never> {
|
var actionsPublisher: AnyPublisher<RoomRolesAndPermissionsScreenViewModelAction, Never> {
|
||||||
@@ -24,7 +25,8 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM
|
|||||||
self.roomProxy = roomProxy
|
self.roomProxy = roomProxy
|
||||||
self.userIndicatorController = userIndicatorController
|
self.userIndicatorController = userIndicatorController
|
||||||
self.analytics = analytics
|
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.
|
// Automatically update the admin/moderator counts.
|
||||||
roomProxy.membersPublisher
|
roomProxy.membersPublisher
|
||||||
@@ -84,12 +86,15 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM
|
|||||||
// MARK: - Members
|
// MARK: - Members
|
||||||
|
|
||||||
private func updateMembers(_ members: [RoomMemberProxyProtocol]) {
|
private func updateMembers(_ members: [RoomMemberProxyProtocol]) {
|
||||||
// TODO: Will probably be changed when we implement the owners list
|
state.administratorsAndOwnersCount = members.filter { $0.role.isAdminOrHigher && $0.isActive }.count
|
||||||
state.administratorCount = 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
|
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()
|
showSavingIndicator()
|
||||||
|
|
||||||
// A task we can await until the room's info gets modified with the new power levels.
|
// A task we can await until the room's info gets modified with the new power levels.
|
||||||
|
|||||||
+43
-12
@@ -26,13 +26,23 @@ struct RoomRolesAndPermissionsScreen: View {
|
|||||||
|
|
||||||
private var rolesSection: some View {
|
private var rolesSection: some View {
|
||||||
Section {
|
Section {
|
||||||
ListRow(label: .default(title: L10n.screenRoomRolesAndPermissionsAdmins,
|
if context.viewState.ownRole == .creator {
|
||||||
icon: \.admin),
|
ListRow(label: .default(title: L10n.screenRoomRolesAndPermissionsAdminsAndOwners,
|
||||||
details: administratorDetails,
|
icon: \.admin),
|
||||||
kind: .navigationLink {
|
details: administratorOrOwnersDetails,
|
||||||
context.send(viewAction: .editRoles(.administrators))
|
kind: .navigationLink {
|
||||||
})
|
context.send(viewAction: .editRoles(.administrators))
|
||||||
.accessibilityIdentifier(A11yIdentifiers.roomRolesAndPermissionsScreen.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,
|
ListRow(label: .default(title: L10n.screenRoomRolesAndPermissionsModerators,
|
||||||
icon: \.chatProblem),
|
icon: \.chatProblem),
|
||||||
@@ -42,17 +52,27 @@ struct RoomRolesAndPermissionsScreen: View {
|
|||||||
})
|
})
|
||||||
.accessibilityIdentifier(A11yIdentifiers.roomRolesAndPermissionsScreen.moderators)
|
.accessibilityIdentifier(A11yIdentifiers.roomRolesAndPermissionsScreen.moderators)
|
||||||
|
|
||||||
ListRow(label: .default(title: L10n.screenRoomRolesAndPermissionsChangeMyRole,
|
if context.viewState.ownRole != .creator {
|
||||||
icon: \.edit),
|
ListRow(label: .default(title: L10n.screenRoomRolesAndPermissionsChangeMyRole,
|
||||||
kind: .button {
|
icon: \.edit),
|
||||||
context.send(viewAction: .editOwnUserRole)
|
kind: .button {
|
||||||
})
|
context.send(viewAction: .editOwnUserRole)
|
||||||
|
})
|
||||||
|
}
|
||||||
} header: {
|
} header: {
|
||||||
Text(L10n.screenRoomRolesAndPermissionsRolesHeader)
|
Text(L10n.screenRoomRolesAndPermissionsRolesHeader)
|
||||||
.compoundListSectionHeader()
|
.compoundListSectionHeader()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var administratorOrOwnersDetails: ListRowDetails<Image> {
|
||||||
|
if let administratorCount = context.viewState.administratorsAndOwnersCount {
|
||||||
|
.title("\(administratorCount)")
|
||||||
|
} else {
|
||||||
|
.isWaiting(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var administratorDetails: ListRowDetails<Image> {
|
private var administratorDetails: ListRowDetails<Image> {
|
||||||
if let administratorCount = context.viewState.administratorCount {
|
if let administratorCount = context.viewState.administratorCount {
|
||||||
.title("\(administratorCount)")
|
.title("\(administratorCount)")
|
||||||
@@ -121,9 +141,20 @@ struct RoomRolesAndPermissionsScreen_Previews: PreviewProvider, TestablePreview
|
|||||||
roomProxy: JoinedRoomProxyMock(.init(members: .allMembersAsAdmin)),
|
roomProxy: JoinedRoomProxyMock(.init(members: .allMembersAsAdmin)),
|
||||||
userIndicatorController: UserIndicatorControllerMock(),
|
userIndicatorController: UserIndicatorControllerMock(),
|
||||||
analytics: ServiceLocator.shared.analytics)
|
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 {
|
static var previews: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
RoomRolesAndPermissionsScreen(context: viewModel.context)
|
RoomRolesAndPermissionsScreen(context: viewModel.context)
|
||||||
}
|
}
|
||||||
|
.previewDisplayName("Admin")
|
||||||
|
|
||||||
|
NavigationStack {
|
||||||
|
RoomRolesAndPermissionsScreen(context: creatorViewModel.context)
|
||||||
|
}
|
||||||
|
.previewDisplayName("Creator")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ extension AnalyticsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Track a room moderation action.
|
/// 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)
|
let role = role.map(AnalyticsEvent.RoomModeration.Role.init)
|
||||||
capture(event: AnalyticsEvent.RoomModeration(action: action, role: role))
|
capture(event: AnalyticsEvent.RoomModeration(action: action, role: role))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import AnalyticsEvents
|
import AnalyticsEvents
|
||||||
|
|
||||||
extension AnalyticsEvent.RoomModeration.Role {
|
extension AnalyticsEvent.RoomModeration.Role {
|
||||||
init(role: RoomMemberDetails.Role) {
|
init(role: RoomRole) {
|
||||||
switch role {
|
switch role {
|
||||||
case .administrator, .creator, .owner:
|
case .administrator, .creator, .owner:
|
||||||
// This probably needs to be updates
|
// This probably needs to be updates
|
||||||
|
|||||||
@@ -19,20 +19,7 @@ struct RoomMemberDetails: Identifiable, Hashable {
|
|||||||
var isBanned: Bool
|
var isBanned: Bool
|
||||||
var isActive: Bool
|
var isActive: Bool
|
||||||
|
|
||||||
enum Role {
|
let role: RoomRole
|
||||||
/// 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 powerLevel: RoomPowerLevel
|
let powerLevel: RoomPowerLevel
|
||||||
|
|
||||||
func matches(searchQuery: String) -> Bool {
|
func matches(searchQuery: String) -> Bool {
|
||||||
@@ -51,47 +38,7 @@ extension RoomMemberDetails {
|
|||||||
isInvited = proxy.membership == .invite
|
isInvited = proxy.membership == .invite
|
||||||
isIgnored = proxy.isIgnored
|
isIgnored = proxy.isIgnored
|
||||||
isBanned = proxy.membership == .ban
|
isBanned = proxy.membership == .ban
|
||||||
role = .init(proxy.role, powerLevel: proxy.powerLevel)
|
role = proxy.role
|
||||||
powerLevel = proxy.powerLevel
|
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 isIgnored: Bool { member.isIgnored }
|
||||||
|
|
||||||
var powerLevel: RoomPowerLevel { .init(rustPowerLevel: member.powerLevel) }
|
var powerLevel: RoomPowerLevel { .init(rustPowerLevel: member.powerLevel) }
|
||||||
|
|
||||||
var role: RoomMemberRole { member.suggestedRoleForPowerLevel }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ protocol RoomMemberProxyProtocol: AnyObject {
|
|||||||
var isIgnored: Bool { get }
|
var isIgnored: Bool { get }
|
||||||
|
|
||||||
var powerLevel: RoomPowerLevel { get }
|
var powerLevel: RoomPowerLevel { get }
|
||||||
|
|
||||||
var role: RoomMemberRole { get }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension RoomMemberProxyProtocol {
|
extension RoomMemberProxyProtocol {
|
||||||
@@ -58,3 +56,7 @@ extension [RoomMemberProxyProtocol] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension RoomMemberProxyProtocol {
|
||||||
|
var role: RoomRole { .init(powerLevel: powerLevel) }
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,15 +9,15 @@ import Foundation
|
|||||||
import MatrixRustSDK
|
import MatrixRustSDK
|
||||||
|
|
||||||
struct RoomPermissionsSetting: Identifiable {
|
struct RoomPermissionsSetting: Identifiable {
|
||||||
var id: KeyPath<RoomPermissions, RoomMemberDetails.Role> { keyPath }
|
var id: KeyPath<RoomPermissions, RoomRole> { keyPath }
|
||||||
|
|
||||||
/// The title of this setting.
|
/// The title of this setting.
|
||||||
let title: String
|
let title: String
|
||||||
|
|
||||||
/// The selected role of this setting.
|
/// 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.
|
/// 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.screenRoomChangePermissionsAdministrators, tag: .administrator),
|
||||||
(title: L10n.screenRoomChangePermissionsModerators, tag: .moderator),
|
(title: L10n.screenRoomChangePermissionsModerators, tag: .moderator),
|
||||||
@@ -26,7 +26,7 @@ struct RoomPermissionsSetting: Identifiable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The `RoomPermissions` property that this setting is for.
|
/// 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.
|
/// The `RoomPowerLevelChanges` property that this setting is saved into.
|
||||||
var rustKeyPath: WritableKeyPath<RoomPowerLevelChanges, Int64?> {
|
var rustKeyPath: WritableKeyPath<RoomPowerLevelChanges, Int64?> {
|
||||||
switch keyPath {
|
switch keyPath {
|
||||||
@@ -47,99 +47,39 @@ struct RoomPermissionsSetting: Identifiable {
|
|||||||
|
|
||||||
struct RoomPermissions {
|
struct RoomPermissions {
|
||||||
/// The level required to ban a user.
|
/// The level required to ban a user.
|
||||||
var ban: RoomMemberDetails.Role
|
var ban: RoomRole
|
||||||
/// The level required to invite a user.
|
/// The level required to invite a user.
|
||||||
var invite: RoomMemberDetails.Role
|
var invite: RoomRole
|
||||||
/// The level required to kick a user.
|
/// The level required to kick a user.
|
||||||
var kick: RoomMemberDetails.Role
|
var kick: RoomRole
|
||||||
/// The level required to redact an event.
|
/// The level required to redact an event.
|
||||||
var redact: RoomMemberDetails.Role
|
var redact: RoomRole
|
||||||
/// The default level required to send message events.
|
/// The default level required to send message events.
|
||||||
var eventsDefault: RoomMemberDetails.Role
|
var eventsDefault: RoomRole
|
||||||
/// The default level required to send state events.
|
/// 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.
|
/// 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.
|
/// 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.
|
/// 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.
|
/// The level required to change the room's topic.
|
||||||
var roomTopic: RoomMemberDetails.Role
|
var roomTopic: RoomRole
|
||||||
}
|
}
|
||||||
|
|
||||||
extension RoomPermissions {
|
extension RoomPermissions {
|
||||||
/// Create permissions from the room's power levels.
|
/// Create permissions from the room's power levels.
|
||||||
init(powerLevels: RoomPowerLevelsValues) {
|
init(powerLevels: RoomPowerLevelsValues) {
|
||||||
ban = RoomMemberDetails.Role(powerLevelValue: powerLevels.ban)
|
ban = RoomRole(powerLevelValue: powerLevels.ban)
|
||||||
invite = RoomMemberDetails.Role(powerLevelValue: powerLevels.invite)
|
invite = RoomRole(powerLevelValue: powerLevels.invite)
|
||||||
kick = RoomMemberDetails.Role(powerLevelValue: powerLevels.kick)
|
kick = RoomRole(powerLevelValue: powerLevels.kick)
|
||||||
redact = RoomMemberDetails.Role(powerLevelValue: powerLevels.redact)
|
redact = RoomRole(powerLevelValue: powerLevels.redact)
|
||||||
eventsDefault = RoomMemberDetails.Role(powerLevelValue: powerLevels.eventsDefault)
|
eventsDefault = RoomRole(powerLevelValue: powerLevels.eventsDefault)
|
||||||
stateDefault = RoomMemberDetails.Role(powerLevelValue: powerLevels.stateDefault)
|
stateDefault = RoomRole(powerLevelValue: powerLevels.stateDefault)
|
||||||
usersDefault = RoomMemberDetails.Role(powerLevelValue: powerLevels.usersDefault)
|
usersDefault = RoomRole(powerLevelValue: powerLevels.usersDefault)
|
||||||
roomName = RoomMemberDetails.Role(powerLevelValue: powerLevels.roomName)
|
roomName = RoomRole(powerLevelValue: powerLevels.roomName)
|
||||||
roomAvatar = RoomMemberDetails.Role(powerLevelValue: powerLevels.roomAvatar)
|
roomAvatar = RoomRole(powerLevelValue: powerLevels.roomAvatar)
|
||||||
roomTopic = RoomMemberDetails.Role(powerLevelValue: powerLevels.roomTopic)
|
roomTopic = RoomRole(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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:45bfc56242e51815e312057b7603d19c3f2ae488f81145f6e8c45bdd405bafd4
|
oid sha256:356954ba65b0fe34441df8440182ab743df30aa9f6c6932bb9b5107ae87cb839
|
||||||
size 219778
|
size 221488
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:ef779c260c378cb37d32e19aa9fb58c73818aca7153cdb762078487e6245b8cb
|
oid sha256:7ce7e57ce7b90e91395dd3e0c9b3a73d944ac43a8f36feb77fb106e9f86add67
|
||||||
size 226361
|
size 231973
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:08b1ed25327b18ce7be7831bf62644d25269b7204d79fb1058466893923f3559
|
oid sha256:28dd538e435dc4125d34c8cbf45c5403a4dc1b4759d3d16cb0e56f4b1bb5a63f
|
||||||
size 172593
|
size 167867
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:22561fa01da4cff09515ed5bf9c70beb55b2c691bf86c911aedc0090bef1396c
|
oid sha256:d56fb49526da5e418e3311a8ab121efcde6cc5dd2234c3662997884e0f2e6991
|
||||||
size 178152
|
size 176485
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:06194fbeb1d19db9882525340b7a73d6cbae649c9a13f1b527e1d26306946c40
|
oid sha256:5d7ee9d49212820a88171df680a31ad658ac62ae7fd3474a24403c130a4630d8
|
||||||
size 184357
|
size 231534
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:1a14dd1411077dd6f20e46c67a8856169f04bc186bf7015ebe5fa37cdceb8981
|
oid sha256:312f9f0d95ae3dd31c2c8ab4ce37a9de51febdd79a9d65b60205241e5a18f1cb
|
||||||
size 195057
|
size 247092
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:6382218fc0c357a9b4cabcbfb737e4678d2f52f89949a9ddab5301f3c70023fc
|
oid sha256:ddc42a0c0de638440189c23ec7ef85c9b84ea2fdbc8e222994adbc5f4ab88d5a
|
||||||
size 144983
|
size 172583
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:1a2014f024ade0cb7901089e7637f7136474d057eb8dbb84577deecc7d3d16bb
|
oid sha256:47fa5b57b70a18e75a9b690cc96e5bb3c7db26ccd06eae05f3d3e6f8bbd92647
|
||||||
size 154055
|
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
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:673f640cc1b7368d7c4bb70d5b5ac325941723cbf740507a0611606eee50d373
|
oid sha256:60efb65986ae484b7f7da9648db252ab4aa11e0047020574244e070972813f0a
|
||||||
size 62443
|
size 62712
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:673f640cc1b7368d7c4bb70d5b5ac325941723cbf740507a0611606eee50d373
|
oid sha256:60efb65986ae484b7f7da9648db252ab4aa11e0047020574244e070972813f0a
|
||||||
size 62443
|
size 62712
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:3d07e63d1d9dd2a676e05c95c3562e7e2ef397d9cb2ac07f8c5ca0c3eb510024
|
oid sha256:e9f33cdbe4d65566be99949b00a54439b90d4d010ec9fb9af505fbc043271177
|
||||||
size 52234
|
size 52462
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:3d07e63d1d9dd2a676e05c95c3562e7e2ef397d9cb2ac07f8c5ca0c3eb510024
|
oid sha256:e9f33cdbe4d65566be99949b00a54439b90d4d010ec9fb9af505fbc043271177
|
||||||
size 52234
|
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
|
// set app language to English
|
||||||
Bundle.overrideLocalizations = ["en"]
|
Bundle.overrideLocalizations = ["en"]
|
||||||
|
|
||||||
XCTAssertEqual(L10n.commonMemberCount(1), "1 member")
|
XCTAssertEqual(L10n.commonMemberCount(1), "1 Member")
|
||||||
XCTAssertEqual(L10n.commonMemberCount(2), "2 members")
|
XCTAssertEqual(L10n.commonMemberCount(2), "2 Members")
|
||||||
|
|
||||||
// set app language to Italian
|
// set app language to Italian
|
||||||
Bundle.overrideLocalizations = ["it"]
|
Bundle.overrideLocalizations = ["it"]
|
||||||
|
|
||||||
XCTAssertEqual(L10n.commonMemberCount(1), "1 membro")
|
XCTAssertEqual(L10n.commonMemberCount(1), "1 Membro")
|
||||||
XCTAssertEqual(L10n.commonMemberCount(2), "2 membri")
|
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test plurals fallback language for a language not supported at all
|
/// 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")
|
// set app language to something Element don't support at all ("invalid identifier")
|
||||||
Bundle.overrideLocalizations = ["xx"]
|
Bundle.overrideLocalizations = ["xx"]
|
||||||
|
|
||||||
XCTAssertEqual(L10n.commonMemberCount(1), "1 member")
|
XCTAssertEqual(L10n.commonMemberCount(1), "1 Member")
|
||||||
XCTAssertEqual(L10n.commonMemberCount(2), "2 members")
|
XCTAssertEqual(L10n.commonMemberCount(2), "2 Members")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test untranslated strings
|
/// Test untranslated strings
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
|
|||||||
XCTAssertEqual(context.viewState.administrators, context.viewState.visibleAdministrators)
|
XCTAssertEqual(context.viewState.administrators, context.viewState.visibleAdministrators)
|
||||||
XCTAssertEqual(context.viewState.moderators, context.viewState.visibleModerators)
|
XCTAssertEqual(context.viewState.moderators, context.viewState.visibleModerators)
|
||||||
XCTAssertEqual(context.viewState.users, context.viewState.visibleUsers)
|
XCTAssertEqual(context.viewState.users, context.viewState.visibleUsers)
|
||||||
XCTAssertEqual(context.viewState.membersWithRole.count, 1)
|
XCTAssertEqual(context.viewState.membersWithRole.count, 3)
|
||||||
XCTAssertEqual(context.viewState.membersWithRole.first?.id, RoomMemberProxyMock.mockModerator.userID)
|
XCTAssert(context.viewState.membersWithRole.first(where: { $0.id == RoomMemberProxyMock.mockModerator.userID }) != nil)
|
||||||
XCTAssertFalse(context.viewState.hasChanges)
|
XCTAssertFalse(context.viewState.hasChanges)
|
||||||
XCTAssertFalse(context.viewState.isSearching)
|
XCTAssertFalse(context.viewState.isSearching)
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertEqual(context.viewState.membersToPromote, [firstUser])
|
XCTAssertEqual(context.viewState.membersToPromote, [firstUser])
|
||||||
XCTAssertEqual(context.viewState.membersToDemote, [])
|
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.membersWithRole.contains(firstUser))
|
||||||
XCTAssertTrue(context.viewState.hasChanges)
|
XCTAssertTrue(context.viewState.hasChanges)
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertEqual(context.viewState.membersToPromote, [])
|
XCTAssertEqual(context.viewState.membersToPromote, [])
|
||||||
XCTAssertEqual(context.viewState.membersToDemote, [])
|
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.membersWithRole.contains(firstUser))
|
||||||
XCTAssertFalse(context.viewState.hasChanges)
|
XCTAssertFalse(context.viewState.hasChanges)
|
||||||
}
|
}
|
||||||
@@ -87,14 +87,14 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertEqual(context.viewState.membersToPromote, [])
|
XCTAssertEqual(context.viewState.membersToPromote, [])
|
||||||
XCTAssertEqual(context.viewState.membersToDemote, [])
|
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.membersWithRole.contains(firstUser))
|
||||||
XCTAssertFalse(context.viewState.hasChanges)
|
XCTAssertFalse(context.viewState.hasChanges)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testToggleModeratorOff() {
|
func testToggleModeratorOff() {
|
||||||
testInitialStateModerators()
|
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.")
|
XCTFail("There should be a member with the role before we begin.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,7 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertEqual(context.viewState.membersToPromote, [])
|
XCTAssertEqual(context.viewState.membersToPromote, [])
|
||||||
XCTAssertEqual(context.viewState.membersToDemote, [existingModerator])
|
XCTAssertEqual(context.viewState.membersToDemote, [existingModerator])
|
||||||
XCTAssertEqual(context.viewState.membersWithRole.count, 0)
|
XCTAssertEqual(context.viewState.membersWithRole.count, 2)
|
||||||
XCTAssertFalse(context.viewState.membersWithRole.contains(existingModerator))
|
XCTAssertFalse(context.viewState.membersWithRole.contains(existingModerator))
|
||||||
XCTAssertTrue(context.viewState.hasChanges)
|
XCTAssertTrue(context.viewState.hasChanges)
|
||||||
}
|
}
|
||||||
@@ -120,14 +120,14 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertEqual(context.viewState.membersToPromote, [])
|
XCTAssertEqual(context.viewState.membersToPromote, [])
|
||||||
XCTAssertEqual(context.viewState.membersToDemote, [])
|
XCTAssertEqual(context.viewState.membersToDemote, [])
|
||||||
XCTAssertEqual(context.viewState.membersWithRole.count, 1)
|
XCTAssertEqual(context.viewState.membersWithRole.count, 3)
|
||||||
XCTAssertTrue(context.viewState.membersWithRole.contains(demotedMember))
|
XCTAssertTrue(context.viewState.membersWithRole.contains(demotedMember))
|
||||||
XCTAssertFalse(context.viewState.hasChanges)
|
XCTAssertFalse(context.viewState.hasChanges)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDemoteModerator() {
|
func testDemoteModerator() {
|
||||||
testInitialStateModerators()
|
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.")
|
XCTFail("There should be a member with the role before we begin.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertEqual(context.viewState.membersToPromote, [])
|
XCTAssertEqual(context.viewState.membersToPromote, [])
|
||||||
XCTAssertEqual(context.viewState.membersToDemote, [existingModerator])
|
XCTAssertEqual(context.viewState.membersToDemote, [existingModerator])
|
||||||
XCTAssertEqual(context.viewState.membersWithRole.count, 0)
|
XCTAssertEqual(context.viewState.membersWithRole.count, 2)
|
||||||
XCTAssertFalse(context.viewState.membersWithRole.contains(existingModerator))
|
XCTAssertFalse(context.viewState.membersWithRole.contains(existingModerator))
|
||||||
XCTAssertTrue(context.viewState.hasChanges)
|
XCTAssertTrue(context.viewState.hasChanges)
|
||||||
}
|
}
|
||||||
@@ -146,7 +146,7 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
|
|||||||
setupViewModel(mode: .moderator)
|
setupViewModel(mode: .moderator)
|
||||||
|
|
||||||
guard let firstUser = context.viewState.users.first(where: { !context.viewState.isMemberSelected($0) }),
|
guard let firstUser = context.viewState.users.first(where: { !context.viewState.isMemberSelected($0) }),
|
||||||
let existingModerator = context.viewState.membersWithRole.first else {
|
let existingModerator = context.viewState.membersWithRole.first(where: { $0.role == .moderator }) else {
|
||||||
XCTFail("There should be a regular user and a moderator to begin with.")
|
XCTFail("There should be a regular user and a moderator to begin with.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -192,7 +192,7 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
|
|||||||
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.contains { $0.userID == firstUser.id && $0.powerLevel == 100 }, true)
|
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))
|
roomProxy = JoinedRoomProxyMock(.init(members: .allMembersAsAdmin))
|
||||||
viewModel = RoomChangeRolesScreenViewModel(mode: mode,
|
viewModel = RoomChangeRolesScreenViewModel(mode: mode,
|
||||||
roomProxy: roomProxy,
|
roomProxy: roomProxy,
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class RoomRolesAndPermissionsScreenViewModelTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertTrue(roomProxy.updatePowerLevelsForUsersCalled)
|
XCTAssertTrue(roomProxy.updatePowerLevelsForUsersCalled)
|
||||||
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.first?.powerLevel,
|
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.first?.powerLevel,
|
||||||
RoomMemberDetails.Role.moderator.powerLevelValue)
|
RoomRole.moderator.powerLevelValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDemoteToMember() async throws {
|
func testDemoteToMember() async throws {
|
||||||
@@ -70,7 +70,7 @@ class RoomRolesAndPermissionsScreenViewModelTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertTrue(roomProxy.updatePowerLevelsForUsersCalled)
|
XCTAssertTrue(roomProxy.updatePowerLevelsForUsersCalled)
|
||||||
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.first?.powerLevel,
|
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.first?.powerLevel,
|
||||||
RoomMemberDetails.Role.user.powerLevelValue)
|
RoomRole.user.powerLevelValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupViewModel(members: [RoomMemberProxyMock]) {
|
private func setupViewModel(members: [RoomMemberProxyMock]) {
|
||||||
|
|||||||
Reference in New Issue
Block a user