Space Settings: Leave Room (#4700)

* Implementation for all navigations inside the space settings aside the left space action

# Conflicts:
#	ElementX/Sources/FlowCoordinators/SpaceSettingsFlowCoordinator.swift

* refactored the leave space view to use its own view model

# Conflicts:
#	ElementX.xcodeproj/project.pbxproj

* implemented the leave space view model also in the settings screen, and corrected some tests

* reusing the details coordinator for the space settings screen

* leave space from settings implemented

* fix project

* minor pr fixes

* code improvements
This commit is contained in:
Mauro
2025-11-07 12:11:21 +01:00
committed by GitHub
parent 8974a4584a
commit 096e2d1713
22 changed files with 407 additions and 277 deletions
+70 -51
View File
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objectVersion = 63;
objects = {
/* Begin PBXAggregateTarget section */
@@ -583,6 +583,7 @@
68B2DD307C57ECFABBB05323 /* DeclineAndBlockScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 127D1947BA9C6CA62E3D03EC /* DeclineAndBlockScreen.swift */; };
68C3AF257678F6E7BB238C3F /* AppAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FD22AEFFA20065494ED2333 /* AppAppearance.swift */; };
695825D20A761C678809345D /* MessageForwardingScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52135BD9E0E7A091688F627A /* MessageForwardingScreenModels.swift */; };
695BE6A2337A634F48B5DBC8 /* RoomMembersFlowCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC666DAE98245269775329B2 /* RoomMembersFlowCoordinatorTests.swift */; };
69A9B430397C15075D86193F /* UserPropertiesExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66AFD800AF033D8B0D11191A /* UserPropertiesExt.swift */; };
69B3C6010B42010F591FC3CB /* RoomRolesAndPermissionsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C1AF829F12FDC99717082D9 /* RoomRolesAndPermissionsScreenViewModel.swift */; };
69C7B956B74BEC3DB88224EA /* NavigationSplitCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78913D6E120D46138E97C107 /* NavigationSplitCoordinatorTests.swift */; };
@@ -944,10 +945,10 @@
A6F345328CCC5C9B0DAE2257 /* LogViewerScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BB05221D7D941CC82DC8480 /* LogViewerScreenViewModel.swift */; };
A6FFC4C5154C446BAD6B40D8 /* TimelineItemProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8520AFD6680CBAD388F6D927 /* TimelineItemProvider.swift */; };
A722F426FD81FC67706BB1E0 /* CustomLayoutLabelStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42236480CF0431535EBE8387 /* CustomLayoutLabelStyle.swift */; };
A7385BAC2EBDFF80000B0961 /* LeaveSpaceModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7385BAB2EBDFF7B000B0961 /* LeaveSpaceModels.swift */; };
A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */; };
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; };
A7DB75E090542331F6668A23 /* CreateRoomScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF19027E7FFA5E63D148873A /* CreateRoomScreenViewModel.swift */; };
A7F6CE532EBB76A500450B70 /* RoomMembersFlowCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F6CE522EBB769D00450B70 /* RoomMembersFlowCoordinatorTests.swift */; };
A808DC3F72D15C6C5A52317E /* TimelineItemDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCDA016D05107DED3B9495CB /* TimelineItemDebugView.swift */; };
A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6E30BB748F3F480F077969 /* RoomMemberDetailsScreenModels.swift */; };
A851635B3255C6DC07034A12 /* RoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */; };
@@ -1337,6 +1338,7 @@
F255083E18CDBFDF7E640FB1 /* Avatars.swift in Sources */ = {isa = PBXBuildFile; fileRef = C142248014E08E885E323E56 /* Avatars.swift */; };
F2D5C0E1351DA7BD16867629 /* TimelineStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CD4823EAB4B4E8BAB4F6B8C /* TimelineStyle.swift */; };
F2E580C0FBFBEFFE9D69893B /* RoomPreviewProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 739077686814E4EA339B1C83 /* RoomPreviewProxyProtocol.swift */; };
F3382570701BB87DA816AC82 /* LeaveSpaceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8C59748CC34606FF4FE95F0 /* LeaveSpaceViewModel.swift */; };
F34D06F86C29E219E7132E87 /* AuthenticationStartScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F71A54CB96DAA1E72C6541D /* AuthenticationStartScreenViewModel.swift */; };
F35FAD1B1B289E221A07D719 /* ManageRoomMemberSheetViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB7B588A06911B455AC0B4C9 /* ManageRoomMemberSheetViewModelProtocol.swift */; };
F37629BAA5E8F50AAF2A131D /* SoftLogoutScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB7BAD55A4E2B8E5828CD64C /* SoftLogoutScreenViewModel.swift */; };
@@ -1399,7 +1401,6 @@
FCD3F2B82CAB29A07887A127 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = DE8DC9B3FBA402117DC4C49F /* Kingfisher */; };
FCF95603F1D056B1B106A415 /* AdvancedSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83E2B20431F890ED64255CA1 /* AdvancedSettingsScreenViewModelProtocol.swift */; };
FD29471C72872F8B7580E3E1 /* KeychainControllerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39C0D861FC397AC34BCF089E /* KeychainControllerMock.swift */; };
FD3C94F01ACAF2D4948CF9BE /* SpaceSettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA9DA018160CF76AFFBFBA7 /* SpaceSettingsScreenCoordinator.swift */; };
FD439E183A48BE871AEEFAEA /* TimelineScrollToBottomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10765FBC83B34A3BC4ADB23 /* TimelineScrollToBottomButton.swift */; };
FD4C21F8DA1E273DE94FCD1A /* NotificationItemProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B927CF5EF7FCCDA5EDC474B /* NotificationItemProxyProtocol.swift */; };
FD573B5D665824EB79EABF06 /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5327E3B3C58BEB0E65F4CF98 /* Observable.swift */; };
@@ -1523,7 +1524,7 @@
044E501B8331B339874D1B96 /* CompoundIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompoundIcon.swift; sourceTree = "<group>"; };
045253F9967A535EE5B16691 /* Label.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Label.swift; sourceTree = "<group>"; };
046C0D3F53B0B5EF0A1F5BEA /* RoomSummaryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryTests.swift; sourceTree = "<group>"; };
048A21188AB19349D026BECD /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
048A21188AB19349D026BECD /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
04BB8DDE245ED86C489BA983 /* AccessibilityIdentifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityIdentifiers.swift; sourceTree = "<group>"; };
04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManager.swift; sourceTree = "<group>"; };
0516C69708D5CBDE1A8E77EC /* RoomDirectorySearchProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchProxyProtocol.swift; sourceTree = "<group>"; };
@@ -1603,7 +1604,7 @@
128501375217576AF0FE3E92 /* RoomAttachmentPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomAttachmentPicker.swift; sourceTree = "<group>"; };
12B09A94C519227264A41208 /* RoomMembershipDetailsProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembershipDetailsProxy.swift; sourceTree = "<group>"; };
12FD5280AF55AB7F50F8E47D /* preview_avatar_room.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = preview_avatar_room.jpg; sourceTree = "<group>"; };
1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; path = IntegrationTests.xctestplan; sourceTree = "<group>"; };
1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = IntegrationTests.xctestplan; sourceTree = "<group>"; };
130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNTextInputNotificationResponse+Creator.swift"; sourceTree = "<group>"; };
136F80A613B55BDD071DCEA5 /* JoinRoomScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRoomScreenModels.swift; sourceTree = "<group>"; };
13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
@@ -1623,7 +1624,7 @@
16D09C79746BDCD9173EB3A7 /* RoomDetailsEditScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenModels.swift; sourceTree = "<group>"; };
16D353E10A64172D863769BF /* TombstonedAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TombstonedAvatarImage.swift; sourceTree = "<group>"; };
1715E3D7F53C0748AA50C91C /* PostHogAnalyticsClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogAnalyticsClient.swift; sourceTree = "<group>"; };
174E4AEF3DED300AA81046EC /* compound-ios */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "compound-ios"; path = "compound-ios"; sourceTree = SOURCE_ROOT; };
174E4AEF3DED300AA81046EC /* compound-ios */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "compound-ios"; sourceTree = SOURCE_ROOT; };
17A8AA0DFA06012A9DAB951E /* TimelineProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineProxyMock.swift; sourceTree = "<group>"; };
17BAE25A0E9E9F2F1BBA8930 /* DeactivateAccountScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeactivateAccountScreenViewModel.swift; sourceTree = "<group>"; };
181CF280BC8E3F335AFCB4B8 /* RemotePreferenceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemotePreferenceTests.swift; sourceTree = "<group>"; };
@@ -1713,7 +1714,7 @@
25F7FE40EF7490A7E09D7BE6 /* NotificationItemProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationItemProxy.swift; sourceTree = "<group>"; };
25F8664F1FB95AF3C4202478 /* PollFormScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollFormScreenCoordinator.swift; sourceTree = "<group>"; };
260004737C573A56FA01E86E /* Encodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encodable.swift; sourceTree = "<group>"; };
267BB1D5B08A9511F894CB57 /* PreviewTests.xctestplan */ = {isa = PBXFileReference; path = PreviewTests.xctestplan; sourceTree = "<group>"; };
267BB1D5B08A9511F894CB57 /* PreviewTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = PreviewTests.xctestplan; sourceTree = "<group>"; };
26B0A96B8FE4849227945067 /* VoiceMessageRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecorder.swift; sourceTree = "<group>"; };
26EAAB54C6CE91D64B69A9F8 /* AppLockServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockServiceProtocol.swift; sourceTree = "<group>"; };
2711E5996016ABD6EAAEB58A /* LogLevel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogLevel.swift; sourceTree = "<group>"; };
@@ -1796,7 +1797,7 @@
355C8C46DA9C0B45F1B7FC4F /* SpaceRoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceRoomProxy.swift; sourceTree = "<group>"; };
35A057BA9BE0F079784CD061 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
35AFCF4C05DEED04E3DB1A16 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
36DA824791172B9821EACBED /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
36DA824791172B9821EACBED /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
36FD673E24FBFCFDF398716A /* RoomMemberProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberProxyMock.swift; sourceTree = "<group>"; };
3747C96188856006F784BF49 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ko; path = ko.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
37A63A59BFDDC494B1C20119 /* CallScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallScreenViewModel.swift; sourceTree = "<group>"; };
@@ -1909,7 +1910,7 @@
4A2B5274C1D3D2999D643786 /* EncryptionResetPasswordScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreenViewModelProtocol.swift; sourceTree = "<group>"; };
4A5B4CD611DE7E94F5BA87B2 /* AppLockTimerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockTimerTests.swift; sourceTree = "<group>"; };
4AB29A2D95D3469B5F016655 /* SecureBackupControllerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupControllerMock.swift; sourceTree = "<group>"; };
4B1F71AC585827E6C416C15A /* AppIcon.icon */ = {isa = PBXFileReference; path = AppIcon.icon; sourceTree = "<group>"; };
4B1F71AC585827E6C416C15A /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon.icon; sourceTree = "<group>"; };
4B2B564CA6570E1487A7C7CC /* SpaceRoomListProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceRoomListProxy.swift; sourceTree = "<group>"; };
4B2D4EEBE8C098BBADD10939 /* SecureBackupKeyBackupScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupKeyBackupScreenCoordinator.swift; sourceTree = "<group>"; };
4B41FABA2B0AEF4389986495 /* LoginMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginMode.swift; sourceTree = "<group>"; };
@@ -1923,7 +1924,7 @@
4E2245243369B99216C7D84E /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
4E600B315B920B9687F8EE1B /* ComposerDraftServiceMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerDraftServiceMock.swift; sourceTree = "<group>"; };
4E625B0EB2F86B37C14EF7E6 /* SettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenViewModel.swift; sourceTree = "<group>"; };
4E6690212271866C899AD2BA /* new-message.caf */ = {isa = PBXFileReference; path = "new-message.caf"; sourceTree = "<group>"; };
4E6690212271866C899AD2BA /* new-message.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "new-message.caf"; sourceTree = "<group>"; };
4E7F7A975514E850A834B29F /* PaginationIndicatorRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationIndicatorRoomTimelineView.swift; sourceTree = "<group>"; };
4F5F0662483ED69791D63B16 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = et; path = et.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
4F75EF13F49DD2204E760910 /* FileRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineView.swift; sourceTree = "<group>"; };
@@ -2240,7 +2241,7 @@
8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStateEventStringBuilder.swift; sourceTree = "<group>"; };
8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; };
8DA1E8F287680C8ED25EDBAC /* NetworkMonitorMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitorMock.swift; sourceTree = "<group>"; };
8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = "<group>"; };
8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = "<group>"; };
8E1584F8BCF407BB94F48F04 /* EncryptionResetPasswordScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreen.swift; sourceTree = "<group>"; };
8EAF4A49F3ACD8BB8B0D2371 /* ClientSDKMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientSDKMock.swift; sourceTree = "<group>"; };
8F062DD2CCD95DC33528A16F /* KnockRequestProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KnockRequestProxy.swift; sourceTree = "<group>"; };
@@ -2362,6 +2363,7 @@
A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = "<group>"; };
A6C11AD9813045E44F950410 /* ElementCallWidgetDriverProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallWidgetDriverProtocol.swift; sourceTree = "<group>"; };
A6EA0D8B0BBD8805F7D5A133 /* TextBasedRoomTimelineViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineViewProtocol.swift; sourceTree = "<group>"; };
A7385BAB2EBDFF7B000B0961 /* LeaveSpaceModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaveSpaceModels.swift; sourceTree = "<group>"; };
A73A07BAEDD74C48795A996A /* AsyncSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSequence.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>"; };
@@ -2369,7 +2371,6 @@
A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = "<group>"; };
A7D452AF7B5F7E3A0A7DB54C /* SessionVerificationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenViewModelProtocol.swift; sourceTree = "<group>"; };
A7E37072597F67C4DD8CC2DB /* ComposerDraftServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerDraftServiceProtocol.swift; sourceTree = "<group>"; };
A7F6CE522EBB769D00450B70 /* RoomMembersFlowCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersFlowCoordinatorTests.swift; sourceTree = "<group>"; };
A84D413BF49F0E980F010A6B /* LogViewerScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewerScreenCoordinator.swift; sourceTree = "<group>"; };
A8558D41DD4B553A752C868A /* StackedAvatarsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackedAvatarsView.swift; sourceTree = "<group>"; };
A861DA5932B128FE1DCB5CE2 /* InviteUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenCoordinator.swift; sourceTree = "<group>"; };
@@ -2387,7 +2388,7 @@
AAD8234D0E9C9B12BF9F240B /* LocationAnnotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationAnnotation.swift; sourceTree = "<group>"; };
AB07F03461023BC39C730922 /* PhishingDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhishingDetector.swift; sourceTree = "<group>"; };
AB26D5444A4A7E095222DE8B /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
AB389C38BD41EB3E47092CFB /* AccessibilityTests.xctestplan */ = {isa = PBXFileReference; path = AccessibilityTests.xctestplan; sourceTree = "<group>"; };
AB389C38BD41EB3E47092CFB /* AccessibilityTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = AccessibilityTests.xctestplan; sourceTree = "<group>"; };
ABA4CF2F5B4F68D02E412004 /* ServerConfirmationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenViewModelProtocol.swift; sourceTree = "<group>"; };
AC0275CEE9CA078B34028BDF /* AppLockScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockScreenViewModelTests.swift; sourceTree = "<group>"; };
AC1DA29A5A041CC0BACA7CB0 /* MockImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockImageCache.swift; sourceTree = "<group>"; };
@@ -2455,7 +2456,7 @@
B53AC78E49A297AC1D72A7CF /* AppMediator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppMediator.swift; sourceTree = "<group>"; };
B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = "<group>"; };
B5D829FD8958376614504B18 /* TargetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TargetConfiguration.swift; sourceTree = "<group>"; };
B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = "<group>"; };
B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = ConfettiScene.scn; sourceTree = "<group>"; };
B6404166CBF5CC88673FF9E2 /* RoomDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetails.swift; sourceTree = "<group>"; };
B65DDCF8E41759890355ACBC /* AuthenticationStartScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationStartScreenViewModelProtocol.swift; sourceTree = "<group>"; };
B682FE2C44C5E163E7023B05 /* CopyTextButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyTextButton.swift; sourceTree = "<group>"; };
@@ -2478,6 +2479,7 @@
B858A61F2A570DFB8DE570A7 /* AggregratedReaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AggregratedReaction.swift; sourceTree = "<group>"; };
B88CE0A058727BC68EEEC6B6 /* ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareExtension.entitlements; sourceTree = "<group>"; };
B8A3B7637DDBD6AA97AC2545 /* CameraPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPicker.swift; sourceTree = "<group>"; };
B8C59748CC34606FF4FE95F0 /* LeaveSpaceViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaveSpaceViewModel.swift; sourceTree = "<group>"; };
B8F28602AC7AC881AED37EBA /* NavigationCoordinators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationCoordinators.swift; sourceTree = "<group>"; };
B902EA6CD3296B0E10EE432B /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = "<group>"; };
B99E13633862847D8B7E2815 /* StartChatScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreenModels.swift; sourceTree = "<group>"; };
@@ -2485,7 +2487,7 @@
BA40B98B098B6F0371B750B3 /* TemplateScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenModels.swift; sourceTree = "<group>"; };
BA919F521E9F0EE3638AFC85 /* BugReportScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreen.swift; sourceTree = "<group>"; };
BB284643AF7AB131E307DCE0 /* AudioSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioSessionProtocol.swift; sourceTree = "<group>"; };
BB576F4118C35E6B5124FA22 /* test_apple_image.heic */ = {isa = PBXFileReference; path = test_apple_image.heic; sourceTree = "<group>"; };
BB576F4118C35E6B5124FA22 /* test_apple_image.heic */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_apple_image.heic; sourceTree = "<group>"; };
BB5B00A014307CE37B2812CD /* TimelineViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineViewModelProtocol.swift; sourceTree = "<group>"; };
BB6ED50FE104992419310EEB /* NotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHandler.swift; sourceTree = "<group>"; };
BB8BC4C791D0E88CFCF4E5DF /* ServerSelectionScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionScreenCoordinator.swift; sourceTree = "<group>"; };
@@ -2579,6 +2581,7 @@
CC03209FDE8CE0810617BFFF /* RoomMembersListScreenMemberCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenMemberCell.swift; sourceTree = "<group>"; };
CC1DDB2293A51EA4C2739351 /* RoomListFiltersEmptyStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListFiltersEmptyStateView.swift; sourceTree = "<group>"; };
CC437C491EA6996513B1CEAB /* ElementCallConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallConfiguration.swift; sourceTree = "<group>"; };
CC666DAE98245269775329B2 /* RoomMembersFlowCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersFlowCoordinatorTests.swift; sourceTree = "<group>"; };
CC680E0E79D818706CB28CF8 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
CC743C7A85E3171BCBF0A653 /* AvatarHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarHeaderView.swift; sourceTree = "<group>"; };
CCF71646898A2F720C5BFDF5 /* RoomDirectorySearchScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchScreenViewModel.swift; sourceTree = "<group>"; };
@@ -2589,7 +2592,7 @@
CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = "<group>"; };
CDE3F3911FF7CC639BDE5844 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = "<group>"; };
CEE20623EB4A9B88FB29F2BA /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/SAS.strings; sourceTree = "<group>"; };
CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = "<group>"; };
CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = "<group>"; };
CF19027E7FFA5E63D148873A /* CreateRoomScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomScreenViewModel.swift; sourceTree = "<group>"; };
CF847A34FC4C8C937CD39E08 /* LabsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
CFFA5E881D281810AB428EA3 /* RoomPowerLevelsProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPowerLevelsProxy.swift; sourceTree = "<group>"; };
@@ -2658,7 +2661,7 @@
DC0AEA686E425F86F6BA0404 /* UNNotification+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNNotification+Creator.swift"; sourceTree = "<group>"; };
DC10CCC8D68B863E20660DBC /* MessageForwardingScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenViewModelProtocol.swift; sourceTree = "<group>"; };
DC528B3764E3CF7FCFEF40E7 /* PollInteractionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollInteractionHandler.swift; sourceTree = "<group>"; };
DCA2D836BD10303F37FAAEED /* test_voice_message.m4a */ = {isa = PBXFileReference; path = test_voice_message.m4a; sourceTree = "<group>"; };
DCA2D836BD10303F37FAAEED /* test_voice_message.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_voice_message.m4a; sourceTree = "<group>"; };
DCAC01A97A43BE07B9E94E43 /* ShareExtensionModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareExtensionModels.swift; sourceTree = "<group>"; };
DCDAB580109C09A6AA97AF7E /* PollFormScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollFormScreenTests.swift; sourceTree = "<group>"; };
DCF239C619971FDE48132550 /* SecureBackupLogoutConfirmationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenModels.swift; sourceTree = "<group>"; };
@@ -2705,7 +2708,7 @@
E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = "<group>"; };
E53BFB7E4F329621C844E8C3 /* AnalyticsPromptScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreen.swift; sourceTree = "<group>"; };
E55B5EA766E89FF1F87C3ACB /* RoomNotificationSettingsProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsProxyProtocol.swift; sourceTree = "<group>"; };
E5E7D4EE7CA295E5039FDA21 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; path = portrait_test_video.mp4; sourceTree = "<group>"; };
E5E7D4EE7CA295E5039FDA21 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = portrait_test_video.mp4; sourceTree = "<group>"; };
E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerProtocol.swift; sourceTree = "<group>"; };
E5F2B6443D1ED8602F328539 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ru.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
E5FDFAA04174CC99FB66391C /* EditRoomAddressScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditRoomAddressScreenViewModel.swift; sourceTree = "<group>"; };
@@ -2752,13 +2755,12 @@
ED0CBEAB5F796BEFBAF7BB6A /* VideoRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineView.swift; sourceTree = "<group>"; };
ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; };
ED33988DA4FD4FC666800106 /* SessionVerificationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenViewModel.swift; sourceTree = "<group>"; };
ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = "<group>"; };
ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = message.caf; sourceTree = "<group>"; };
ED49073BB1C1FC649DAC2CCD /* LocationRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRoomTimelineView.swift; sourceTree = "<group>"; };
ED60E4D2CD678E1EBF16F77A /* BlockedUsersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreen.swift; sourceTree = "<group>"; };
EDDE826EAB1BAB80C1104980 /* SpaceFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceFlowCoordinator.swift; sourceTree = "<group>"; };
EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = "<group>"; };
EE6BFF453838CF6C3982C5A3 /* RoomDirectorySearchScreenScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchScreenScreenViewModelTests.swift; sourceTree = "<group>"; };
EEA9DA018160CF76AFFBFBA7 /* SpaceSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceSettingsScreenCoordinator.swift; sourceTree = "<group>"; };
EEAB5662310AE73D93815134 /* JoinRoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRoomScreenViewModelProtocol.swift; sourceTree = "<group>"; };
EF13BFD415CA84B1272E94F8 /* PINTextFieldTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PINTextFieldTests.swift; sourceTree = "<group>"; };
EF1593DD87F974F8509BB619 /* ElementAnimations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementAnimations.swift; sourceTree = "<group>"; };
@@ -4253,7 +4255,6 @@
55312ACF4155CC5B2054AD75 /* SpaceSettingsScreen */ = {
isa = PBXGroup;
children = (
EEA9DA018160CF76AFFBFBA7 /* SpaceSettingsScreenCoordinator.swift */,
7C19E3A92E016D6E126DB06D /* View */,
);
path = SpaceSettingsScreen;
@@ -4579,7 +4580,6 @@
73CD9796729EB702B4DFA88C /* Sources */ = {
isa = PBXGroup;
children = (
A7F6CE522EBB769D00450B70 /* RoomMembersFlowCoordinatorTests.swift */,
58C2527813FDAE23E72A9063 /* AnalyticsSettingsScreenViewModelTests.swift */,
C687844F60BFF532D49A994C /* AnalyticsTests.swift */,
E461B3C8BBBFCA400B268D14 /* AppRouteURLParserTests.swift */,
@@ -4653,6 +4653,7 @@
4FCB2126C091EEF2454B4D56 /* RoomFlowCoordinatorTests.swift */,
8AE0C9653870803E4F91F474 /* RoomListFiltersStateTests.swift */,
EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */,
CC666DAE98245269775329B2 /* RoomMembersFlowCoordinatorTests.swift */,
3E9E0929CEFA356090BE5FB8 /* RoomMembersListScreenViewModelTests.swift */,
58D295F0081084F38DB20893 /* RoomNotificationSettingsScreenViewModelTests.swift */,
F0096BC5DA86AF6B6E5742AC /* RoomPermissionsTests.swift */,
@@ -5323,7 +5324,9 @@
985411D514A5913EB1B60B54 /* Spaces */ = {
isa = PBXGroup;
children = (
A7385BA92EBDFF31000B0961 /* LeaveSpace */,
BDDD421CD80AD0BCBA035076 /* Common */,
ADA672F2647D05F087305B21 /* LeaveSpaceSheet */,
FCF165F4DDB83F3DECFEB57A /* SpaceListScreen */,
C360FCF7418FE3593D5A0CBF /* SpaceScreen */,
55312ACF4155CC5B2054AD75 /* SpaceSettingsScreen */,
@@ -5523,6 +5526,25 @@
path = View;
sourceTree = "<group>";
};
A7385BA92EBDFF31000B0961 /* LeaveSpace */ = {
isa = PBXGroup;
children = (
A7385BAB2EBDFF7B000B0961 /* LeaveSpaceModels.swift */,
B8C59748CC34606FF4FE95F0 /* LeaveSpaceViewModel.swift */,
A7385BAA2EBDFF42000B0961 /* View */,
);
path = LeaveSpace;
sourceTree = "<group>";
};
A7385BAA2EBDFF42000B0961 /* View */ = {
isa = PBXGroup;
children = (
D7813824C547ED121F6F8E0F /* LeaveSpaceView.swift */,
B329F7962435DB1B5F49F2AC /* LeaveSpaceRoomDetailsCell.swift */,
);
path = View;
sourceTree = "<group>";
};
A7694BCE812C4D7B2B1B42DF /* View */ = {
isa = PBXGroup;
children = (
@@ -5619,6 +5641,13 @@
path = View;
sourceTree = "<group>";
};
ADA672F2647D05F087305B21 /* LeaveSpaceSheet */ = {
isa = PBXGroup;
children = (
);
path = LeaveSpaceSheet;
sourceTree = "<group>";
};
B04B538A859CD012755DC19C /* NSE */ = {
isa = PBXGroup;
children = (
@@ -5848,6 +5877,13 @@
path = MapLibre;
sourceTree = "<group>";
};
C18958141C8ED6D778F779A4 /* CreateRoom */ = {
isa = PBXGroup;
children = (
);
path = CreateRoom;
sourceTree = "<group>";
};
C1CD278862878F9545608040 /* SessionVerificationScreen */ = {
isa = PBXGroup;
children = (
@@ -6230,6 +6266,7 @@
53FB148CD26AFB6A5B9E20B3 /* BugReportScreen */,
1185EECDD07495D65AC84AFC /* CallScreen */,
90DC2E28718955ED87AD1456 /* CreatePollScreen */,
C18958141C8ED6D778F779A4 /* CreateRoom */,
821EB0D1C0019E3C7BBAEDBB /* CreateRoomScreen */,
3E1CCC4B607946CE90B4A827 /* DeclineAndBlockScreen */,
45F2BCFD6E9A6F040CC20582 /* EditRoomAddressScreen */,
@@ -6486,8 +6523,6 @@
FA1D480A302295CFC3582543 /* View */ = {
isa = PBXGroup;
children = (
B329F7962435DB1B5F49F2AC /* LeaveSpaceRoomDetailsCell.swift */,
D7813824C547ED121F6F8E0F /* LeaveSpaceView.swift */,
646B50583A2CE6DA67F7739A /* SpaceScreen.swift */,
);
path = View;
@@ -6964,7 +6999,6 @@
EE40B0E16A55BD23ECBFFD22 /* XCRemoteSwiftPackageReference "matrix-rich-text-editor-swift" */,
C89CF7729E028671C5DC461E /* XCLocalSwiftPackageReference "compound-ios" */,
);
preferredProjectObjectVersion = 77;
projectDirPath = "";
projectRoot = "";
targets = (
@@ -7376,7 +7410,6 @@
3582056513A384F110EC8274 /* MediaPlayerProviderTests.swift in Sources */,
167D00CAA13FAFB822298021 /* MediaProviderTests.swift in Sources */,
B9A8C34A00D03094C0CF56F3 /* MediaUploadPreviewScreenViewModelTests.swift in Sources */,
A7F6CE532EBB76A500450B70 /* RoomMembersFlowCoordinatorTests.swift in Sources */,
23701DE32ACD6FD40AA992C3 /* MediaUploadingPreprocessorTests.swift in Sources */,
F777C6FEE7D106136E2ED2B2 /* MessageForwardingScreenViewModelTests.swift in Sources */,
1146E9EDCF8344F7D6E0D553 /* MockCoder.swift in Sources */,
@@ -7409,6 +7442,7 @@
095D3906CF2F940C2D2D17CC /* RoomFlowCoordinatorTests.swift in Sources */,
4C8C0C9FC10BA73AB7780534 /* RoomListFiltersStateTests.swift in Sources */,
6B31508C6334C617360C2EAB /* RoomMemberDetailsViewModelTests.swift in Sources */,
695BE6A2337A634F48B5DBC8 /* RoomMembersFlowCoordinatorTests.swift in Sources */,
5D56CE09743C6B90C21B04C2 /* RoomMembersListScreenViewModelTests.swift in Sources */,
E49F74BD93230BDEFFE5EA51 /* RoomNotificationSettingsScreenViewModelTests.swift in Sources */,
2335D1AB954C151FD8779F45 /* RoomPermissionsTests.swift in Sources */,
@@ -7591,6 +7625,7 @@
0F6C8033FA60CFD36F7CA205 /* AppLockSetupPINScreenViewModel.swift in Sources */,
4C356F5CCB4CDC99BFA45185 /* AppLockSetupPINScreenViewModelProtocol.swift in Sources */,
4E8A2A2CFEB212F14E49E1A1 /* AppLockSetupSettingsScreen.swift in Sources */,
A7385BAC2EBDFF80000B0961 /* LeaveSpaceModels.swift in Sources */,
8DCD9CC5361FF22A5B2C20F1 /* AppLockSetupSettingsScreenCoordinator.swift in Sources */,
6E4E401BE97AC241DA7C7716 /* AppLockSetupSettingsScreenModels.swift in Sources */,
4807E8F51DB54F56B25E1C7E /* AppLockSetupSettingsScreenViewModel.swift in Sources */,
@@ -7895,6 +7930,7 @@
DD21CE51DF9BD04FC8155972 /* LeaveSpaceHandleSDKMock.swift in Sources */,
37EE1FB8400BBDC7A7338E57 /* LeaveSpaceRoomDetailsCell.swift in Sources */,
B6CA5D18D702D0919BEF0263 /* LeaveSpaceView.swift in Sources */,
F3382570701BB87DA816AC82 /* LeaveSpaceViewModel.swift in Sources */,
42B084FDE621FBEE433AF444 /* LegalInformationScreen.swift in Sources */,
9EBDC79CAC9B63A0D626E333 /* LegalInformationScreenCoordinator.swift in Sources */,
F40B097470D3110DFDB1FAAA /* LegalInformationScreenModels.swift in Sources */,
@@ -8329,7 +8365,6 @@
DB5200B87C4CE9DF0024AC4E /* SpaceServiceProxyProtocol.swift in Sources */,
D0E257557DAC8A34C7B52A9F /* SpaceSettingsFlowCoordinator.swift in Sources */,
383063A7924F06D54BA9B24C /* SpaceSettingsScreen.swift in Sources */,
FD3C94F01ACAF2D4948CF9BE /* SpaceSettingsScreenCoordinator.swift in Sources */,
9DB4B303ECC05F0F33582594 /* SpacesAnnouncementSheetView.swift in Sources */,
DF004A5B2EABBD0574D06A04 /* SplashScreenCoordinator.swift in Sources */,
E1C67E5D9E22135A8FEBBD60 /* StackedAvatarsView.swift in Sources */,
@@ -8784,9 +8819,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
OTHER_SWIFT_FLAGS = (
"-DRELEASE",
);
OTHER_SWIFT_FLAGS = "-DRELEASE";
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.accessibility.tests";
PRODUCT_NAME = AccessibilityTests;
SDKROOT = iphoneos;
@@ -8805,9 +8838,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
OTHER_SWIFT_FLAGS = (
"-DDEBUG",
);
OTHER_SWIFT_FLAGS = "-DDEBUG";
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.accessibility.tests";
PRODUCT_NAME = AccessibilityTests;
SDKROOT = iphoneos;
@@ -8829,9 +8860,7 @@
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = "$(MARKETING_VERSION)";
OTHER_SWIFT_FLAGS = (
"-DIS_NSE",
);
OTHER_SWIFT_FLAGS = "-DIS_NSE";
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.nse";
PRODUCT_DISPLAY_NAME = "$(APP_DISPLAY_NAME)";
PRODUCT_NAME = NSE;
@@ -8880,9 +8909,7 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = "$(MARKETING_VERSION)";
OTHER_SWIFT_FLAGS = (
"-DIS_MAIN_APP",
);
OTHER_SWIFT_FLAGS = "-DIS_MAIN_APP";
PILLS_UT_TYPE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).pills";
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = "$(APP_NAME)";
@@ -8908,9 +8935,7 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = "$(MARKETING_VERSION)";
OTHER_SWIFT_FLAGS = (
"-DIS_MAIN_APP",
);
OTHER_SWIFT_FLAGS = "-DIS_MAIN_APP";
PILLS_UT_TYPE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).pills";
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = "$(APP_NAME)";
@@ -9135,9 +9160,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
OTHER_SWIFT_FLAGS = (
"-DDEBUG",
);
OTHER_SWIFT_FLAGS = "-DDEBUG";
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.ui.tests";
PRODUCT_NAME = UITests;
SDKROOT = iphoneos;
@@ -9156,9 +9179,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
OTHER_SWIFT_FLAGS = (
"-DRELEASE",
);
OTHER_SWIFT_FLAGS = "-DRELEASE";
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.ui.tests";
PRODUCT_NAME = UITests;
SDKROOT = iphoneos;
@@ -9180,9 +9201,7 @@
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = "$(MARKETING_VERSION)";
OTHER_SWIFT_FLAGS = (
"-DIS_NSE",
);
OTHER_SWIFT_FLAGS = "-DIS_NSE";
PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.nse";
PRODUCT_DISPLAY_NAME = "$(APP_DISPLAY_NAME)";
PRODUCT_NAME = NSE;
@@ -43,6 +43,7 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
private var roomFlowCoordinator: RoomFlowCoordinator?
private var membersFlowCoordinator: RoomMembersFlowCoordinator?
private var settingsFlowCoordinator: SpaceSettingsFlowCoordinator?
private var rolesAndPermissionsFlowCoordinator: RoomRolesAndPermissionsFlowCoordinator?
indirect enum State: StateType {
/// The state machine hasn't started.
@@ -60,6 +61,8 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
/// A space settings flow is in progress
case settingsFlow
case rolesAndPermissionsFlow
case leftSpace
}
@@ -89,6 +92,9 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
case startSettingsFlow
case stopSettingsFlow
case startRolesAndPermissionsFlow
case stopRolesAndPermissionsFlow
}
private let stateMachine: StateMachine<State, Event>
@@ -151,6 +157,9 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
case .settingsFlow:
settingsFlowCoordinator?.clearRoute(animated: animated)
clearRoute(animated: animated) // Re-run with the state machine back in the .space state.
case .rolesAndPermissionsFlow:
rolesAndPermissionsFlowCoordinator?.clearRoute(animated: animated)
clearRoute(animated: animated) // Re-run with the state machine back in the .space state.
}
}
@@ -248,6 +257,22 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
settingsFlowCoordinator = nil
}
stateMachine.addRouteMapping { event, fromState, _ in
guard event == .startRolesAndPermissionsFlow, case .space = fromState else { return nil }
return .rolesAndPermissionsFlow
} handler: { [weak self] context in
guard let self, let roomProxy = context.userInfo as? JoinedRoomProxyProtocol else { return }
startRolesAndPermissionsFlow(roomProxy: roomProxy)
}
stateMachine.addRouteMapping { event, fromState, _ in
guard event == .stopRolesAndPermissionsFlow, case .rolesAndPermissionsFlow = fromState else { return nil }
return .space
} handler: { [weak self] _ in
guard let self else { return }
rolesAndPermissionsFlowCoordinator = nil
}
stateMachine.addErrorHandler { context in
fatalError("Unexpected transition: \(context)")
}
@@ -279,6 +304,8 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
stateMachine.tryEvent(.startMembersFlow, userInfo: roomProxy)
case .displaySpaceSettings(let roomProxy):
stateMachine.tryEvent(.startSettingsFlow, userInfo: roomProxy)
case .displayRolesAndPermissions(let roomProxy):
stateMachine.tryEvent(.startRolesAndPermissionsFlow, userInfo: roomProxy)
}
}
.store(in: &cancellables)
@@ -430,8 +457,11 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
flowCoordinator.actions.sink { [weak self] actions in
guard let self else { return }
switch actions {
case .finished:
case .finished(let leftRoom):
stateMachine.tryEvent(.stopSettingsFlow)
if leftRoom {
stateMachine.tryEvent(.leftSpace)
}
case .presentCallScreen(let roomProxy):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
case .verifyUser(userID: let userID):
@@ -443,4 +473,23 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
settingsFlowCoordinator = flowCoordinator
flowCoordinator.start()
}
private func startRolesAndPermissionsFlow(roomProxy: JoinedRoomProxyProtocol) {
let flowCoordinator = RoomRolesAndPermissionsFlowCoordinator(parameters: .init(roomProxy: roomProxy,
mediaProvider: flowParameters.userSession.mediaProvider,
navigationStackCoordinator: navigationStackCoordinator,
userIndicatorController: flowParameters.userIndicatorController,
analytics: flowParameters.analytics))
flowCoordinator.actionsPublisher.sink { [weak self] action in
guard let self else { return }
switch action {
case .complete:
stateMachine.tryEvent(.stopRolesAndPermissionsFlow)
}
}
.store(in: &cancellables)
rolesAndPermissionsFlowCoordinator = flowCoordinator
flowCoordinator.start()
}
}
@@ -10,7 +10,7 @@ import Foundation
import SwiftState
enum SpaceSettingsFlowCoordinatorAction {
case finished
case finished(leftRoom: Bool)
case presentCallScreen(roomProxy: JoinedRoomProxyProtocol)
case verifyUser(userID: String)
}
@@ -65,6 +65,9 @@ final class SpaceSettingsFlowCoordinator: FlowCoordinatorProtocol {
private let stateMachine: StateMachine<State, Event>
private var cancellables = Set<AnyCancellable>()
private var membersFlowCoordinator: RoomMembersFlowCoordinator?
private var rolesAndPermissionsFlowCoordinator: RoomRolesAndPermissionsFlowCoordinator?
private var childFlowCoordinator: FlowCoordinatorProtocol?
private let actionsSubject: PassthroughSubject<SpaceSettingsFlowCoordinatorAction, Never> = .init()
@@ -178,31 +181,40 @@ final class SpaceSettingsFlowCoordinator: FlowCoordinatorProtocol {
}
private func presentSpaceSettings(animated: Bool) {
let coordinator = SpaceSettingsScreenCoordinator(parameters: .init(roomProxy: roomProxy,
userSession: flowParameters.userSession,
analyticsService: flowParameters.analytics,
userIndicator: flowParameters.userIndicatorController,
notificationSettingsProxy: flowParameters.userSession.clientProxy.notificationSettings,
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: flowParameters.appSettings))
let coordinator = RoomDetailsScreenCoordinator(parameters: .init(roomProxy: roomProxy,
userSession: flowParameters.userSession,
analyticsService: flowParameters.analytics,
userIndicatorController: flowParameters.userIndicatorController,
notificationSettings: flowParameters.userSession.clientProxy.notificationSettings,
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: flowParameters.appSettings))
coordinator.actionsPublisher.sink { [weak self] action in
var leftRoom = false
coordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .presentEditDetailsScreen:
case .presentRoomDetailsEditScreen:
stateMachine.tryEvent(.presentEditDetailsScreen)
case .presentSecurityAndPrivacyScreen:
stateMachine.tryEvent(.presentSecurityAndPrivacyScreen)
case .presentMembersListScreen:
case .presentRoomMembersList:
stateMachine.tryEvent(.startMembersListFlow)
case .presentRolesAndPermissionsScreen:
stateMachine.tryEvent(.startRolesAndPermissionsFlow)
case .leftRoom:
leftRoom = true
navigationStackCoordinator.pop()
case .presentRecipientDetails, .presentNotificationSettingsScreen, .transferOwnership,
.presentInviteUsersScreen, .presentPollsHistory, .presentCall,
.presentPinnedEventsTimeline, .presentMediaEventsTimeline, .presentKnockingRequestsListScreen,
.presentReportRoomScreen:
fatalError("Not handled in the space context")
}
}
.store(in: &cancellables)
navigationStackCoordinator.push(coordinator, animated: animated) { [weak self] in
self?.actionsSubject.send(.finished)
self?.actionsSubject.send(.finished(leftRoom: leftRoom))
}
}
@@ -39,6 +39,7 @@ enum RoomDetailsScreenCoordinatorAction {
final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
private var viewModel: RoomDetailsScreenViewModelProtocol
private let isSpace: Bool
private let actionsSubject: PassthroughSubject<RoomDetailsScreenCoordinatorAction, Never> = .init()
private var cancellables = Set<AnyCancellable>()
@@ -48,6 +49,7 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
}
init(parameters: RoomDetailsScreenCoordinatorParameters) {
isSpace = parameters.roomProxy.infoPublisher.value.isSpace
viewModel = RoomDetailsScreenViewModel(roomProxy: parameters.roomProxy,
userSession: parameters.userSession,
analyticsService: parameters.analyticsService,
@@ -105,6 +107,10 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
}
func toPresentable() -> AnyView {
AnyView(RoomDetailsScreen(context: viewModel.context))
if isSpace {
AnyView(SpaceSettingsScreen(context: viewModel.context))
} else {
AnyView(RoomDetailsScreen(context: viewModel.context))
}
}
}
@@ -172,6 +172,9 @@ struct RoomDetailsScreenViewStateBindings {
/// A media item that will be previewed with QuickLook.
var mediaPreviewItem: MediaPreviewItem?
/// The view model used to display the leave space sheet, will only be used if the room is a space.
var leaveSpaceViewModel: LeaveSpaceViewModel?
}
struct LeaveRoomAlertItem: AlertProtocol {
@@ -209,10 +209,29 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
private func processLeaveSpace() async {
switch await userSession.clientProxy.spaceService.leaveSpace(spaceID: roomProxy.id) {
case .success:
// TODO: Handle leave space
break
case .failure(let failure):
case .success(let leaveHandle):
let leaveSpaceViewModel = LeaveSpaceViewModel(spaceName: state.details.name ?? state.details.id,
canEditRolesAndPermissions: state.canEditRolesOrPermissions,
leaveHandle: leaveHandle,
userIndicatorController: userIndicatorController,
mediaProvider: userSession.mediaProvider)
leaveSpaceViewModel.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .didCancel:
state.bindings.leaveSpaceViewModel = nil
case .presentRolesAndPermissions:
state.bindings.leaveSpaceViewModel = nil
actionsSubject.send(.requestRolesAndPermissionsPresentation)
case .didLeaveSpace:
state.bindings.leaveSpaceViewModel = nil
actionsSubject.send(.leftRoom)
}
}
.store(in: &cancellables)
state.bindings.leaveSpaceViewModel = leaveSpaceViewModel
case .failure:
userIndicatorController.submitIndicator(.init(title: L10n.errorUnknown))
}
}
@@ -0,0 +1,48 @@
//
// Copyright 2025 Element Creations Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
enum LeaveSpaceViewAction {
case confirmLeaveSpace
case deselectAll
case selectAll
case toggleRoom(roomID: String)
case rolesAndPermissions
case cancel
}
struct LeaveSpaceViewState: BindableState {
let spaceName: String
let canEditRolesAndPermissions: Bool
let leaveHandle: LeaveSpaceHandleProxy
var title: String {
switch leaveHandle.mode {
case .lastSpaceAdmin: L10n.screenLeaveSpaceTitleLastAdmin(spaceName)
default: L10n.screenLeaveSpaceTitle(spaceName)
}
}
var subtitle: String? {
switch leaveHandle.mode {
case .manyRooms: L10n.screenLeaveSpaceSubtitle
case .onlyAdminRooms: L10n.screenLeaveSpaceSubtitleOnlyLastAdmin
case .noRooms: nil
case .lastSpaceAdmin: L10n.screenLeaveSpaceSubtitleLastAdmin
}
}
var confirmationTitle: String {
let selectedCount = leaveHandle.selectedCount
return selectedCount > 0 ? L10n.screenLeaveSpaceSubmit(selectedCount) : L10n.actionLeaveSpace
}
}
enum LeaveSpaceViewModelAction {
case didLeaveSpace
case presentRolesAndPermissions
case didCancel
}
@@ -0,0 +1,79 @@
//
// Copyright 2025 Element Creations Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//
import Combine
import SwiftUI
typealias LeaveSpaceViewModelType = StateStoreViewModelV2<LeaveSpaceViewState, LeaveSpaceViewAction>
class LeaveSpaceViewModel: LeaveSpaceViewModelType {
let actionsSubject = PassthroughSubject<LeaveSpaceViewModelAction, Never>()
var actions: AnyPublisher<LeaveSpaceViewModelAction, Never> { actionsSubject.eraseToAnyPublisher() }
private let userIndicatorController: UserIndicatorControllerProtocol
private let mediaProvider: MediaProviderProtocol
init(spaceName: String, canEditRolesAndPermissions: Bool, leaveHandle: LeaveSpaceHandleProxy, userIndicatorController: UserIndicatorControllerProtocol, mediaProvider: MediaProviderProtocol) {
self.userIndicatorController = userIndicatorController
self.mediaProvider = mediaProvider
super.init(initialViewState: LeaveSpaceViewState(spaceName: spaceName, canEditRolesAndPermissions: canEditRolesAndPermissions, leaveHandle: leaveHandle), mediaProvider: mediaProvider)
}
override func process(viewAction: LeaveSpaceViewAction) {
switch viewAction {
case .confirmLeaveSpace:
Task { await confirmLeaveSpace() }
case .rolesAndPermissions:
actionsSubject.send(.presentRolesAndPermissions)
case .cancel:
actionsSubject.send(.didCancel)
case .deselectAll:
state.leaveHandle.deselectAll()
case .selectAll:
state.leaveHandle.selectAll()
case .toggleRoom(let roomID):
withTransaction(\.disablesAnimations, true) { // The button is adding an unwanted animation.
state.leaveHandle.toggleRoom(roomID: roomID)
}
}
}
private func confirmLeaveSpace() async {
showLeavingIndicator()
defer { hideLeavingIndicator() }
switch await state.leaveHandle.leave() {
case .success:
actionsSubject.send(.didLeaveSpace)
case .failure:
showFailureIndicator()
}
}
private static var leavingIndicatorID: String { "\(Self.self)-Leaving" }
private static var failureIndicatorID: String { "\(Self.self)-Failure" }
private func showLeavingIndicator() {
userIndicatorController.submitIndicator(UserIndicator(id: Self.leavingIndicatorID,
type: .modal(progress: .indeterminate, interactiveDismissDisabled: true, allowsInteraction: false),
title: L10n.commonLeavingSpace))
}
private func hideLeavingIndicator() {
userIndicatorController.retractIndicatorWithId(Self.leavingIndicatorID)
}
private func showFailureIndicator() {
userIndicatorController.submitIndicator(UserIndicator(id: Self.failureIndicatorID,
type: .toast,
title: L10n.errorUnknown,
iconName: "xmark"))
}
}
extension LeaveSpaceViewModel: Identifiable {
var id: String { state.leaveHandle.id }
}
@@ -12,8 +12,7 @@ import SwiftUI
struct LeaveSpaceView: View {
@Environment(\.dismiss) private var dismiss
let context: SpaceScreenViewModel.Context
let leaveHandle: LeaveSpaceHandleProxy
let context: LeaveSpaceViewModel.Context
@State private var scrollViewHeight: CGFloat = .zero
@State private var buttonsHeight: CGFloat = .zero
@@ -43,12 +42,12 @@ struct LeaveSpaceView: View {
BigIcon(icon: \.errorSolid, style: .alertSolid)
VStack(spacing: 8) {
Text(leaveHandle.title(spaceName: context.viewState.space.name))
Text(context.viewState.title)
.font(.compound.headingMDBold)
.foregroundStyle(.compound.textPrimary)
.multilineTextAlignment(.center)
if let subtitle = leaveHandle.subtitle {
if let subtitle = context.viewState.subtitle {
Text(subtitle)
.font(.compound.bodyMD)
.foregroundStyle(.compound.textSecondary)
@@ -61,21 +60,22 @@ struct LeaveSpaceView: View {
@ViewBuilder
var rooms: some View {
if !leaveHandle.rooms.isEmpty, leaveHandle.canLeave {
if !context.viewState.leaveHandle.rooms.isEmpty,
context.viewState.leaveHandle.canLeave {
LazyVStack(spacing: 0, pinnedViews: .sectionHeaders) {
Section {
ForEach(leaveHandle.rooms, id: \.spaceRoomProxy.id) { room in
ForEach(context.viewState.leaveHandle.rooms, id: \.spaceRoomProxy.id) { room in
LeaveSpaceRoomDetailsCell(room: room,
hideSelection: leaveHandle.mode == .onlyAdminRooms,
hideSelection: context.viewState.leaveHandle.mode == .onlyAdminRooms,
mediaProvider: context.mediaProvider) {
context.send(viewAction: .toggleLeaveSpaceRoomDetails(id: room.spaceRoomProxy.id))
context.send(viewAction: .toggleRoom(roomID: room.spaceRoomProxy.id))
}
.disabled(room.isLastAdmin)
}
} header: {
if leaveHandle.mode == .manyRooms {
Button(leaveHandle.selectedCount > 0 ? L10n.actionDeselectAll : L10n.actionSelectAll) {
context.send(viewAction: leaveHandle.selectedCount > 0 ? .deselectAllLeaveRoomDetails : .selectAllLeaveRoomDetails)
if context.viewState.leaveHandle.mode == .manyRooms {
Button(context.viewState.leaveHandle.selectedCount > 0 ? L10n.actionDeselectAll : L10n.actionSelectAll) {
context.send(viewAction: context.viewState.leaveHandle.selectedCount > 0 ? .deselectAll : .selectAll)
}
.buttonStyle(.compound(.textLink, size: .small))
.frame(maxWidth: .infinity, alignment: .trailing)
@@ -90,11 +90,11 @@ struct LeaveSpaceView: View {
var buttons: some View {
VStack(spacing: 16) {
if leaveHandle.canLeave {
if context.viewState.leaveHandle.canLeave {
Button(role: .destructive) {
context.send(viewAction: .confirmLeaveSpace)
} label: {
Label(leaveHandle.confirmationTitle, icon: \.leave)
Label(context.viewState.confirmationTitle, icon: \.leave)
}
.buttonStyle(.compound(.primary))
} else if context.viewState.canEditRolesAndPermissions {
@@ -114,44 +114,24 @@ struct LeaveSpaceView: View {
}
}
private extension LeaveSpaceHandleProxy {
func title(spaceName: String) -> String {
switch mode {
case .lastSpaceAdmin: L10n.screenLeaveSpaceTitleLastAdmin(spaceName)
default: L10n.screenLeaveSpaceTitle(spaceName)
}
}
var subtitle: String? {
switch mode {
case .manyRooms: L10n.screenLeaveSpaceSubtitle
case .onlyAdminRooms: L10n.screenLeaveSpaceSubtitleOnlyLastAdmin
case .noRooms: nil
case .lastSpaceAdmin: L10n.screenLeaveSpaceSubtitleLastAdmin
}
}
var confirmationTitle: String {
let selectedCount = selectedCount
return selectedCount > 0 ? L10n.screenLeaveSpaceSubmit(selectedCount) : L10n.actionLeaveSpace
}
}
// MARK: - Previews
import MatrixRustSDK
struct LeaveSpaceView_Previews: PreviewProvider, TestablePreview {
static let viewModel = makeViewModel()
static let manyViewModel = makeViewModel(mode: .manyRooms)
static let onlyAdminViewModel = makeViewModel(mode: .onlyAdminRooms)
static let noRoomsViewModel = makeViewModel(mode: .noRooms)
static let lastAdminViewModel = makeViewModel(mode: .lastSpaceAdmin)
static var previews: some View {
LeaveSpaceView(context: viewModel.context, leaveHandle: makeLeaveHandle(mode: .manyRooms))
LeaveSpaceView(context: manyViewModel.context)
.previewDisplayName("Many Rooms")
LeaveSpaceView(context: viewModel.context, leaveHandle: makeLeaveHandle(mode: .onlyAdminRooms))
LeaveSpaceView(context: onlyAdminViewModel.context)
.previewDisplayName("Only Admin Rooms")
LeaveSpaceView(context: viewModel.context, leaveHandle: makeLeaveHandle(mode: .noRooms))
LeaveSpaceView(context: noRoomsViewModel.context)
.previewDisplayName("No Rooms")
LeaveSpaceView(context: viewModel.context, leaveHandle: makeLeaveHandle(mode: .lastSpaceAdmin))
LeaveSpaceView(context: lastAdminViewModel.context)
.previewDisplayName("Last Space Admin")
}
@@ -164,21 +144,7 @@ struct LeaveSpaceView_Previews: PreviewProvider, TestablePreview {
topic: "Description of the space goes right here. Lorem ipsum dolor sit amet consectetur. Leo viverra morbi habitant in.",
joinRule: .knockRestricted(rules: [.roomMembership(roomId: "")])))
static func makeViewModel() -> SpaceScreenViewModel {
let spaceRoomListProxy = SpaceRoomListProxyMock(.init(spaceRoomProxy: spaceRoomProxy,
initialSpaceRooms: .mockSpaceList))
let spaceServiceProxy = SpaceServiceProxyMock(.init())
let viewModel = SpaceScreenViewModel(spaceRoomListProxy: spaceRoomListProxy,
spaceServiceProxy: spaceServiceProxy,
selectedSpaceRoomPublisher: .init(nil),
userSession: UserSessionMock(.init()),
appSettings: AppSettings(),
userIndicatorController: UserIndicatorControllerMock())
return viewModel
}
static func makeLeaveHandle(mode: LeaveSpaceHandleProxy.Mode) -> LeaveSpaceHandleProxy {
static func makeViewModel(mode: LeaveSpaceHandleProxy.Mode) -> LeaveSpaceViewModel {
let rooms: [LeaveSpaceRoom] = switch mode {
case .manyRooms: .mockRooms
case .onlyAdminRooms: .mockAdminRooms
@@ -186,7 +152,13 @@ struct LeaveSpaceView_Previews: PreviewProvider, TestablePreview {
case .lastSpaceAdmin: .mockLastSpaceAdmin(spaceRoomProxy: spaceRoomProxy)
}
return LeaveSpaceHandleProxy(spaceID: spaceRoomProxy.id,
leaveHandle: LeaveSpaceHandleSDKMock(.init(rooms: rooms)))
let leaveHandle = LeaveSpaceHandleProxy(spaceID: spaceRoomProxy.id,
leaveHandle: LeaveSpaceHandleSDKMock(.init(rooms: rooms)))
return LeaveSpaceViewModel(spaceName: spaceRoomProxy.name,
canEditRolesAndPermissions: true,
leaveHandle: leaveHandle,
userIndicatorController: UserIndicatorControllerMock(),
mediaProvider: MediaProviderMock(configuration: .init()))
}
}
@@ -27,6 +27,7 @@ enum SpaceScreenCoordinatorAction {
case leftSpace
case displayMembers(roomProxy: JoinedRoomProxyProtocol)
case displaySpaceSettings(roomProxy: JoinedRoomProxyProtocol)
case displayRolesAndPermissions(roomProxy: JoinedRoomProxyProtocol)
}
final class SpaceScreenCoordinator: CoordinatorProtocol {
@@ -69,6 +70,8 @@ final class SpaceScreenCoordinator: CoordinatorProtocol {
actionsSubject.send(.displayMembers(roomProxy: roomProxy))
case .displaySpaceSettings(let roomProxy):
actionsSubject.send(.displaySpaceSettings(roomProxy: roomProxy))
case .presentRolesAndPermissions(let roomProxy):
actionsSubject.send(.displayRolesAndPermissions(roomProxy: roomProxy))
}
}
.store(in: &cancellables)
@@ -13,6 +13,7 @@ enum SpaceScreenViewModelAction {
case selectUnjoinedSpace(SpaceRoomProxyProtocol)
case selectRoom(roomID: String)
case leftSpace
case presentRolesAndPermissions(roomProxy: JoinedRoomProxyProtocol)
case displayMembers(roomProxy: JoinedRoomProxyProtocol)
case displaySpaceSettings(roomProxy: JoinedRoomProxyProtocol)
}
@@ -39,17 +40,12 @@ struct SpaceScreenViewState: BindableState {
}
struct SpaceScreenViewStateBindings {
var leaveHandle: LeaveSpaceHandleProxy?
var leaveSpaceViewModel: LeaveSpaceViewModel?
}
enum SpaceScreenViewAction {
case spaceAction(SpaceRoomCell.Action)
case leaveSpace
case deselectAllLeaveRoomDetails
case selectAllLeaveRoomDetails
case toggleLeaveSpaceRoomDetails(id: String)
case confirmLeaveSpace
case spaceSettings(roomProxy: JoinedRoomProxyProtocol)
case rolesAndPermissions
case displayMembers(roomProxy: JoinedRoomProxyProtocol)
}
@@ -15,6 +15,7 @@ class SpaceScreenViewModel: SpaceScreenViewModelType, SpaceScreenViewModelProtoc
private let spaceRoomListProxy: SpaceRoomListProxyProtocol
private let spaceServiceProxy: SpaceServiceProxyProtocol
private let clientProxy: ClientProxyProtocol
private let mediaProvider: MediaProviderProtocol
private let appSettings: AppSettings
private let userIndicatorController: UserIndicatorControllerProtocol
@@ -32,6 +33,7 @@ class SpaceScreenViewModel: SpaceScreenViewModelType, SpaceScreenViewModelProtoc
self.spaceRoomListProxy = spaceRoomListProxy
self.spaceServiceProxy = spaceServiceProxy
clientProxy = userSession.clientProxy
mediaProvider = userSession.mediaProvider
self.userIndicatorController = userIndicatorController
self.appSettings = appSettings
@@ -117,31 +119,10 @@ class SpaceScreenViewModel: SpaceScreenViewModelType, SpaceScreenViewModelProtoc
Task { await join(spaceRoomProxy) }
case .leaveSpace:
Task { await showLeaveSpaceConfirmation() }
case .deselectAllLeaveRoomDetails:
guard let leaveHandle = state.bindings.leaveHandle else { fatalError("The leave handle should be available.") }
for room in leaveHandle.rooms {
room.isSelected = false
}
case .selectAllLeaveRoomDetails:
guard let leaveHandle = state.bindings.leaveHandle else { fatalError("The leave handle should be available.") }
for room in leaveHandle.rooms where !room.isLastAdmin {
room.isSelected = true
}
case .toggleLeaveSpaceRoomDetails(let spaceRoomID):
guard let room = state.bindings.leaveHandle?.rooms.first(where: { $0.spaceRoomProxy.id == spaceRoomID }) else {
fatalError("The space room to toggle is not in the list of rooms to leave.")
}
withTransaction(\.disablesAnimations, true) { // The button is adding an unwanted animation.
room.isSelected.toggle()
}
case .confirmLeaveSpace:
Task { await confirmLeaveSpace() }
case .displayMembers(let roomProxy):
actionsSubject.send(.displayMembers(roomProxy: roomProxy))
case .spaceSettings(let roomProxy):
actionsSubject.send(.displaySpaceSettings(roomProxy: roomProxy))
case .rolesAndPermissions:
break // Not implemented yet
}
}
@@ -180,41 +161,37 @@ class SpaceScreenViewModel: SpaceScreenViewModelType, SpaceScreenViewModelProtoc
return
}
state.bindings.leaveHandle = leaveHandle
}
private func confirmLeaveSpace() async {
guard let leaveHandle = state.bindings.leaveHandle else { fatalError("Leaving without a handle is impossible.") }
showLeavingIndicator()
defer { hideLeavingIndicator() }
switch await leaveHandle.leave() {
case .success:
state.bindings.leaveHandle = nil
actionsSubject.send(.leftSpace)
case .failure:
showFailureIndicator()
let leaveSpaceViewModel = LeaveSpaceViewModel(spaceName: state.space.name,
canEditRolesAndPermissions: appSettings.spaceSettingsEnabled && state.canEditRolesAndPermissions,
leaveHandle: leaveHandle,
userIndicatorController: userIndicatorController,
mediaProvider: mediaProvider)
leaveSpaceViewModel.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .didCancel:
state.bindings.leaveSpaceViewModel = nil
case .presentRolesAndPermissions:
guard let roomProxy = state.roomProxy else {
fatalError("The space screen should always have a room proxy")
}
state.bindings.leaveSpaceViewModel = nil
actionsSubject.send(.presentRolesAndPermissions(roomProxy: roomProxy))
case .didLeaveSpace:
state.bindings.leaveSpaceViewModel = nil
actionsSubject.send(.leftSpace)
}
}
.store(in: &cancellables)
state.bindings.leaveSpaceViewModel = leaveSpaceViewModel
}
private func updatePermissions() { }
// MARK: - Indicators
private static var leavingIndicatorID: String { "\(Self.self)-Leaving" }
private static var failureIndicatorID: String { "\(Self.self)-Failure" }
private func showLeavingIndicator() {
userIndicatorController.submitIndicator(UserIndicator(id: Self.leavingIndicatorID,
type: .modal(progress: .indeterminate, interactiveDismissDisabled: true, allowsInteraction: false),
title: L10n.commonLeavingSpace))
}
private func hideLeavingIndicator() {
userIndicatorController.retractIndicatorWithId(Self.leavingIndicatorID)
}
private func showFailureIndicator() {
userIndicatorController.submitIndicator(UserIndicator(id: Self.failureIndicatorID,
type: .toast,
@@ -25,8 +25,8 @@ struct SpaceScreen: View {
.navigationTitle(context.viewState.space.name)
.navigationBarTitleDisplayMode(.inline)
.toolbar { toolbar }
.sheet(item: $context.leaveHandle) { leaveHandle in
LeaveSpaceView(context: context, leaveHandle: leaveHandle)
.sheet(item: $context.leaveSpaceViewModel) { leaveSpaceViewModel in
LeaveSpaceView(context: leaveSpaceViewModel.context)
}
}
@@ -1,76 +0,0 @@
//
// Copyright 2025 Element Creations Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
import Combine
import SwiftUI
struct SpaceSettingsScreenCoordinatorParameters {
let roomProxy: JoinedRoomProxyProtocol
let userSession: UserSessionProtocol
let analyticsService: AnalyticsService
let userIndicator: UserIndicatorControllerProtocol
let notificationSettingsProxy: NotificationSettingsProxyProtocol
let attributedStringBuilder: AttributedStringBuilderProtocol
let appSettings: AppSettings
}
enum SpaceSettingsScreenCoordinatorAction {
case presentEditDetailsScreen
case presentSecurityAndPrivacyScreen
case presentMembersListScreen
case presentRolesAndPermissionsScreen
}
final class SpaceSettingsScreenCoordinator: CoordinatorProtocol {
private let viewModel: RoomDetailsScreenViewModelProtocol
private var cancellables = Set<AnyCancellable>()
private let actionsSubject: PassthroughSubject<SpaceSettingsScreenCoordinatorAction, Never> = .init()
var actionsPublisher: AnyPublisher<SpaceSettingsScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: SpaceSettingsScreenCoordinatorParameters) {
viewModel = RoomDetailsScreenViewModel(roomProxy: parameters.roomProxy,
userSession: parameters.userSession,
analyticsService: parameters.analyticsService,
userIndicatorController: parameters.userIndicator,
notificationSettingsProxy: parameters.notificationSettingsProxy,
attributedStringBuilder: parameters.attributedStringBuilder,
appSettings: parameters.appSettings)
}
func start() {
viewModel.actions.sink { [weak self] action in
MXLog.info("Coordinator: received view model action: \(action)")
guard let self else { return }
switch action {
case .requestNotificationSettingsPresentation, .requestRecipientDetailsPresentation, .requestInvitePeoplePresentation, .requestPollsHistoryPresentation,
.startCall, .displayPinnedEventsTimeline, .displayMediaEventsTimeline, .displayKnockingRequests,
.displayReportRoom, .transferOwnership:
break // Not handled in this context
case .requestEditDetailsPresentation:
actionsSubject.send(.presentEditDetailsScreen)
case .displaySecurityAndPrivacy:
actionsSubject.send(.presentSecurityAndPrivacyScreen)
case .requestMemberDetailsPresentation:
actionsSubject.send(.presentMembersListScreen)
case .requestRolesAndPermissionsPresentation:
actionsSubject.send(.presentRolesAndPermissionsScreen)
case .leftRoom:
break // TODO:
}
}
.store(in: &cancellables)
}
func toPresentable() -> AnyView {
AnyView(SpaceSettingsScreen(context: viewModel.context))
}
}
@@ -22,6 +22,9 @@ struct SpaceSettingsScreen: View {
}
.compoundList()
.navigationTitle(L10n.commonSettings)
.sheet(item: $context.leaveSpaceViewModel) { viewModel in
LeaveSpaceView(context: viewModel.context)
}
}
private var editSection: some View {
@@ -9,7 +9,7 @@
import Foundation
import MatrixRustSDK
class LeaveSpaceHandleProxy: Identifiable {
final class LeaveSpaceHandleProxy {
let id: String
var rooms: [LeaveSpaceRoomDetails]
@@ -51,6 +51,25 @@ class LeaveSpaceHandleProxy: Identifiable {
}
}
func deselectAll() {
for room in rooms {
room.isSelected = false
}
}
func selectAll() {
for room in rooms where !room.isLastAdmin {
room.isSelected = true
}
}
func toggleRoom(roomID: String) {
guard let room = rooms.first(where: { $0.spaceRoomProxy.id == roomID }) else {
return
}
room.isSelected.toggle()
}
func leave() async -> Result<Void, SpaceServiceProxyError> {
let selectedRoomIDs = rooms.filter(\.isSelected).map(\.spaceRoomProxy.id)
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c396362920b57958f7e0586fe9161ad7808e91c62e9457c6dbb0379c04b1b488
size 92482
oid sha256:b2fe3c4f6354f94523ee75f69ed37b702aac943a095bcee679a6743be420a2a2
size 103431
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3316fbd3a5c7b5310f940bef50381a3a947bfe82c836fe212f3a12d301ccf439
size 100701
oid sha256:b6087fcb56a01cebe9cbe17ce6e87764a3b586fdb4443b8a5a6c7be9fb5bbc7d
size 113376
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4626895535cc88986dc35a8ddb0fac304ca3cc1bf21db3a58daa6efb66912132
size 52109
oid sha256:f8d1d8999c2df94cc24c7b11a3727878ce3dd7bfde23910da358fe5293fd58f7
size 62111
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ef1fa43dc064fc2426bfa091eafcf4234f3a85ba2631dd95d5b0dca2648ddac5
size 70188
oid sha256:7d3418e7499a070280e0305e06b4b3a318d9acb73befe72cd9c42efa88451828
size 86087
@@ -178,29 +178,30 @@ class SpaceScreenViewModelTests: XCTestCase {
func testLeavingSpace() async throws {
setupViewModel()
XCTAssertNil(context.leaveHandle)
XCTAssertNil(context.leaveSpaceViewModel)
let deferredHandle = deferFulfillment(context.observe(\.leaveHandle)) { $0 != nil }
let deferredHandle = deferFulfillment(context.observe(\.leaveSpaceViewModel)) { $0 != nil }
context.send(viewAction: .leaveSpace)
try await deferredHandle.fulfill()
XCTAssertNotNil(context.leaveHandle, "The leave action should show the leave view.")
XCTAssertNotNil(context.leaveSpaceViewModel, "The leave action should show the leave view.")
let handle = try XCTUnwrap(context.leaveHandle)
let leaveSpaceViewModel = try XCTUnwrap(context.leaveSpaceViewModel)
let handle = try XCTUnwrap(context.leaveSpaceViewModel?.state.leaveHandle)
let selectedCount = handle.selectedCount
let firstSelectedRoom = try XCTUnwrap(handle.rooms.first { $0.isSelected })
XCTAssertGreaterThan(selectedCount, 0, "The leave view should have selected rooms to begin with")
context.send(viewAction: .deselectAllLeaveRoomDetails)
leaveSpaceViewModel.context.send(viewAction: .deselectAll)
XCTAssertEqual(handle.selectedCount, 0, "Deselecting all should result in no selected rooms.")
context.send(viewAction: .toggleLeaveSpaceRoomDetails(id: firstSelectedRoom.spaceRoomProxy.id))
leaveSpaceViewModel.context.send(viewAction: .toggleRoom(roomID: firstSelectedRoom.spaceRoomProxy.id))
XCTAssertEqual(handle.selectedCount, 1, "Toggling a room should result in 1 selected room")
// Confirming the leave should leave the selected room and then the space.
let deferredAction = deferFulfillment(viewModel.actionsPublisher) { $0.isLeftSpace }
context.send(viewAction: .confirmLeaveSpace)
leaveSpaceViewModel.context.send(viewAction: .confirmLeaveSpace)
try await deferredAction.fulfill()
XCTAssertNil(context.leaveHandle)
XCTAssertNil(context.leaveSpaceViewModel)
XCTAssertTrue(rustLeaveHandle.leaveRoomIdsCalled)
XCTAssertEqual(rustLeaveHandle.leaveRoomIdsReceivedRoomIds,
[firstSelectedRoom.spaceRoomProxy.id, spaceRoomListProxy.id],