Introduce a StartChatFlowCoordinator instead of handing a navigation stack to the Screen Coordinator. (#4674)

* Introduce a basic StartChatFlowCoordinator.

* Move the rest of the start chat flow from the screen coordinator into the flow coordinator.

* Add a UI test for the entire start chat flow.

* Refactor CreateRoom… to CreateRoomScreen…
This commit is contained in:
Doug
2025-10-30 16:12:56 +00:00
committed by GitHub
parent 401ff49882
commit 933891614c
68 changed files with 764 additions and 789 deletions
+44 -56
View File
@@ -78,7 +78,6 @@
0B05A35FF5D1ED9E8A0B41A7 /* AuthenticationStartScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65DDCF8E41759890355ACBC /* AuthenticationStartScreenViewModelProtocol.swift */; }; 0B05A35FF5D1ED9E8A0B41A7 /* AuthenticationStartScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65DDCF8E41759890355ACBC /* AuthenticationStartScreenViewModelProtocol.swift */; };
0BAF83521871E69D222EE8E4 /* ClientBuilderHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC0CD1CAFD3F8B057F9AEA5 /* ClientBuilderHook.swift */; }; 0BAF83521871E69D222EE8E4 /* ClientBuilderHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC0CD1CAFD3F8B057F9AEA5 /* ClientBuilderHook.swift */; };
0BDA19079FD6E17C5AC62E22 /* RoomDetailsEditScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB06F22CFA34885B40976061 /* RoomDetailsEditScreen.swift */; }; 0BDA19079FD6E17C5AC62E22 /* RoomDetailsEditScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB06F22CFA34885B40976061 /* RoomDetailsEditScreen.swift */; };
0BE4D5CBF86956410F071F91 /* CreateRoomViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15A657D96779D1DEB8EF1327 /* CreateRoomViewModel.swift */; };
0BFA67AFD757EE2BA569836A /* ScrollViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53482ECA4B6633961EC224F5 /* ScrollViewAdapter.swift */; }; 0BFA67AFD757EE2BA569836A /* ScrollViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53482ECA4B6633961EC224F5 /* ScrollViewAdapter.swift */; };
0C1E537A49ABB386F7554D4A /* HighlightedTimelineItemModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59B7CC77B82C6C67DE3AD869 /* HighlightedTimelineItemModifier.swift */; }; 0C1E537A49ABB386F7554D4A /* HighlightedTimelineItemModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59B7CC77B82C6C67DE3AD869 /* HighlightedTimelineItemModifier.swift */; };
0C346A4AD174F441EDB1414E /* IdentityConfirmationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB76A9AFC6CCAD4998D9B045 /* IdentityConfirmationScreenViewModel.swift */; }; 0C346A4AD174F441EDB1414E /* IdentityConfirmationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB76A9AFC6CCAD4998D9B045 /* IdentityConfirmationScreenViewModel.swift */; };
@@ -194,6 +193,7 @@
21813AF91CFC6F3E3896DB53 /* AppLockSetupBiometricsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10F130DF775CE6BC51A4E392 /* AppLockSetupBiometricsScreenModels.swift */; }; 21813AF91CFC6F3E3896DB53 /* AppLockSetupBiometricsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10F130DF775CE6BC51A4E392 /* AppLockSetupBiometricsScreenModels.swift */; };
21AFEFB8CEFE56A3811A1F5B /* VoiceMessageCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 283974987DA7EC61D2AB57D9 /* VoiceMessageCacheTests.swift */; }; 21AFEFB8CEFE56A3811A1F5B /* VoiceMessageCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 283974987DA7EC61D2AB57D9 /* VoiceMessageCacheTests.swift */; };
21F29351EDD7B2A5534EE862 /* SecureBackupKeyBackupScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD558A898847C179E4B7A237 /* SecureBackupKeyBackupScreen.swift */; }; 21F29351EDD7B2A5534EE862 /* SecureBackupKeyBackupScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD558A898847C179E4B7A237 /* SecureBackupKeyBackupScreen.swift */; };
221A7F92116722322AE150AE /* StartChatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0367E0AF1163F42292D788BD /* StartChatTests.swift */; };
22882C710BC99EC34A5024A0 /* UITestsScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CEBE5EA91E8691EDF364EC2 /* UITestsScreenIdentifier.swift */; }; 22882C710BC99EC34A5024A0 /* UITestsScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CEBE5EA91E8691EDF364EC2 /* UITestsScreenIdentifier.swift */; };
22B380C579C148BA0BFB5952 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3557ACB95D0F666EF5AF0CE /* Secrets.swift */; }; 22B380C579C148BA0BFB5952 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3557ACB95D0F666EF5AF0CE /* Secrets.swift */; };
22BA593A5E19D8D3DE2FAA6F /* TimelineEventContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F926D08EB3D622A480BCA71 /* TimelineEventContent.swift */; }; 22BA593A5E19D8D3DE2FAA6F /* TimelineEventContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F926D08EB3D622A480BCA71 /* TimelineEventContent.swift */; };
@@ -233,9 +233,7 @@
281BED345D59A9A6A99E9D98 /* UNNotificationContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */; }; 281BED345D59A9A6A99E9D98 /* UNNotificationContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */; };
2835FD52F3F618D07F799B3D /* Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7310D8DFE01AF45F0689C3AA /* Publisher.swift */; }; 2835FD52F3F618D07F799B3D /* Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7310D8DFE01AF45F0689C3AA /* Publisher.swift */; };
288408E6151D7BD3EBAA073A /* RoomScreenHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = B343C5255FB408DDE853CFDF /* RoomScreenHook.swift */; }; 288408E6151D7BD3EBAA073A /* RoomScreenHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = B343C5255FB408DDE853CFDF /* RoomScreenHook.swift */; };
28AB1614E749D1147A2AC6C2 /* CreateRoomScreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2214C32EA81FA9168D923D4C /* CreateRoomScreenTests.swift */; };
28E8C44DD6E39BEB2A1599C8 /* AccessibilityTestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CABD320DE5566D133890B24 /* AccessibilityTestsAppCoordinator.swift */; }; 28E8C44DD6E39BEB2A1599C8 /* AccessibilityTestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CABD320DE5566D133890B24 /* AccessibilityTestsAppCoordinator.swift */; };
292827744227DF61C930BDDB /* CreateRoomScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB0D6CB491777E7FC6B5BA12 /* CreateRoomScreen.swift */; };
2932570AA418974979D16DED /* UserPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E6DE144D887A254F4CAF203 /* UserPreference.swift */; }; 2932570AA418974979D16DED /* UserPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E6DE144D887A254F4CAF203 /* UserPreference.swift */; };
29491EE7AE37E239E839C5A3 /* LocationSharingScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BEBF0E59F25E842EDB6FD11 /* LocationSharingScreenModels.swift */; }; 29491EE7AE37E239E839C5A3 /* LocationSharingScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BEBF0E59F25E842EDB6FD11 /* LocationSharingScreenModels.swift */; };
2955F4C160CFD7794D819C64 /* EffectsScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 024F7398C5FC12586FB10E9D /* EffectsScene.swift */; }; 2955F4C160CFD7794D819C64 /* EffectsScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 024F7398C5FC12586FB10E9D /* EffectsScene.swift */; };
@@ -399,7 +397,6 @@
47305C0911C9E1AA774A4000 /* TemplateScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA90BD288E5AE6BC643AFDDF /* TemplateScreenCoordinator.swift */; }; 47305C0911C9E1AA774A4000 /* TemplateScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA90BD288E5AE6BC643AFDDF /* TemplateScreenCoordinator.swift */; };
4764FC9A843D1F9865EDC29C /* EditRoomAddressScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4048547AC50ADCF201684E87 /* EditRoomAddressScreen.swift */; }; 4764FC9A843D1F9865EDC29C /* EditRoomAddressScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4048547AC50ADCF201684E87 /* EditRoomAddressScreen.swift */; };
478C363F63A5DFC3C83E334C /* MediaProviderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F65E4BB9E82EB8373207CF8 /* MediaProviderMock.swift */; }; 478C363F63A5DFC3C83E334C /* MediaProviderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F65E4BB9E82EB8373207CF8 /* MediaProviderMock.swift */; };
4799A852132F1744E2825994 /* CreateRoomViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340179A0FC1AD4AEDA7FC134 /* CreateRoomViewModelProtocol.swift */; };
4807E8F51DB54F56B25E1C7E /* AppLockSetupSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D8C38663020DF2EB2D13F5E /* AppLockSetupSettingsScreenViewModel.swift */; }; 4807E8F51DB54F56B25E1C7E /* AppLockSetupSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D8C38663020DF2EB2D13F5E /* AppLockSetupSettingsScreenViewModel.swift */; };
48416BBEB8DDF3E4DED0EDB6 /* ElementCallServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FC8B21E86B137BE4E91F82A /* ElementCallServiceProtocol.swift */; }; 48416BBEB8DDF3E4DED0EDB6 /* ElementCallServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FC8B21E86B137BE4E91F82A /* ElementCallServiceProtocol.swift */; };
484202C5D50983442D24D061 /* AttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52BD6ED18E2EB61E28C340AD /* AttributedString.swift */; }; 484202C5D50983442D24D061 /* AttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52BD6ED18E2EB61E28C340AD /* AttributedString.swift */; };
@@ -482,7 +479,6 @@
55DF6DEEF2CEEF40F84B53B0 /* VoiceMessageRoomPlaybackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E3B41C36800DD4558D7BDA7 /* VoiceMessageRoomPlaybackView.swift */; }; 55DF6DEEF2CEEF40F84B53B0 /* VoiceMessageRoomPlaybackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E3B41C36800DD4558D7BDA7 /* VoiceMessageRoomPlaybackView.swift */; };
5618ED25F092DF5712003829 /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */; }; 5618ED25F092DF5712003829 /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */; };
562EFB9AB62B38830D9AA778 /* TimelineMediaFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 933B074F006F8E930DB98B4E /* TimelineMediaFrame.swift */; }; 562EFB9AB62B38830D9AA778 /* TimelineMediaFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 933B074F006F8E930DB98B4E /* TimelineMediaFrame.swift */; };
564BF06B3E93D6DD55F903B2 /* CreateRoomCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C618CA2B6C8758B06C88013C /* CreateRoomCoordinator.swift */; };
565868808A1DA565707394ED /* CurrentValuePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */; }; 565868808A1DA565707394ED /* CurrentValuePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */; };
56DACDD379A86A1F5DEFE7BE /* AuthenticationServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E75948AA1FE1D1A7809931F /* AuthenticationServiceProtocol.swift */; }; 56DACDD379A86A1F5DEFE7BE /* AuthenticationServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E75948AA1FE1D1A7809931F /* AuthenticationServiceProtocol.swift */; };
56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5530B2212862FA4BEFF2D /* HomeScreenViewModelProtocol.swift */; }; 56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5530B2212862FA4BEFF2D /* HomeScreenViewModelProtocol.swift */; };
@@ -883,6 +879,7 @@
9D79B94493FB32249F7E472F /* PlaceholderAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C705E605EF57C19DBE86FFA1 /* PlaceholderAvatarImage.swift */; }; 9D79B94493FB32249F7E472F /* PlaceholderAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C705E605EF57C19DBE86FFA1 /* PlaceholderAvatarImage.swift */; };
9D9690D2FD4CD26FF670620F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C75EF87651B00A176AB08E97 /* AppDelegate.swift */; }; 9D9690D2FD4CD26FF670620F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C75EF87651B00A176AB08E97 /* AppDelegate.swift */; };
9DB4B303ECC05F0F33582594 /* SpacesAnnouncementSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7DA8FFD31B18324CC04A823 /* SpacesAnnouncementSheetView.swift */; }; 9DB4B303ECC05F0F33582594 /* SpacesAnnouncementSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7DA8FFD31B18324CC04A823 /* SpacesAnnouncementSheetView.swift */; };
9DBF6524DFD8143A4D6A17F0 /* CreateRoomScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D432771B389EC486E7F90C6 /* CreateRoomScreenModels.swift */; };
9DD5AA10E85137140FEA86A3 /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */; }; 9DD5AA10E85137140FEA86A3 /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */; };
9DD84E014ADFB2DD813022D5 /* RoomDetailsEditScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00E5B2CBEF8F96424F095508 /* RoomDetailsEditScreenViewModelTests.swift */; }; 9DD84E014ADFB2DD813022D5 /* RoomDetailsEditScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00E5B2CBEF8F96424F095508 /* RoomDetailsEditScreenViewModelTests.swift */; };
9DE801D278AC34737467F937 /* VoiceMessageMediaManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 889DEDD63C68ABDA8AD29812 /* VoiceMessageMediaManagerProtocol.swift */; }; 9DE801D278AC34737467F937 /* VoiceMessageMediaManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 889DEDD63C68ABDA8AD29812 /* VoiceMessageMediaManagerProtocol.swift */; };
@@ -896,6 +893,7 @@
9FB41B0E8B2AA9B404E52C8B /* AppLockSetupBiometricsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CCC6C31102E1D8B9106DEDE /* AppLockSetupBiometricsScreenViewModelProtocol.swift */; }; 9FB41B0E8B2AA9B404E52C8B /* AppLockSetupBiometricsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CCC6C31102E1D8B9106DEDE /* AppLockSetupBiometricsScreenViewModelProtocol.swift */; };
9FBE1FB20171012260A32492 /* TimelineSenderAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D53FCCE44F96E0BC411A6CF0 /* TimelineSenderAvatarView.swift */; }; 9FBE1FB20171012260A32492 /* TimelineSenderAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D53FCCE44F96E0BC411A6CF0 /* TimelineSenderAvatarView.swift */; };
9FBF0078657CA4F2A9747E98 /* SpaceRoomListProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DB2A5FF1E68BA580A20D405 /* SpaceRoomListProxyMock.swift */; }; 9FBF0078657CA4F2A9747E98 /* SpaceRoomListProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DB2A5FF1E68BA580A20D405 /* SpaceRoomListProxyMock.swift */; };
9FC79DA30AE0E1502DAEBD51 /* StartChatFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 477A8C656B7B26E99C35927F /* StartChatFlowCoordinator.swift */; };
A009BDFB0A6816D4C392ADCB /* SettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AF715D4FD4710EBB637D661 /* SettingsScreenViewModelProtocol.swift */; }; A009BDFB0A6816D4C392ADCB /* SettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AF715D4FD4710EBB637D661 /* SettingsScreenViewModelProtocol.swift */; };
A021827B528F1EDC9101CA58 /* AppCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBC776F301D374A3298C69DA /* AppCoordinatorProtocol.swift */; }; A021827B528F1EDC9101CA58 /* AppCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBC776F301D374A3298C69DA /* AppCoordinatorProtocol.swift */; };
A0601810597769B81C2358AF /* EncryptionResetPasswordScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2B5274C1D3D2999D643786 /* EncryptionResetPasswordScreenViewModelProtocol.swift */; }; A0601810597769B81C2358AF /* EncryptionResetPasswordScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2B5274C1D3D2999D643786 /* EncryptionResetPasswordScreenViewModelProtocol.swift */; };
@@ -947,6 +945,7 @@
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 */; };
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 */; };
A7DB75E090542331F6668A23 /* CreateRoomScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF19027E7FFA5E63D148873A /* CreateRoomScreenViewModel.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 */; };
A851635B3255C6DC07034A12 /* RoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */; }; A851635B3255C6DC07034A12 /* RoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */; };
@@ -1080,7 +1079,6 @@
C280BD841DA2EB6B5BC84F84 /* MapTilerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1F2AAA3F0F2B72D2FFE4D0 /* MapTilerConfiguration.swift */; }; C280BD841DA2EB6B5BC84F84 /* MapTilerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1F2AAA3F0F2B72D2FFE4D0 /* MapTilerConfiguration.swift */; };
C2879369106A419A5071F1F8 /* VoiceMessageRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B0A96B8FE4849227945067 /* VoiceMessageRecorder.swift */; }; C2879369106A419A5071F1F8 /* VoiceMessageRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B0A96B8FE4849227945067 /* VoiceMessageRecorder.swift */; };
C292A8DA07E6DBD66E946383 /* RageshakeConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FC598338E7CF41107293AB5 /* RageshakeConfiguration.swift */; }; C292A8DA07E6DBD66E946383 /* RageshakeConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FC598338E7CF41107293AB5 /* RageshakeConfiguration.swift */; };
C32765D740C81AD4C42E8F50 /* CreateRoomFlowParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 935C2FB18EFB8EEE96B26330 /* CreateRoomFlowParameters.swift */; };
C3317EF833AB4060988DF098 /* SAS.strings in Resources */ = {isa = PBXBuildFile; fileRef = 135FC689EA39AE1D34153B58 /* SAS.strings */; }; C3317EF833AB4060988DF098 /* SAS.strings in Resources */ = {isa = PBXBuildFile; fileRef = 135FC689EA39AE1D34153B58 /* SAS.strings */; };
C3522917C0C367C403429EEC /* CoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B251F5B4511D1CA0BA8361FE /* CoordinatorProtocol.swift */; }; C3522917C0C367C403429EEC /* CoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B251F5B4511D1CA0BA8361FE /* CoordinatorProtocol.swift */; };
C3AFDF6349E54290AA31EC88 /* preview_video.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 45A4B934BA41D6C255900265 /* preview_video.jpg */; }; C3AFDF6349E54290AA31EC88 /* preview_video.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 45A4B934BA41D6C255900265 /* preview_video.jpg */; };
@@ -1148,6 +1146,7 @@
CE3B7FC34FB2C279AAA5EA01 /* AVMetadataMachineReadableCodeObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3339B1DDB1341E833D2555BC /* AVMetadataMachineReadableCodeObject.swift */; }; CE3B7FC34FB2C279AAA5EA01 /* AVMetadataMachineReadableCodeObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3339B1DDB1341E833D2555BC /* AVMetadataMachineReadableCodeObject.swift */; };
CE4B342F9DD747CF4BEDB5AB /* TestablePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43F773904F87FF5ADFE4DD1 /* TestablePreview.swift */; }; CE4B342F9DD747CF4BEDB5AB /* TestablePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43F773904F87FF5ADFE4DD1 /* TestablePreview.swift */; };
CE6F237360875D3D573FD0B2 /* RoomNotificationSettingsProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6B522BD637845AB9570B10 /* RoomNotificationSettingsProxy.swift */; }; CE6F237360875D3D573FD0B2 /* RoomNotificationSettingsProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6B522BD637845AB9570B10 /* RoomNotificationSettingsProxy.swift */; };
CE8296D4AD30DDC6D0C67A74 /* CreateRoomScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFD1CFB730C7417925264F17 /* CreateRoomScreen.swift */; };
CE9530A4CA661E090635C2F2 /* NotificationItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25F7FE40EF7490A7E09D7BE6 /* NotificationItemProxy.swift */; }; CE9530A4CA661E090635C2F2 /* NotificationItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25F7FE40EF7490A7E09D7BE6 /* NotificationItemProxy.swift */; };
CEAEA57B7665C8E790599A78 /* BlockedUsersScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 240610DF32F3213BEC5611D7 /* BlockedUsersScreenViewModelTests.swift */; }; CEAEA57B7665C8E790599A78 /* BlockedUsersScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 240610DF32F3213BEC5611D7 /* BlockedUsersScreenViewModelTests.swift */; };
CEB8FB1269DE20536608B957 /* LoginMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41FABA2B0AEF4389986495 /* LoginMode.swift */; }; CEB8FB1269DE20536608B957 /* LoginMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41FABA2B0AEF4389986495 /* LoginMode.swift */; };
@@ -1177,6 +1176,7 @@
D31B34B3902BC597593F3ABB /* preview_image.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 200626E8353AB2729444F991 /* preview_image.jpg */; }; D31B34B3902BC597593F3ABB /* preview_image.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 200626E8353AB2729444F991 /* preview_image.jpg */; };
D33AC79A50DFC26D2498DD28 /* FileRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5098DA7799946A61E34A2373 /* FileRoomTimelineItem.swift */; }; D33AC79A50DFC26D2498DD28 /* FileRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5098DA7799946A61E34A2373 /* FileRoomTimelineItem.swift */; };
D34E328E9E65904358248FDD /* GlobalSearchScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 436A0D98D372B17EAE9AA999 /* GlobalSearchScreenModels.swift */; }; D34E328E9E65904358248FDD /* GlobalSearchScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 436A0D98D372B17EAE9AA999 /* GlobalSearchScreenModels.swift */; };
D38E59C48BE5499A48D12031 /* CreateRoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AC8FCE224D4185F28636FF /* CreateRoomScreenCoordinator.swift */; };
D3FD96913D2B1AAA3149DAC7 /* CreateRoomViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69D42EE0102D2857933625DD /* CreateRoomViewModelTests.swift */; }; D3FD96913D2B1AAA3149DAC7 /* CreateRoomViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69D42EE0102D2857933625DD /* CreateRoomViewModelTests.swift */; };
D43F0503EF2CBC55272538FE /* SDKGeneratedMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F079B5DBD0D85FEA687AAE /* SDKGeneratedMocks.swift */; }; D43F0503EF2CBC55272538FE /* SDKGeneratedMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F079B5DBD0D85FEA687AAE /* SDKGeneratedMocks.swift */; };
D46C33F8B61B55F0C8C2D15F /* LocationRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B2AC540DE619B36832A5DB5 /* LocationRoomTimelineItem.swift */; }; D46C33F8B61B55F0C8C2D15F /* LocationRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B2AC540DE619B36832A5DB5 /* LocationRoomTimelineItem.swift */; };
@@ -1204,6 +1204,7 @@
DA10C99BA43A0F1E732F6274 /* LogLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2711E5996016ABD6EAAEB58A /* LogLevel.swift */; }; DA10C99BA43A0F1E732F6274 /* LogLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2711E5996016ABD6EAAEB58A /* LogLevel.swift */; };
DA7E867F5EAFF8E20B2EE3B6 /* SecureBackupScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3D16709ADD4F4BCC710B1E /* SecureBackupScreenModels.swift */; }; DA7E867F5EAFF8E20B2EE3B6 /* SecureBackupScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3D16709ADD4F4BCC710B1E /* SecureBackupScreenModels.swift */; };
DAF63A9CF9932CA8F6830F11 /* ShareExtensionModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAC01A97A43BE07B9E94E43 /* ShareExtensionModels.swift */; }; DAF63A9CF9932CA8F6830F11 /* ShareExtensionModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAC01A97A43BE07B9E94E43 /* ShareExtensionModels.swift */; };
DB01FDD050A43B72C5425549 /* CreateRoomScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC5E094DFE18C3AB7619D34 /* CreateRoomScreenViewModelProtocol.swift */; };
DB079D1929B5A5F52D207C83 /* RoomDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 466C71A0FED9BFF287613C82 /* RoomDetailsScreenModels.swift */; }; DB079D1929B5A5F52D207C83 /* RoomDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 466C71A0FED9BFF287613C82 /* RoomDetailsScreenModels.swift */; };
DB5200B87C4CE9DF0024AC4E /* SpaceServiceProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C57DB49B8426AA721BF85D83 /* SpaceServiceProxyProtocol.swift */; }; DB5200B87C4CE9DF0024AC4E /* SpaceServiceProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C57DB49B8426AA721BF85D83 /* SpaceServiceProxyProtocol.swift */; };
DB65401349C143DFF883E2B0 /* AnalyticsPromptScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C8EC6EA7EDFCE46710DA306 /* AnalyticsPromptScreenViewModel.swift */; }; DB65401349C143DFF883E2B0 /* AnalyticsPromptScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C8EC6EA7EDFCE46710DA306 /* AnalyticsPromptScreenViewModel.swift */; };
@@ -1379,7 +1380,6 @@
F8C87130FD999F7F1076208C /* RoomChangePermissionsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89AAEA70CFF3284920811941 /* RoomChangePermissionsScreen.swift */; }; F8C87130FD999F7F1076208C /* RoomChangePermissionsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89AAEA70CFF3284920811941 /* RoomChangePermissionsScreen.swift */; };
F8E725D42023ECA091349245 /* AudioRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57EAAF82432B0B53881CF826 /* AudioRoomTimelineItem.swift */; }; F8E725D42023ECA091349245 /* AudioRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57EAAF82432B0B53881CF826 /* AudioRoomTimelineItem.swift */; };
F8F47CE757EE656905F01F2C /* RoomRolesAndPermissionsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90DFF217B3D9D0941283278C /* RoomRolesAndPermissionsScreenViewModelProtocol.swift */; }; F8F47CE757EE656905F01F2C /* RoomRolesAndPermissionsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90DFF217B3D9D0941283278C /* RoomRolesAndPermissionsScreenViewModelProtocol.swift */; };
F9788AE0B9EA19F1190ED14F /* StartChatScreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B927A399A5418DA40A5CA15 /* StartChatScreenTests.swift */; };
F9842667B68DC6FA1F9ECCBB /* NSItemProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72EFC8C634469F9262659C7 /* NSItemProvider.swift */; }; F9842667B68DC6FA1F9ECCBB /* NSItemProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72EFC8C634469F9262659C7 /* NSItemProvider.swift */; };
F996259EAE68664BC345C197 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9DBE1C69F3A60D93B2203F /* Application.swift */; }; F996259EAE68664BC345C197 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9DBE1C69F3A60D93B2203F /* Application.swift */; };
F99FB21EFC6D99D247FE7CBE /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 19CD5B074D7DD44AF4C58BB6 /* SwiftState */; }; F99FB21EFC6D99D247FE7CBE /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 19CD5B074D7DD44AF4C58BB6 /* SwiftState */; };
@@ -1390,7 +1390,6 @@
FA71CD334F2D2289BEF0D749 /* SecureBackupRecoveryKeyScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A2FCA3D0F239B9E911B966B /* SecureBackupRecoveryKeyScreen.swift */; }; FA71CD334F2D2289BEF0D749 /* SecureBackupRecoveryKeyScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A2FCA3D0F239B9E911B966B /* SecureBackupRecoveryKeyScreen.swift */; };
FA9C427FFB11B1AA2DCC5602 /* RoomProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */; }; FA9C427FFB11B1AA2DCC5602 /* RoomProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */; };
FB0A9D06FC9122E37992D962 /* LayoutDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14D83B2B7CD5501A0089EFC /* LayoutDirection.swift */; }; FB0A9D06FC9122E37992D962 /* LayoutDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14D83B2B7CD5501A0089EFC /* LayoutDirection.swift */; };
FB53CD9B74A15B3B94F9F788 /* CreateRoomModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B849D2FF2CC12BA411A1651 /* CreateRoomModels.swift */; };
FB595EC9C00AB32F39034055 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A37E2FACFD041CE466223CD /* SceneDelegate.swift */; }; FB595EC9C00AB32F39034055 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A37E2FACFD041CE466223CD /* SceneDelegate.swift */; };
FBD402E3170EB1ED0D1AA672 /* EncryptionKeyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2355398E4A55DA5A89128AD1 /* EncryptionKeyProvider.swift */; }; FBD402E3170EB1ED0D1AA672 /* EncryptionKeyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2355398E4A55DA5A89128AD1 /* EncryptionKeyProvider.swift */; };
FBF09B6C900415800DDF2A21 /* EmojiProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C113E0CB7E15E9765B1817A /* EmojiProvider.swift */; }; FBF09B6C900415800DDF2A21 /* EmojiProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C113E0CB7E15E9765B1817A /* EmojiProvider.swift */; };
@@ -1517,6 +1516,7 @@
03277E40D0E0DE0712021A71 /* ServerConfirmationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenCoordinator.swift; sourceTree = "<group>"; }; 03277E40D0E0DE0712021A71 /* ServerConfirmationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenCoordinator.swift; sourceTree = "<group>"; };
033DB41C51865A2E83174E87 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; }; 033DB41C51865A2E83174E87 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
035177BCD8E8308B098AC3C2 /* WindowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowManager.swift; sourceTree = "<group>"; }; 035177BCD8E8308B098AC3C2 /* WindowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowManager.swift; sourceTree = "<group>"; };
0367E0AF1163F42292D788BD /* StartChatTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatTests.swift; sourceTree = "<group>"; };
0376C429FAB1687C3D905F3E /* MockCoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCoder.swift; sourceTree = "<group>"; }; 0376C429FAB1687C3D905F3E /* MockCoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCoder.swift; sourceTree = "<group>"; };
03DD998E523D4EC93C7ED703 /* RoomNotificationSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; }; 03DD998E523D4EC93C7ED703 /* RoomNotificationSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
03FABD73FD8086EFAB699F42 /* MediaUploadPreviewScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModelTests.swift; sourceTree = "<group>"; }; 03FABD73FD8086EFAB699F42 /* MediaUploadPreviewScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModelTests.swift; sourceTree = "<group>"; };
@@ -1556,6 +1556,7 @@
0A459AE4B6566B2FA99E86B2 /* TimelineItemBubbledStylerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemBubbledStylerView.swift; sourceTree = "<group>"; }; 0A459AE4B6566B2FA99E86B2 /* TimelineItemBubbledStylerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemBubbledStylerView.swift; sourceTree = "<group>"; };
0A81FD0C60175FA081EB19AD /* EventTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventTimelineItem.swift; sourceTree = "<group>"; }; 0A81FD0C60175FA081EB19AD /* EventTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventTimelineItem.swift; sourceTree = "<group>"; };
0AB7A0C06CB527A1095DEB33 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = da; path = da.lproj/Localizable.stringsdict; sourceTree = "<group>"; }; 0AB7A0C06CB527A1095DEB33 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = da; path = da.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
0AC5E094DFE18C3AB7619D34 /* CreateRoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomScreenViewModelProtocol.swift; sourceTree = "<group>"; };
0B0E0B55E2EE75AF67029924 /* SwipeToReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeToReplyView.swift; sourceTree = "<group>"; }; 0B0E0B55E2EE75AF67029924 /* SwipeToReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeToReplyView.swift; sourceTree = "<group>"; };
0B32BBA8887BD7A5C4ECF16F /* RoomModerationRole.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomModerationRole.swift; sourceTree = "<group>"; }; 0B32BBA8887BD7A5C4ECF16F /* RoomModerationRole.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomModerationRole.swift; sourceTree = "<group>"; };
0B987FC3FDBAA0E1C5AA235C /* PaginationIndicatorRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationIndicatorRoomTimelineItem.swift; sourceTree = "<group>"; }; 0B987FC3FDBAA0E1C5AA235C /* PaginationIndicatorRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationIndicatorRoomTimelineItem.swift; sourceTree = "<group>"; };
@@ -1569,6 +1570,7 @@
0CB569EAA5017B5B23970655 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Localizable.strings; sourceTree = "<group>"; }; 0CB569EAA5017B5B23970655 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Localizable.strings; sourceTree = "<group>"; };
0CCC6C31102E1D8B9106DEDE /* AppLockSetupBiometricsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupBiometricsScreenViewModelProtocol.swift; sourceTree = "<group>"; }; 0CCC6C31102E1D8B9106DEDE /* AppLockSetupBiometricsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupBiometricsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
0D0B159AFFBBD8ECFD0E37FA /* LoginScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenModels.swift; sourceTree = "<group>"; }; 0D0B159AFFBBD8ECFD0E37FA /* LoginScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenModels.swift; sourceTree = "<group>"; };
0D432771B389EC486E7F90C6 /* CreateRoomScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomScreenModels.swift; sourceTree = "<group>"; };
0D879FC4E881E748BB9B34DC /* RoomChangePermissionsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangePermissionsScreenCoordinator.swift; sourceTree = "<group>"; }; 0D879FC4E881E748BB9B34DC /* RoomChangePermissionsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangePermissionsScreenCoordinator.swift; sourceTree = "<group>"; };
0D8F620C8B314840D8602E3F /* NSE.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = NSE.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 0D8F620C8B314840D8602E3F /* NSE.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = NSE.appex; sourceTree = BUILT_PRODUCTS_DIR; };
0DBB08A95EFA668F2CF27211 /* AppLockSetupFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupFlowCoordinator.swift; sourceTree = "<group>"; }; 0DBB08A95EFA668F2CF27211 /* AppLockSetupFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupFlowCoordinator.swift; sourceTree = "<group>"; };
@@ -1615,7 +1617,6 @@
1562EAF6231151A675BED7A9 /* RoomDirectorySearchScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchScreenCoordinator.swift; sourceTree = "<group>"; }; 1562EAF6231151A675BED7A9 /* RoomDirectorySearchScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchScreenCoordinator.swift; sourceTree = "<group>"; };
15748C254911E3654C93B0ED /* MentionBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionBuilder.swift; sourceTree = "<group>"; }; 15748C254911E3654C93B0ED /* MentionBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionBuilder.swift; sourceTree = "<group>"; };
1575947B7A6FE08C57FE5EE4 /* NetworkMonitorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitorProtocol.swift; sourceTree = "<group>"; }; 1575947B7A6FE08C57FE5EE4 /* NetworkMonitorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitorProtocol.swift; sourceTree = "<group>"; };
15A657D96779D1DEB8EF1327 /* CreateRoomViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomViewModel.swift; sourceTree = "<group>"; };
161CD412E75F4086F422AE39 /* SessionVerificationScreenStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenStateMachine.swift; sourceTree = "<group>"; }; 161CD412E75F4086F422AE39 /* SessionVerificationScreenStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenStateMachine.swift; sourceTree = "<group>"; };
1627F2D56477BD331F6D732C /* RoomHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomHeaderView.swift; sourceTree = "<group>"; }; 1627F2D56477BD331F6D732C /* RoomHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomHeaderView.swift; sourceTree = "<group>"; };
166D45E1861A73B232109843 /* RoomDetailsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenViewModelTests.swift; sourceTree = "<group>"; }; 166D45E1861A73B232109843 /* RoomDetailsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenViewModelTests.swift; sourceTree = "<group>"; };
@@ -1686,7 +1687,6 @@
218AB05B4E3889731959C5F1 /* EventBasedTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBasedTimelineItemProtocol.swift; sourceTree = "<group>"; }; 218AB05B4E3889731959C5F1 /* EventBasedTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBasedTimelineItemProtocol.swift; sourceTree = "<group>"; };
21BA866267F84BF4350B0CB7 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "pt-BR"; path = "pt-BR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; }; 21BA866267F84BF4350B0CB7 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "pt-BR"; path = "pt-BR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
21DD8599815136EFF5B73F38 /* UserFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserFlowTests.swift; sourceTree = "<group>"; }; 21DD8599815136EFF5B73F38 /* UserFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserFlowTests.swift; sourceTree = "<group>"; };
2214C32EA81FA9168D923D4C /* CreateRoomScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomScreenTests.swift; sourceTree = "<group>"; };
226A56746AD99ABDA97189E1 /* SpaceServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceServiceProxy.swift; sourceTree = "<group>"; }; 226A56746AD99ABDA97189E1 /* SpaceServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceServiceProxy.swift; sourceTree = "<group>"; };
22730A30C50AC2E3D5BA8642 /* InviteUsersScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenViewModelProtocol.swift; sourceTree = "<group>"; }; 22730A30C50AC2E3D5BA8642 /* InviteUsersScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenViewModelProtocol.swift; sourceTree = "<group>"; };
227AC5D71A4CE43512062243 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; }; 227AC5D71A4CE43512062243 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; };
@@ -1786,7 +1786,6 @@
3339B1DDB1341E833D2555BC /* AVMetadataMachineReadableCodeObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVMetadataMachineReadableCodeObject.swift; sourceTree = "<group>"; }; 3339B1DDB1341E833D2555BC /* AVMetadataMachineReadableCodeObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVMetadataMachineReadableCodeObject.swift; sourceTree = "<group>"; };
33AE897D86784CCA5E4E9227 /* ElementCallService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallService.swift; sourceTree = "<group>"; }; 33AE897D86784CCA5E4E9227 /* ElementCallService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallService.swift; sourceTree = "<group>"; };
33E49C5C6F802B4D94CA78D1 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = "<group>"; }; 33E49C5C6F802B4D94CA78D1 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = "<group>"; };
340179A0FC1AD4AEDA7FC134 /* CreateRoomViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomViewModelProtocol.swift; sourceTree = "<group>"; };
342BEBC3C5FC3F9943C41C4C /* TemplateScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenViewModelProtocol.swift; sourceTree = "<group>"; }; 342BEBC3C5FC3F9943C41C4C /* TemplateScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenViewModelProtocol.swift; sourceTree = "<group>"; };
345172AD4377E83A44BD864F /* MessageComposerTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageComposerTextField.swift; sourceTree = "<group>"; }; 345172AD4377E83A44BD864F /* MessageComposerTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageComposerTextField.swift; sourceTree = "<group>"; };
347D708104CCEF771428C9A3 /* PollFormScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollFormScreenViewModelTests.swift; sourceTree = "<group>"; }; 347D708104CCEF771428C9A3 /* PollFormScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollFormScreenViewModelTests.swift; sourceTree = "<group>"; };
@@ -1817,7 +1816,6 @@
3AB34956C87731AB094DB33A /* URLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLTests.swift; sourceTree = "<group>"; }; 3AB34956C87731AB094DB33A /* URLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLTests.swift; sourceTree = "<group>"; };
3AD253E7EFF88F308D644272 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/SAS.strings"; sourceTree = "<group>"; }; 3AD253E7EFF88F308D644272 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/SAS.strings"; sourceTree = "<group>"; };
3B5E97E9615A158C76B2AB77 /* DateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTests.swift; sourceTree = "<group>"; }; 3B5E97E9615A158C76B2AB77 /* DateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTests.swift; sourceTree = "<group>"; };
3B927A399A5418DA40A5CA15 /* StartChatScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreenTests.swift; sourceTree = "<group>"; };
3BAC027034248429A438886B /* AppMediatorMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppMediatorMock.swift; sourceTree = "<group>"; }; 3BAC027034248429A438886B /* AppMediatorMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppMediatorMock.swift; sourceTree = "<group>"; };
3BC1B7CB061C9865B2B91B56 /* QRCodeLoginScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeLoginScreenViewModel.swift; sourceTree = "<group>"; }; 3BC1B7CB061C9865B2B91B56 /* QRCodeLoginScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeLoginScreenViewModel.swift; sourceTree = "<group>"; };
3BDCCD2F6B405C14B9BCE94E /* JoinRoomScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRoomScreenCoordinator.swift; sourceTree = "<group>"; }; 3BDCCD2F6B405C14B9BCE94E /* JoinRoomScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRoomScreenCoordinator.swift; sourceTree = "<group>"; };
@@ -1894,6 +1892,7 @@
471BB7276C97AF60B3A5463B /* RoomDirectorySearchProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchProxy.swift; sourceTree = "<group>"; }; 471BB7276C97AF60B3A5463B /* RoomDirectorySearchProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchProxy.swift; sourceTree = "<group>"; };
475D47D0BFE961B02BAC5D49 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = id; path = id.lproj/Localizable.stringsdict; sourceTree = "<group>"; }; 475D47D0BFE961B02BAC5D49 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = id; path = id.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
475EB595D7527E9A8A14043E /* uz */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uz; path = uz.lproj/Localizable.strings; sourceTree = "<group>"; }; 475EB595D7527E9A8A14043E /* uz */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uz; path = uz.lproj/Localizable.strings; sourceTree = "<group>"; };
477A8C656B7B26E99C35927F /* StartChatFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatFlowCoordinator.swift; sourceTree = "<group>"; };
47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenModels.swift; sourceTree = "<group>"; }; 47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenModels.swift; sourceTree = "<group>"; };
47EBB5D698CE9A25BB553A2D /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = "<group>"; }; 47EBB5D698CE9A25BB553A2D /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = "<group>"; };
47F29139BC2A804CE5E0757E /* MediaUploadPreviewScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModel.swift; sourceTree = "<group>"; }; 47F29139BC2A804CE5E0757E /* MediaUploadPreviewScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModel.swift; sourceTree = "<group>"; };
@@ -2028,6 +2027,7 @@
646B50583A2CE6DA67F7739A /* SpaceScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceScreen.swift; sourceTree = "<group>"; }; 646B50583A2CE6DA67F7739A /* SpaceScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceScreen.swift; sourceTree = "<group>"; };
648DD1C10E4957CB791FE0B8 /* OverridableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverridableAvatarImage.swift; sourceTree = "<group>"; }; 648DD1C10E4957CB791FE0B8 /* OverridableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverridableAvatarImage.swift; sourceTree = "<group>"; };
6493AC9979CEB1410302BFE3 /* RoomDetailsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenCoordinator.swift; sourceTree = "<group>"; }; 6493AC9979CEB1410302BFE3 /* RoomDetailsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenCoordinator.swift; sourceTree = "<group>"; };
64AC8FCE224D4185F28636FF /* CreateRoomScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomScreenCoordinator.swift; sourceTree = "<group>"; };
64F49FB9EE2913234F06CE68 /* MediaPickerScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPickerScreenCoordinator.swift; sourceTree = "<group>"; }; 64F49FB9EE2913234F06CE68 /* MediaPickerScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPickerScreenCoordinator.swift; sourceTree = "<group>"; };
6509708F54FC883604DFDC95 /* TimelineViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineViewModelTests.swift; sourceTree = "<group>"; }; 6509708F54FC883604DFDC95 /* TimelineViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineViewModelTests.swift; sourceTree = "<group>"; };
653610CB5F9776EAAAB98155 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fr; path = fr.lproj/Localizable.stringsdict; sourceTree = "<group>"; }; 653610CB5F9776EAAAB98155 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fr; path = fr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
@@ -2137,7 +2137,6 @@
7B19B2BCC779ED934E0BBC2A /* AudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayer.swift; sourceTree = "<group>"; }; 7B19B2BCC779ED934E0BBC2A /* AudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayer.swift; sourceTree = "<group>"; };
7B25F959A434BB9923A3223F /* ExpiringTaskRunner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpiringTaskRunner.swift; sourceTree = "<group>"; }; 7B25F959A434BB9923A3223F /* ExpiringTaskRunner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpiringTaskRunner.swift; sourceTree = "<group>"; };
7B3D16709ADD4F4BCC710B1E /* SecureBackupScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupScreenModels.swift; sourceTree = "<group>"; }; 7B3D16709ADD4F4BCC710B1E /* SecureBackupScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupScreenModels.swift; sourceTree = "<group>"; };
7B849D2FF2CC12BA411A1651 /* CreateRoomModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomModels.swift; sourceTree = "<group>"; };
7B9FCA1CFD07B8CF9BD21266 /* FlowCoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowCoordinatorProtocol.swift; sourceTree = "<group>"; }; 7B9FCA1CFD07B8CF9BD21266 /* FlowCoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowCoordinatorProtocol.swift; sourceTree = "<group>"; };
7BBADF8010C813D905C172CE /* SecurityAndPrivacyScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityAndPrivacyScreenModels.swift; sourceTree = "<group>"; }; 7BBADF8010C813D905C172CE /* SecurityAndPrivacyScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityAndPrivacyScreenModels.swift; sourceTree = "<group>"; };
7BD5523BDEDB247E29228476 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; }; 7BD5523BDEDB247E29228476 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
@@ -2272,7 +2271,6 @@
933B074F006F8E930DB98B4E /* TimelineMediaFrame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMediaFrame.swift; sourceTree = "<group>"; }; 933B074F006F8E930DB98B4E /* TimelineMediaFrame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMediaFrame.swift; sourceTree = "<group>"; };
9342F5D6729627B6393AF853 /* ServerConfirmationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenModels.swift; sourceTree = "<group>"; }; 9342F5D6729627B6393AF853 /* ServerConfirmationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenModels.swift; sourceTree = "<group>"; };
9349F590E35CE514A71E6764 /* LoginHomeserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginHomeserver.swift; sourceTree = "<group>"; }; 9349F590E35CE514A71E6764 /* LoginHomeserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginHomeserver.swift; sourceTree = "<group>"; };
935C2FB18EFB8EEE96B26330 /* CreateRoomFlowParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomFlowParameters.swift; sourceTree = "<group>"; };
93C713D124FE915ABF47A6B7 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = "<group>"; }; 93C713D124FE915ABF47A6B7 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = "<group>"; };
93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModelTests.swift; sourceTree = "<group>"; }; 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModelTests.swift; sourceTree = "<group>"; };
93E1FF0DFBB3768F79FDBF6D /* AVMetadataMachineReadableCodeObjectExtensionsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVMetadataMachineReadableCodeObjectExtensionsTest.swift; sourceTree = "<group>"; }; 93E1FF0DFBB3768F79FDBF6D /* AVMetadataMachineReadableCodeObjectExtensionsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVMetadataMachineReadableCodeObjectExtensionsTest.swift; sourceTree = "<group>"; };
@@ -2547,7 +2545,6 @@
C5AEB5907E24092D741718AF /* ResolveVerifiedUserSendFailureScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolveVerifiedUserSendFailureScreenCoordinator.swift; sourceTree = "<group>"; }; C5AEB5907E24092D741718AF /* ResolveVerifiedUserSendFailureScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolveVerifiedUserSendFailureScreenCoordinator.swift; sourceTree = "<group>"; };
C5F06F2F09B2EDD067DC2174 /* NotificationSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreen.swift; sourceTree = "<group>"; }; C5F06F2F09B2EDD067DC2174 /* NotificationSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreen.swift; sourceTree = "<group>"; };
C616D90B1E2F033CAA325439 /* StaticLocationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLocationScreenViewModelProtocol.swift; sourceTree = "<group>"; }; C616D90B1E2F033CAA325439 /* StaticLocationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLocationScreenViewModelProtocol.swift; sourceTree = "<group>"; };
C618CA2B6C8758B06C88013C /* CreateRoomCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomCoordinator.swift; sourceTree = "<group>"; };
C687844F60BFF532D49A994C /* AnalyticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsTests.swift; sourceTree = "<group>"; }; C687844F60BFF532D49A994C /* AnalyticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsTests.swift; sourceTree = "<group>"; };
C6A9F49B3EE59147AF2F70BB /* SeparatorRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineItem.swift; sourceTree = "<group>"; }; C6A9F49B3EE59147AF2F70BB /* SeparatorRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineItem.swift; sourceTree = "<group>"; };
C705E605EF57C19DBE86FFA1 /* PlaceholderAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderAvatarImage.swift; sourceTree = "<group>"; }; C705E605EF57C19DBE86FFA1 /* PlaceholderAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderAvatarImage.swift; sourceTree = "<group>"; };
@@ -2592,6 +2589,7 @@
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; 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>"; }; 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>"; }; 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>"; };
@@ -2766,6 +2764,7 @@
EF1593DD87F974F8509BB619 /* ElementAnimations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementAnimations.swift; sourceTree = "<group>"; }; EF1593DD87F974F8509BB619 /* ElementAnimations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementAnimations.swift; sourceTree = "<group>"; };
EF36FC3DE25B20B7FA91F1FD /* SpaceServiceProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceServiceProxyMock.swift; sourceTree = "<group>"; }; EF36FC3DE25B20B7FA91F1FD /* SpaceServiceProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceServiceProxyMock.swift; sourceTree = "<group>"; };
EF98A02DED04075F7CF0C721 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = "<group>"; }; EF98A02DED04075F7CF0C721 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = "<group>"; };
EFD1CFB730C7417925264F17 /* CreateRoomScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomScreen.swift; sourceTree = "<group>"; };
EFF7BF82A950B91BC5469E91 /* ViewFrameReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewFrameReader.swift; sourceTree = "<group>"; }; EFF7BF82A950B91BC5469E91 /* ViewFrameReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewFrameReader.swift; sourceTree = "<group>"; };
EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportServiceTests.swift; sourceTree = "<group>"; }; EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportServiceTests.swift; sourceTree = "<group>"; };
F0096BC5DA86AF6B6E5742AC /* RoomPermissionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPermissionsTests.swift; sourceTree = "<group>"; }; F0096BC5DA86AF6B6E5742AC /* RoomPermissionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPermissionsTests.swift; sourceTree = "<group>"; };
@@ -2819,7 +2818,6 @@
FA723686F23EF45E2B398FBC /* TestablePreviewsDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestablePreviewsDictionary.swift; sourceTree = "<group>"; }; FA723686F23EF45E2B398FBC /* TestablePreviewsDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestablePreviewsDictionary.swift; sourceTree = "<group>"; };
FA7BB497B2F539C17E88F6B7 /* NotificationSettingsEditScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenViewModelProtocol.swift; sourceTree = "<group>"; }; FA7BB497B2F539C17E88F6B7 /* NotificationSettingsEditScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenViewModelProtocol.swift; sourceTree = "<group>"; };
FABAC5C4373B0EC24D399663 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/SAS.strings"; sourceTree = "<group>"; }; FABAC5C4373B0EC24D399663 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/SAS.strings"; sourceTree = "<group>"; };
FB0D6CB491777E7FC6B5BA12 /* CreateRoomScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomScreen.swift; sourceTree = "<group>"; };
FB7BAD55A4E2B8E5828CD64C /* SoftLogoutScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutScreenViewModel.swift; sourceTree = "<group>"; }; FB7BAD55A4E2B8E5828CD64C /* SoftLogoutScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutScreenViewModel.swift; sourceTree = "<group>"; };
FB9EABCA9348DFA27439A809 /* WaveformCursorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaveformCursorView.swift; sourceTree = "<group>"; }; FB9EABCA9348DFA27439A809 /* WaveformCursorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaveformCursorView.swift; sourceTree = "<group>"; };
FBB0328F2887BF0A65BC5D49 /* NotificationSettingsEditScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreen.swift; sourceTree = "<group>"; }; FBB0328F2887BF0A65BC5D49 /* NotificationSettingsEditScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreen.swift; sourceTree = "<group>"; };
@@ -3055,7 +3053,6 @@
0ED3F5C21537519389C07644 /* BugReport */, 0ED3F5C21537519389C07644 /* BugReport */,
8039515BAA53B7C3275AC64A /* Client */, 8039515BAA53B7C3275AC64A /* Client */,
8B5E91450E85A9689931B221 /* ComposerDraft */, 8B5E91450E85A9689931B221 /* ComposerDraft */,
8C3BAE06B336D97DABBE2509 /* CreateRoom */,
92E99C57D7F92ED16F73282C /* ElementCall */, 92E99C57D7F92ED16F73282C /* ElementCall */,
39557ADF21345E18F3865B9E /* Emojis */, 39557ADF21345E18F3865B9E /* Emojis */,
CA555F7C7CA382ACACF0D82B /* Keychain */, CA555F7C7CA382ACACF0D82B /* Keychain */,
@@ -4305,6 +4302,7 @@
5A4EF5724C0F894911AF7811 /* SpaceExplorerFlowCoordinator.swift */, 5A4EF5724C0F894911AF7811 /* SpaceExplorerFlowCoordinator.swift */,
EDDE826EAB1BAB80C1104980 /* SpaceFlowCoordinator.swift */, EDDE826EAB1BAB80C1104980 /* SpaceFlowCoordinator.swift */,
BDE3EDEA7E64D68FEB828F83 /* SpaceSettingsFlowCoordinator.swift */, BDE3EDEA7E64D68FEB828F83 /* SpaceSettingsFlowCoordinator.swift */,
477A8C656B7B26E99C35927F /* StartChatFlowCoordinator.swift */,
C99FDEEB71173C4C6FA2734C /* UserSessionFlowCoordinator.swift */, C99FDEEB71173C4C6FA2734C /* UserSessionFlowCoordinator.swift */,
); );
path = FlowCoordinators; path = FlowCoordinators;
@@ -4962,6 +4960,18 @@
path = View; path = View;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
821EB0D1C0019E3C7BBAEDBB /* CreateRoomScreen */ = {
isa = PBXGroup;
children = (
64AC8FCE224D4185F28636FF /* CreateRoomScreenCoordinator.swift */,
0D432771B389EC486E7F90C6 /* CreateRoomScreenModels.swift */,
CF19027E7FFA5E63D148873A /* CreateRoomScreenViewModel.swift */,
0AC5E094DFE18C3AB7619D34 /* CreateRoomScreenViewModelProtocol.swift */,
A7694BCE812C4D7B2B1B42DF /* View */,
);
path = CreateRoomScreen;
sourceTree = "<group>";
};
823ED0EC3F1B6CF47D284011 /* Tools */ = { 823ED0EC3F1B6CF47D284011 /* Tools */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -5117,14 +5127,6 @@
path = ComposerDraft; path = ComposerDraft;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
8C3BAE06B336D97DABBE2509 /* CreateRoom */ = {
isa = PBXGroup;
children = (
935C2FB18EFB8EEE96B26330 /* CreateRoomFlowParameters.swift */,
);
path = CreateRoom;
sourceTree = "<group>";
};
8F074E22FD93E64211971845 /* Onboarding */ = { 8F074E22FD93E64211971845 /* Onboarding */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -5252,7 +5254,6 @@
E8495F37D6245AD0CFA1F60B /* AppLockTests.swift */, E8495F37D6245AD0CFA1F60B /* AppLockTests.swift */,
37F46CC4FD89ECF4CF26391A /* AuthenticationFlowCoordinatorTests.swift */, 37F46CC4FD89ECF4CF26391A /* AuthenticationFlowCoordinatorTests.swift */,
27DF257F5D968E5DD719583C /* BugReportTests.swift */, 27DF257F5D968E5DD719583C /* BugReportTests.swift */,
2214C32EA81FA9168D923D4C /* CreateRoomScreenTests.swift */,
89BB11A792EF6F70B95B467E /* EncryptionResetTests.swift */, 89BB11A792EF6F70B95B467E /* EncryptionResetTests.swift */,
57AD14D3ADADE8F6A10F9E88 /* EncryptionSettingsTests.swift */, 57AD14D3ADADE8F6A10F9E88 /* EncryptionSettingsTests.swift */,
DCDAB580109C09A6AA97AF7E /* PollFormScreenTests.swift */, DCDAB580109C09A6AA97AF7E /* PollFormScreenTests.swift */,
@@ -5261,7 +5262,7 @@
D97A4E73EA97CA08D2BB9806 /* RoomScreenTests.swift */, D97A4E73EA97CA08D2BB9806 /* RoomScreenTests.swift */,
6640DB5B9171D163E6742639 /* ServerSelectionTests.swift */, 6640DB5B9171D163E6742639 /* ServerSelectionTests.swift */,
D8AA084E10B80D64449C02A9 /* SessionVerificationTests.swift */, D8AA084E10B80D64449C02A9 /* SessionVerificationTests.swift */,
3B927A399A5418DA40A5CA15 /* StartChatScreenTests.swift */, 0367E0AF1163F42292D788BD /* StartChatTests.swift */,
F899D02CF26EA7675EEBE74C /* UserSessionScreenTests.swift */, F899D02CF26EA7675EEBE74C /* UserSessionScreenTests.swift */,
); );
path = Sources; path = Sources;
@@ -5522,6 +5523,14 @@
path = View; path = View;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
A7694BCE812C4D7B2B1B42DF /* View */ = {
isa = PBXGroup;
children = (
EFD1CFB730C7417925264F17 /* CreateRoomScreen.swift */,
);
path = View;
sourceTree = "<group>";
};
A78C2592419CA4C76FBA8FD2 /* Application */ = { A78C2592419CA4C76FBA8FD2 /* Application */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -5839,18 +5848,6 @@
path = MapLibre; path = MapLibre;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
C18958141C8ED6D778F779A4 /* CreateRoom */ = {
isa = PBXGroup;
children = (
C618CA2B6C8758B06C88013C /* CreateRoomCoordinator.swift */,
7B849D2FF2CC12BA411A1651 /* CreateRoomModels.swift */,
15A657D96779D1DEB8EF1327 /* CreateRoomViewModel.swift */,
340179A0FC1AD4AEDA7FC134 /* CreateRoomViewModelProtocol.swift */,
F84100ED0C09031BAB7BB77E /* View */,
);
path = CreateRoom;
sourceTree = "<group>";
};
C1CD278862878F9545608040 /* SessionVerificationScreen */ = { C1CD278862878F9545608040 /* SessionVerificationScreen */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -6233,7 +6230,7 @@
53FB148CD26AFB6A5B9E20B3 /* BugReportScreen */, 53FB148CD26AFB6A5B9E20B3 /* BugReportScreen */,
1185EECDD07495D65AC84AFC /* CallScreen */, 1185EECDD07495D65AC84AFC /* CallScreen */,
90DC2E28718955ED87AD1456 /* CreatePollScreen */, 90DC2E28718955ED87AD1456 /* CreatePollScreen */,
C18958141C8ED6D778F779A4 /* CreateRoom */, 821EB0D1C0019E3C7BBAEDBB /* CreateRoomScreen */,
3E1CCC4B607946CE90B4A827 /* DeclineAndBlockScreen */, 3E1CCC4B607946CE90B4A827 /* DeclineAndBlockScreen */,
45F2BCFD6E9A6F040CC20582 /* EditRoomAddressScreen */, 45F2BCFD6E9A6F040CC20582 /* EditRoomAddressScreen */,
F5A65D1D3B83593598DC278D /* EmojiPickerScreen */, F5A65D1D3B83593598DC278D /* EmojiPickerScreen */,
@@ -6486,14 +6483,6 @@
path = IdentityConfirmedScreen; path = IdentityConfirmedScreen;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
F84100ED0C09031BAB7BB77E /* View */ = {
isa = PBXGroup;
children = (
FB0D6CB491777E7FC6B5BA12 /* CreateRoomScreen.swift */,
);
path = View;
sourceTree = "<group>";
};
FA1D480A302295CFC3582543 /* View */ = { FA1D480A302295CFC3582543 /* View */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -7718,12 +7707,11 @@
EA6613B29BA671F39CE1B1D2 /* ConfirmationDialog.swift in Sources */, EA6613B29BA671F39CE1B1D2 /* ConfirmationDialog.swift in Sources */,
AC7AA215D60FBC307F984028 /* Consumable.swift in Sources */, AC7AA215D60FBC307F984028 /* Consumable.swift in Sources */,
C3522917C0C367C403429EEC /* CoordinatorProtocol.swift in Sources */, C3522917C0C367C403429EEC /* CoordinatorProtocol.swift in Sources */,
564BF06B3E93D6DD55F903B2 /* CreateRoomCoordinator.swift in Sources */, CE8296D4AD30DDC6D0C67A74 /* CreateRoomScreen.swift in Sources */,
C32765D740C81AD4C42E8F50 /* CreateRoomFlowParameters.swift in Sources */, D38E59C48BE5499A48D12031 /* CreateRoomScreenCoordinator.swift in Sources */,
FB53CD9B74A15B3B94F9F788 /* CreateRoomModels.swift in Sources */, 9DBF6524DFD8143A4D6A17F0 /* CreateRoomScreenModels.swift in Sources */,
292827744227DF61C930BDDB /* CreateRoomScreen.swift in Sources */, A7DB75E090542331F6668A23 /* CreateRoomScreenViewModel.swift in Sources */,
0BE4D5CBF86956410F071F91 /* CreateRoomViewModel.swift in Sources */, DB01FDD050A43B72C5425549 /* CreateRoomScreenViewModelProtocol.swift in Sources */,
4799A852132F1744E2825994 /* CreateRoomViewModelProtocol.swift in Sources */,
565868808A1DA565707394ED /* CurrentValuePublisher.swift in Sources */, 565868808A1DA565707394ED /* CurrentValuePublisher.swift in Sources */,
A722F426FD81FC67706BB1E0 /* CustomLayoutLabelStyle.swift in Sources */, A722F426FD81FC67706BB1E0 /* CustomLayoutLabelStyle.swift in Sources */,
12C867E85E6D12EEDFD0B127 /* CustomStringConvertible.swift in Sources */, 12C867E85E6D12EEDFD0B127 /* CustomStringConvertible.swift in Sources */,
@@ -8346,6 +8334,7 @@
9DB4B303ECC05F0F33582594 /* SpacesAnnouncementSheetView.swift in Sources */, 9DB4B303ECC05F0F33582594 /* SpacesAnnouncementSheetView.swift in Sources */,
DF004A5B2EABBD0574D06A04 /* SplashScreenCoordinator.swift in Sources */, DF004A5B2EABBD0574D06A04 /* SplashScreenCoordinator.swift in Sources */,
E1C67E5D9E22135A8FEBBD60 /* StackedAvatarsView.swift in Sources */, E1C67E5D9E22135A8FEBBD60 /* StackedAvatarsView.swift in Sources */,
9FC79DA30AE0E1502DAEBD51 /* StartChatFlowCoordinator.swift in Sources */,
3DAF325D8AE461F7CDB282BD /* StartChatScreen.swift in Sources */, 3DAF325D8AE461F7CDB282BD /* StartChatScreen.swift in Sources */,
6CD61FAF03E8986523C2ABB8 /* StartChatScreenCoordinator.swift in Sources */, 6CD61FAF03E8986523C2ABB8 /* StartChatScreenCoordinator.swift in Sources */,
C051475DFF4C8EBDDF4DC8E4 /* StartChatScreenModels.swift in Sources */, C051475DFF4C8EBDDF4DC8E4 /* StartChatScreenModels.swift in Sources */,
@@ -8541,7 +8530,6 @@
CB07184D37D5D65327A5A693 /* AuthenticationFlowCoordinatorTests.swift in Sources */, CB07184D37D5D65327A5A693 /* AuthenticationFlowCoordinatorTests.swift in Sources */,
9EF72884B74BC39B01640098 /* BugReportTests.swift in Sources */, 9EF72884B74BC39B01640098 /* BugReportTests.swift in Sources */,
94D0F36A87E596A93C0C178A /* Bundle.swift in Sources */, 94D0F36A87E596A93C0C178A /* Bundle.swift in Sources */,
28AB1614E749D1147A2AC6C2 /* CreateRoomScreenTests.swift in Sources */,
8D24671992A1C1753B211221 /* EncryptionResetTests.swift in Sources */, 8D24671992A1C1753B211221 /* EncryptionResetTests.swift in Sources */,
E5E43A0CA99AF5BA11B194A2 /* EncryptionSettingsTests.swift in Sources */, E5E43A0CA99AF5BA11B194A2 /* EncryptionSettingsTests.swift in Sources */,
A950C95855C474F75B30CA7B /* PollFormScreenTests.swift in Sources */, A950C95855C474F75B30CA7B /* PollFormScreenTests.swift in Sources */,
@@ -8550,7 +8538,7 @@
94EB81606839C175CA6C2ADB /* RoomScreenTests.swift in Sources */, 94EB81606839C175CA6C2ADB /* RoomScreenTests.swift in Sources */,
E9985DCD1B0D026D7E8BF809 /* ServerSelectionTests.swift in Sources */, E9985DCD1B0D026D7E8BF809 /* ServerSelectionTests.swift in Sources */,
D5C2DA52162A978743A183F2 /* SessionVerificationTests.swift in Sources */, D5C2DA52162A978743A183F2 /* SessionVerificationTests.swift in Sources */,
F9788AE0B9EA19F1190ED14F /* StartChatScreenTests.swift in Sources */, 221A7F92116722322AE150AE /* StartChatTests.swift in Sources */,
54AE8860D668AFD96E7E177B /* UITestsScreenIdentifier.swift in Sources */, 54AE8860D668AFD96E7E177B /* UITestsScreenIdentifier.swift in Sources */,
84EFCB95F9DA2979C8042B26 /* UITestsSignalling.swift in Sources */, 84EFCB95F9DA2979C8042B26 /* UITestsSignalling.swift in Sources */,
B22D857D1E8FCA6DD74A58E3 /* UserSessionScreenTests.swift in Sources */, B22D857D1E8FCA6DD74A58E3 /* UserSessionScreenTests.swift in Sources */,
@@ -36,9 +36,10 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
// periphery:ignore - retaining purpose // periphery:ignore - retaining purpose
private var bugReportFlowCoordinator: BugReportFlowCoordinator? private var bugReportFlowCoordinator: BugReportFlowCoordinator?
// periphery:ignore - retaining purpose // periphery:ignore - retaining purpose
private var encryptionResetFlowCoordinator: EncryptionResetFlowCoordinator? private var encryptionResetFlowCoordinator: EncryptionResetFlowCoordinator?
// periphery:ignore - retaining purpose
private var startChatFlowCoordinator: StartChatFlowCoordinator?
// periphery:ignore - retaining purpose // periphery:ignore - retaining purpose
private var globalSearchScreenCoordinator: GlobalSearchScreenCoordinator? private var globalSearchScreenCoordinator: GlobalSearchScreenCoordinator?
@@ -223,12 +224,12 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
case (.roomList, .startEncryptionResetFlow, .encryptionResetFlow): case (.roomList, .startEncryptionResetFlow, .encryptionResetFlow):
startEncryptionResetFlow(animated: animated) startEncryptionResetFlow(animated: animated)
case (.encryptionResetFlow, .finishedEncryptionResetFlow, .roomList): case (.encryptionResetFlow, .finishedEncryptionResetFlow, .roomList):
break encryptionResetFlowCoordinator = nil
case (.roomList, .showStartChatScreen, .startChatScreen): case (.roomList, .startStartChatFlow, .startChatFlow):
presentStartChat(animated: animated) startStartChatFlow(animated: animated)
case (.startChatScreen, .dismissedStartChatScreen, .roomList): case (.startChatFlow, .finishedStartChatFlow, .roomList):
break startChatFlowCoordinator = nil
case (.roomList, .showRoomDirectorySearchScreen, .roomDirectorySearchScreen): case (.roomList, .showRoomDirectorySearchScreen, .roomDirectorySearchScreen):
presentRoomDirectorySearch() presentRoomDirectorySearch()
@@ -398,7 +399,7 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
case .presentEncryptionResetScreen: case .presentEncryptionResetScreen:
stateMachine.processEvent(.startEncryptionResetFlow) stateMachine.processEvent(.startEncryptionResetFlow)
case .presentStartChatScreen: case .presentStartChatScreen:
stateMachine.processEvent(.showStartChatScreen) stateMachine.processEvent(.startStartChatFlow)
case .presentGlobalSearch: case .presentGlobalSearch:
presentGlobalSearch() presentGlobalSearch()
case .logout: case .logout:
@@ -565,39 +566,34 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
// MARK: Start Chat // MARK: Start Chat
private func presentStartChat(animated: Bool) { private func startStartChatFlow(animated: Bool) {
let startChatNavigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = StartChatFlowCoordinator(userDiscoveryService: UserDiscoveryService(clientProxy: userSession.clientProxy),
let userDiscoveryService = UserDiscoveryService(clientProxy: userSession.clientProxy) navigationStackCoordinator: navigationStackCoordinator,
let parameters = StartChatScreenCoordinatorParameters(orientationManager: flowParameters.windowManager, flowParameters: flowParameters)
userSession: userSession,
userIndicatorController: flowParameters.userIndicatorController,
navigationStackCoordinator: startChatNavigationStackCoordinator,
userDiscoveryService: userDiscoveryService,
mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: flowParameters.appSettings),
appSettings: flowParameters.appSettings,
analytics: flowParameters.analytics)
let coordinator = StartChatScreenCoordinator(parameters: parameters) coordinator.actionsPublisher
coordinator.actions.sink { [weak self] action in .sink { [weak self] action in
guard let self else { return } guard let self else { return }
switch action { switch action {
case .close: case .finished(let roomID):
navigationSplitCoordinator.setSheetCoordinator(nil) navigationSplitCoordinator.setSheetCoordinator(nil)
case .openRoom(let roomID):
navigationSplitCoordinator.setSheetCoordinator(nil) if let roomID {
stateMachine.processEvent(.selectRoom(roomID: roomID, via: [], entryPoint: .room)) stateMachine.processEvent(.selectRoom(roomID: roomID, via: [], entryPoint: .room))
case .openRoomDirectorySearch: }
navigationSplitCoordinator.setSheetCoordinator(nil) case .showRoomDirectory:
stateMachine.processEvent(.showRoomDirectorySearchScreen) navigationSplitCoordinator.setSheetCoordinator(nil)
stateMachine.processEvent(.showRoomDirectorySearchScreen)
}
} }
} .store(in: &cancellables)
.store(in: &cancellables)
startChatFlowCoordinator = coordinator
startChatNavigationStackCoordinator.setRootCoordinator(coordinator) coordinator.start()
navigationSplitCoordinator.setSheetCoordinator(startChatNavigationStackCoordinator, animated: animated) { [weak self] in navigationSplitCoordinator.setSheetCoordinator(navigationStackCoordinator, animated: animated) { [weak self] in
self?.stateMachine.processEvent(.dismissedStartChatScreen) self?.stateMachine.processEvent(.finishedStartChatFlow)
} }
} }
@@ -639,10 +635,8 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
guard let self else { return } guard let self else { return }
switch action { switch action {
case .resetComplete: case .resetComplete:
encryptionResetFlowCoordinator = nil
navigationSplitCoordinator.setSheetCoordinator(nil) navigationSplitCoordinator.setSheetCoordinator(nil)
case .cancel: case .cancel:
encryptionResetFlowCoordinator = nil
navigationSplitCoordinator.setSheetCoordinator(nil) navigationSplitCoordinator.setSheetCoordinator(nil)
} }
} }
@@ -33,8 +33,8 @@ class ChatsFlowCoordinatorStateMachine {
/// Showing the encryption reset flow. /// Showing the encryption reset flow.
case encryptionResetFlow(detailState: DetailState?) case encryptionResetFlow(detailState: DetailState?)
/// Showing the start chat screen /// Showing the start chat flow
case startChatScreen(detailState: DetailState?) case startChatFlow(detailState: DetailState?)
/// Showing the logout flows /// Showing the logout flows
case logoutConfirmationScreen(detailState: DetailState?) case logoutConfirmationScreen(detailState: DetailState?)
@@ -61,7 +61,7 @@ class ChatsFlowCoordinatorStateMachine {
.feedbackScreen(let detailState), .feedbackScreen(let detailState),
.recoveryKeyScreen(let detailState), .recoveryKeyScreen(let detailState),
.encryptionResetFlow(let detailState), .encryptionResetFlow(let detailState),
.startChatScreen(let detailState), .startChatFlow(let detailState),
.logoutConfirmationScreen(let detailState), .logoutConfirmationScreen(let detailState),
.roomDirectorySearchScreen(let detailState), .roomDirectorySearchScreen(let detailState),
.reportRoomScreen(let detailState), .reportRoomScreen(let detailState),
@@ -111,10 +111,10 @@ class ChatsFlowCoordinatorStateMachine {
/// The encryption reset flow is complete and has been dismissed. /// The encryption reset flow is complete and has been dismissed.
case finishedEncryptionResetFlow case finishedEncryptionResetFlow
/// Request the start of the start chat flow /// Request the start of the start chat flow.
case showStartChatScreen case startStartChatFlow
/// Start chat has been dismissed /// The Start Chat flow is complete and has been dismissed.
case dismissedStartChatScreen case finishedStartChatFlow
/// Request presentation of the room directory search screen. /// Request presentation of the room directory search screen.
case showRoomDirectorySearchScreen case showRoomDirectorySearchScreen
@@ -177,9 +177,9 @@ class ChatsFlowCoordinatorStateMachine {
case (.encryptionResetFlow(let detailState), .finishedEncryptionResetFlow): case (.encryptionResetFlow(let detailState), .finishedEncryptionResetFlow):
return .roomList(detailState: detailState) return .roomList(detailState: detailState)
case (.roomList(let detailState), .showStartChatScreen): case (.roomList(let detailState), .startStartChatFlow):
return .startChatScreen(detailState: detailState) return .startChatFlow(detailState: detailState)
case (.startChatScreen(let detailState), .dismissedStartChatScreen): case (.startChatFlow(let detailState), .finishedStartChatFlow):
return .roomList(detailState: detailState) return .roomList(detailState: detailState)
case (.roomList(let detailState), .showRoomDirectorySearchScreen): case (.roomList(let detailState), .showRoomDirectorySearchScreen):
@@ -1267,12 +1267,10 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
} }
private func presentInviteUsersScreen() { private func presentInviteUsersScreen() {
let selectedUsersSubject: CurrentValueSubject<[UserProfileProxy], Never> = .init([])
let stackCoordinator = NavigationStackCoordinator() let stackCoordinator = NavigationStackCoordinator()
let inviteParameters = InviteUsersScreenCoordinatorParameters(userSession: userSession, let inviteParameters = InviteUsersScreenCoordinatorParameters(userSession: userSession,
selectedUsers: .init(selectedUsersSubject), roomProxy: roomProxy,
roomType: .room(roomProxy: roomProxy), isSkippable: false,
userDiscoveryService: UserDiscoveryService(clientProxy: userSession.clientProxy), userDiscoveryService: UserDiscoveryService(clientProxy: userSession.clientProxy),
userIndicatorController: flowParameters.userIndicatorController, userIndicatorController: flowParameters.userIndicatorController,
appSettings: flowParameters.appSettings) appSettings: flowParameters.appSettings)
@@ -1286,8 +1284,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
switch action { switch action {
case .dismiss: case .dismiss:
navigationStackCoordinator.setSheetCoordinator(nil) navigationStackCoordinator.setSheetCoordinator(nil)
case .proceed:
fatalError("Not handled in this flow.")
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
@@ -255,8 +255,8 @@ final class RoomMembersFlowCoordinator: FlowCoordinatorProtocol {
private func presentInviteUsersScreen() { private func presentInviteUsersScreen() {
let stackCoordinator = NavigationStackCoordinator() let stackCoordinator = NavigationStackCoordinator()
let inviteParameters = InviteUsersScreenCoordinatorParameters(userSession: flowParameters.userSession, let inviteParameters = InviteUsersScreenCoordinatorParameters(userSession: flowParameters.userSession,
selectedUsers: nil, roomProxy: roomProxy,
roomType: .room(roomProxy: roomProxy), isSkippable: false,
userDiscoveryService: UserDiscoveryService(clientProxy: flowParameters.userSession.clientProxy), userDiscoveryService: UserDiscoveryService(clientProxy: flowParameters.userSession.clientProxy),
userIndicatorController: flowParameters.userIndicatorController, userIndicatorController: flowParameters.userIndicatorController,
appSettings: flowParameters.appSettings) appSettings: flowParameters.appSettings)
@@ -270,8 +270,6 @@ final class RoomMembersFlowCoordinator: FlowCoordinatorProtocol {
switch action { switch action {
case .dismiss: case .dismiss:
navigationStackCoordinator.setSheetCoordinator(nil) navigationStackCoordinator.setSheetCoordinator(nil)
case .proceed:
fatalError("Not handled in this flow.")
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
@@ -0,0 +1,237 @@
//
// Copyright 2025 Element Creations Ltd.
// 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 Combine
import Foundation
import SwiftState
enum StartChatFlowCoordinatorAction {
case finished(roomID: String?)
case showRoomDirectory
}
class StartChatFlowCoordinator: FlowCoordinatorProtocol {
private let userDiscoveryService: UserDiscoveryServiceProtocol
private let navigationStackCoordinator: NavigationStackCoordinator
private let flowParameters: CommonFlowParameters
private var createRoomScreenCoordinator: CreateRoomScreenCoordinator?
indirect enum State: StateType {
/// The state machine hasn't started.
case initial
/// Shown when the flow is started with options to create a room/DM, join by alias, use the room directory etc.
case startChat
/// The user is creating a new room.
case createRoom
/// The user is selecting an avatar for the new room.
case roomAvatarPicker
/// The user is inviting users to a newly created room.
case inviteUsers
}
enum Event: EventType {
/// The flow is being started.
case start
/// The user would like to create a room.
case createRoom
/// The user dismissed the create room screen.
case dismissedCreateRoom
/// The user would like to pick an avatar for the room.
case presentRoomAvatarPicker
/// The user finished picking the avatar.
case dismissedRoomAvatarPicker
/// The user's room was created successfully.
case createdRoom
}
private let stateMachine: StateMachine<State, Event>
private var cancellables: Set<AnyCancellable> = []
private let actionsSubject: PassthroughSubject<StartChatFlowCoordinatorAction, Never> = .init()
var actionsPublisher: AnyPublisher<StartChatFlowCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(userDiscoveryService: UserDiscoveryServiceProtocol,
navigationStackCoordinator: NavigationStackCoordinator,
flowParameters: CommonFlowParameters) {
self.userDiscoveryService = userDiscoveryService
self.navigationStackCoordinator = navigationStackCoordinator
self.flowParameters = flowParameters
stateMachine = .init(state: .initial)
configureStateMachine()
}
func start(animated: Bool) {
stateMachine.tryEvent(.start)
}
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
// There aren't any routes to this screen yet, so clear the stacks.
clearRoute(animated: animated)
}
func clearRoute(animated: Bool) {
switch stateMachine.state {
case .initial:
break
case .startChat:
navigationStackCoordinator.setRootCoordinator(nil, animated: animated) // StartChatScreen
case .createRoom:
navigationStackCoordinator.pop(animated: animated) // CreateRoomScreen
navigationStackCoordinator.setRootCoordinator(nil, animated: animated) // StartChatScreen
case .roomAvatarPicker:
navigationStackCoordinator.setSheetCoordinator(nil, animated: animated) // Media Picker
clearRoute(animated: animated) // Re-run with the state machine back in the .createRoom state.
case .inviteUsers:
navigationStackCoordinator.pop(animated: animated) // InviteUsersScreen
navigationStackCoordinator.pop(animated: animated) // CreateRoomScreen
navigationStackCoordinator.setRootCoordinator(nil, animated: animated) // StartChatScreen
}
}
// MARK: - Private
private func configureStateMachine() {
stateMachine.addRoutes(event: .start, transitions: [.initial => .startChat]) { [weak self] _ in
self?.presentStartChatScreen()
}
stateMachine.addRoutes(event: .createRoom, transitions: [.startChat => .createRoom]) { [weak self] _ in
self?.presentCreateRoomScreen()
}
stateMachine.addRoutes(event: .dismissedCreateRoom, transitions: [.createRoom => .startChat]) { [weak self] _ in
self?.createRoomScreenCoordinator = nil
}
stateMachine.addRoutes(event: .presentRoomAvatarPicker, transitions: [.createRoom => .roomAvatarPicker]) { [weak self] context in
guard let mode = context.userInfo as? MediaPickerScreenMode else {
fatalError("A picker mode is required for the room avatar.")
}
self?.presentRoomAvatarPicker(mode)
}
stateMachine.addRoutes(event: .dismissedRoomAvatarPicker, transitions: [.roomAvatarPicker => .createRoom])
stateMachine.addRoutes(event: .createdRoom, transitions: [.createRoom => .inviteUsers]) { [weak self] context in
guard let roomProxy = context.userInfo as? JoinedRoomProxyProtocol else {
fatalError("A room proxy is required to invite users.")
}
self?.presentInviteUsersScreen(roomProxy: roomProxy)
}
stateMachine.addErrorHandler { context in
if context.fromState == context.toState {
MXLog.error("Transition between equal states: \(context.fromState)")
} else {
fatalError("Unexpected transition: \(context)")
}
}
}
private func presentStartChatScreen() {
let parameters = StartChatScreenCoordinatorParameters(userSession: flowParameters.userSession,
userDiscoveryService: userDiscoveryService,
userIndicatorController: flowParameters.userIndicatorController,
appSettings: flowParameters.appSettings,
analytics: flowParameters.analytics)
let coordinator = StartChatScreenCoordinator(parameters: parameters)
coordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .close:
actionsSubject.send(.finished(roomID: nil))
case .createRoom:
stateMachine.tryEvent(.createRoom)
case .openRoom(let roomID):
actionsSubject.send(.finished(roomID: roomID))
case .openRoomDirectorySearch:
actionsSubject.send(.showRoomDirectory)
}
}
.store(in: &cancellables)
navigationStackCoordinator.setRootCoordinator(coordinator)
}
private func presentCreateRoomScreen() {
let createParameters = CreateRoomScreenCoordinatorParameters(userSession: flowParameters.userSession,
userIndicatorController: flowParameters.userIndicatorController,
appSettings: flowParameters.appSettings,
analytics: flowParameters.analytics)
let coordinator = CreateRoomScreenCoordinator(parameters: createParameters)
coordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .createdRoom(let roomProxy):
stateMachine.tryEvent(.createdRoom, userInfo: roomProxy)
case .displayMediaPickerWithMode(let mode):
stateMachine.tryEvent(.presentRoomAvatarPicker, userInfo: mode)
}
}
.store(in: &cancellables)
createRoomScreenCoordinator = coordinator
navigationStackCoordinator.push(coordinator) { [weak self] in
self?.stateMachine.tryEvent(.dismissedCreateRoom)
}
}
private func presentRoomAvatarPicker(_ mode: MediaPickerScreenMode) {
let stackCoordinator = NavigationStackCoordinator()
let mediaPickerCoordinator = MediaPickerScreenCoordinator(mode: mode,
userIndicatorController: flowParameters.userIndicatorController,
orientationManager: flowParameters.windowManager) { [weak self] action in
guard let self else { return }
switch action {
case .cancel:
navigationStackCoordinator.setSheetCoordinator(nil)
case .selectedMediaAtURLs(let urls):
guard urls.count == 1 else { fatalError("Received an invalid number of URLs") }
navigationStackCoordinator.setSheetCoordinator(nil)
createRoomScreenCoordinator?.updateAvatar(fileURL: urls[0])
}
}
stackCoordinator.setRootCoordinator(mediaPickerCoordinator)
navigationStackCoordinator.setSheetCoordinator(stackCoordinator) { [weak self] in
self?.stateMachine.tryEvent(.dismissedRoomAvatarPicker)
}
}
private func presentInviteUsersScreen(roomProxy: JoinedRoomProxyProtocol) {
let inviteParameters = InviteUsersScreenCoordinatorParameters(userSession: flowParameters.userSession,
roomProxy: roomProxy,
isSkippable: true,
userDiscoveryService: userDiscoveryService,
userIndicatorController: flowParameters.userIndicatorController,
appSettings: flowParameters.appSettings)
let coordinator = InviteUsersScreenCoordinator(parameters: inviteParameters)
coordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .dismiss:
actionsSubject.send(.finished(roomID: roomProxy.id))
}
}
.store(in: &cancellables)
navigationStackCoordinator.push(coordinator)
}
}
@@ -123,6 +123,7 @@ extension JoinedRoomProxyMock {
powerLevelsReturnValue = .success(powerLevelsProxyMock) powerLevelsReturnValue = .success(powerLevelsProxyMock)
inviteUserIDReturnValue = .success(())
kickUserReasonReturnValue = .success(()) kickUserReasonReturnValue = .success(())
banUserReasonReturnValue = .success(()) banUserReasonReturnValue = .success(())
unbanUserReturnValue = .success(()) unbanUserReturnValue = .success(())
@@ -270,8 +270,11 @@ enum A11yIdentifiers {
} }
struct CreateRoomScreen { struct CreateRoomScreen {
let create = "create_room-create"
let roomAvatar = "create_room-room_avatar"
let roomName = "create_room-room_name" let roomName = "create_room-room_name"
let roomTopic = "create_room-room_topic" let roomTopic = "create_room-room_topic"
let mediaPicker = "create_room-media_picker"
} }
struct PollFormScreen { struct PollFormScreen {
@@ -1,71 +0,0 @@
//
// Copyright 2025 Element Creations Ltd.
// Copyright 2022-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 Combine
import SwiftUI
struct CreateRoomCoordinatorParameters {
let userSession: UserSessionProtocol
let userIndicatorController: UserIndicatorControllerProtocol
let createRoomParameters: CurrentValuePublisher<CreateRoomFlowParameters, Never>
let selectedUsers: [UserProfileProxy]
let appSettings: AppSettings
let analytics: AnalyticsService
}
enum CreateRoomCoordinatorAction {
case openRoom(withIdentifier: String)
case updateSelectedUsers([UserProfileProxy])
case updateDetails(CreateRoomFlowParameters)
case displayMediaPickerWithMode(MediaPickerScreenMode)
case removeImage
}
final class CreateRoomCoordinator: CoordinatorProtocol {
private var viewModel: CreateRoomViewModelProtocol
private let actionsSubject: PassthroughSubject<CreateRoomCoordinatorAction, Never> = .init()
private var cancellables = Set<AnyCancellable>()
var actions: AnyPublisher<CreateRoomCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: CreateRoomCoordinatorParameters) {
viewModel = CreateRoomViewModel(userSession: parameters.userSession,
createRoomParameters: parameters.createRoomParameters,
selectedUsers: parameters.selectedUsers,
analytics: parameters.analytics,
userIndicatorController: parameters.userIndicatorController,
appSettings: parameters.appSettings)
}
func start() {
viewModel.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .updateSelectedUsers(let users):
actionsSubject.send(.updateSelectedUsers(users))
case .openRoom(let identifier):
actionsSubject.send(.openRoom(withIdentifier: identifier))
case .updateDetails(let details):
actionsSubject.send(.updateDetails(details))
case .displayCameraPicker:
actionsSubject.send(.displayMediaPickerWithMode(.init(source: .camera, selectionType: .single)))
case .displayMediaPicker:
actionsSubject.send(.displayMediaPickerWithMode(.init(source: .photoLibrary, selectionType: .single)))
case .removeImage:
actionsSubject.send(.removeImage)
}
}
.store(in: &cancellables)
}
func toPresentable() -> AnyView {
AnyView(CreateRoomScreen(context: viewModel.context))
}
}
@@ -0,0 +1,62 @@
//
// Copyright 2025 Element Creations Ltd.
// Copyright 2022-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 Combine
import SwiftUI
struct CreateRoomScreenCoordinatorParameters {
let userSession: UserSessionProtocol
let userIndicatorController: UserIndicatorControllerProtocol
let appSettings: AppSettings
let analytics: AnalyticsService
}
enum CreateRoomScreenCoordinatorAction {
case createdRoom(JoinedRoomProxyProtocol)
case displayMediaPickerWithMode(MediaPickerScreenMode)
}
final class CreateRoomScreenCoordinator: CoordinatorProtocol {
private var viewModel: CreateRoomScreenViewModelProtocol
private let actionsSubject: PassthroughSubject<CreateRoomScreenCoordinatorAction, Never> = .init()
private var cancellables = Set<AnyCancellable>()
var actions: AnyPublisher<CreateRoomScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: CreateRoomScreenCoordinatorParameters) {
viewModel = CreateRoomScreenViewModel(userSession: parameters.userSession,
analytics: parameters.analytics,
userIndicatorController: parameters.userIndicatorController,
appSettings: parameters.appSettings)
}
func start() {
viewModel.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .createdRoom(let roomProxy):
actionsSubject.send(.createdRoom(roomProxy))
case .displayCameraPicker:
actionsSubject.send(.displayMediaPickerWithMode(.init(source: .camera, selectionType: .single)))
case .displayMediaPicker:
actionsSubject.send(.displayMediaPickerWithMode(.init(source: .photoLibrary, selectionType: .single)))
}
}
.store(in: &cancellables)
}
func toPresentable() -> AnyView {
AnyView(CreateRoomScreen(context: viewModel.context))
}
func updateAvatar(fileURL: URL) {
viewModel.updateAvatar(fileURL: fileURL)
}
}
@@ -16,28 +16,24 @@ enum CreateRoomScreenErrorType: Error {
case unknown case unknown
} }
enum CreateRoomViewModelAction { enum CreateRoomScreenViewModelAction {
case openRoom(withIdentifier: String) case createdRoom(JoinedRoomProxyProtocol)
case updateSelectedUsers([UserProfileProxy])
case updateDetails(CreateRoomFlowParameters)
case displayMediaPicker case displayMediaPicker
case displayCameraPicker case displayCameraPicker
case removeImage
} }
struct CreateRoomViewState: BindableState { struct CreateRoomScreenViewState: BindableState {
var roomName: String var roomName: String
let serverName: String let serverName: String
let isKnockingFeatureEnabled: Bool let isKnockingFeatureEnabled: Bool
var selectedUsers: [UserProfileProxy]
var aliasLocalPart: String var aliasLocalPart: String
var bindings: CreateRoomViewStateBindings var bindings: CreateRoomScreenViewStateBindings
var avatarURL: URL? var avatarURL: URL?
var canCreateRoom: Bool { var canCreateRoom: Bool {
!roomName.isEmpty && aliasErrors.isEmpty !roomName.isEmpty && aliasErrors.isEmpty
} }
var aliasErrors: Set<CreateRoomAliasErrorState> = [] var aliasErrors: Set<CreateRoomScreenAliasErrorState> = []
var aliasErrorDescription: String? { var aliasErrorDescription: String? {
if aliasErrors.contains(.alreadyExists) { if aliasErrors.contains(.alreadyExists) {
L10n.errorRoomAddressAlreadyExists L10n.errorRoomAddressAlreadyExists
@@ -49,7 +45,7 @@ struct CreateRoomViewState: BindableState {
} }
} }
struct CreateRoomViewStateBindings { struct CreateRoomScreenViewStateBindings {
var roomTopic: String var roomTopic: String
var isRoomPrivate: Bool var isRoomPrivate: Bool
var isKnockingOnly: Bool var isKnockingOnly: Bool
@@ -59,9 +55,8 @@ struct CreateRoomViewStateBindings {
var alertInfo: AlertInfo<CreateRoomScreenErrorType>? var alertInfo: AlertInfo<CreateRoomScreenErrorType>?
} }
enum CreateRoomViewAction { enum CreateRoomScreenViewAction {
case createRoom case createRoom
case deselectUser(UserProfileProxy)
case displayCameraPicker case displayCameraPicker
case displayMediaPicker case displayMediaPicker
case removeImage case removeImage
@@ -69,12 +64,12 @@ enum CreateRoomViewAction {
case updateAliasLocalPart(String) case updateAliasLocalPart(String)
} }
enum CreateRoomAliasErrorState { enum CreateRoomScreenAliasErrorState {
case alreadyExists case alreadyExists
case invalidSymbols case invalidSymbols
} }
extension Set<CreateRoomAliasErrorState> { extension Set<CreateRoomScreenAliasErrorState> {
var errorDescription: String? { var errorDescription: String? {
if contains(.alreadyExists) { if contains(.alreadyExists) {
return L10n.errorRoomAddressAlreadyExists return L10n.errorRoomAddressAlreadyExists
@@ -10,83 +10,70 @@ import Combine
import MatrixRustSDK import MatrixRustSDK
import SwiftUI import SwiftUI
typealias CreateRoomViewModelType = StateStoreViewModel<CreateRoomViewState, CreateRoomViewAction> typealias CreateRoomScreenViewModelType = StateStoreViewModel<CreateRoomScreenViewState, CreateRoomScreenViewAction>
class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol { class CreateRoomScreenViewModel: CreateRoomScreenViewModelType, CreateRoomScreenViewModelProtocol {
struct Parameters {
var name = ""
var topic = ""
var isRoomPrivate = true
var isKnockingOnly = false
var avatarImageMedia: MediaInfo?
var aliasLocalPart: String?
}
private let userSession: UserSessionProtocol private let userSession: UserSessionProtocol
private var createRoomParameters: CreateRoomFlowParameters private var parameters: Parameters
private let mediaUploadingPreprocessor: MediaUploadingPreprocessor
private let analytics: AnalyticsService private let analytics: AnalyticsService
private let userIndicatorController: UserIndicatorControllerProtocol private let userIndicatorController: UserIndicatorControllerProtocol
private var syncNameAndAlias = true private var syncNameAndAlias = true
@CancellableTask private var checkAliasAvailabilityTask: Task<Void, Never>? @CancellableTask private var checkAliasAvailabilityTask: Task<Void, Never>?
private var actionsSubject: PassthroughSubject<CreateRoomViewModelAction, Never> = .init() private var actionsSubject: PassthroughSubject<CreateRoomScreenViewModelAction, Never> = .init()
var actions: AnyPublisher<CreateRoomViewModelAction, Never> { var actions: AnyPublisher<CreateRoomScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher() actionsSubject.eraseToAnyPublisher()
} }
init(userSession: UserSessionProtocol, init(userSession: UserSessionProtocol,
createRoomParameters: CurrentValuePublisher<CreateRoomFlowParameters, Never>, initialParameters: Parameters = .init(),
selectedUsers: [UserProfileProxy],
analytics: AnalyticsService, analytics: AnalyticsService,
userIndicatorController: UserIndicatorControllerProtocol, userIndicatorController: UserIndicatorControllerProtocol,
appSettings: AppSettings) { appSettings: AppSettings) {
let parameters = createRoomParameters.value
self.userSession = userSession self.userSession = userSession
self.createRoomParameters = parameters parameters = initialParameters
mediaUploadingPreprocessor = MediaUploadingPreprocessor(appSettings: appSettings)
self.analytics = analytics self.analytics = analytics
self.userIndicatorController = userIndicatorController self.userIndicatorController = userIndicatorController
let bindings = CreateRoomViewStateBindings(roomTopic: parameters.topic, let bindings = CreateRoomScreenViewStateBindings(roomTopic: parameters.topic,
isRoomPrivate: parameters.isRoomPrivate, isRoomPrivate: parameters.isRoomPrivate,
isKnockingOnly: appSettings.knockingEnabled ? parameters.isKnockingOnly : false) isKnockingOnly: appSettings.knockingEnabled ? parameters.isKnockingOnly : false)
super.init(initialViewState: CreateRoomViewState(roomName: parameters.name, super.init(initialViewState: CreateRoomScreenViewState(roomName: parameters.name,
serverName: userSession.clientProxy.userIDServerName ?? "", serverName: userSession.clientProxy.userIDServerName ?? "",
isKnockingFeatureEnabled: appSettings.knockingEnabled, isKnockingFeatureEnabled: appSettings.knockingEnabled,
selectedUsers: selectedUsers, aliasLocalPart: parameters.aliasLocalPart ?? roomAliasNameFromRoomDisplayName(roomName: parameters.name),
aliasLocalPart: parameters.aliasLocalPart ?? roomAliasNameFromRoomDisplayName(roomName: parameters.name), bindings: bindings),
bindings: bindings),
mediaProvider: userSession.mediaProvider) mediaProvider: userSession.mediaProvider)
createRoomParameters
.map(\.avatarImageMedia)
.removeDuplicates { $0?.url == $1?.url }
.sink { [weak self] mediaInfo in
self?.createRoomParameters.avatarImageMedia = mediaInfo
switch mediaInfo {
case .image(_, let thumbnailURL, _):
self?.state.avatarURL = thumbnailURL
case nil:
self?.state.avatarURL = nil
default:
break
}
}
.store(in: &cancellables)
setupBindings() setupBindings()
} }
// MARK: - Public // MARK: - Public
override func process(viewAction: CreateRoomViewAction) { override func process(viewAction: CreateRoomScreenViewAction) {
switch viewAction { switch viewAction {
case .createRoom: case .createRoom:
Task { Task { await createRoom() }
await createRoom()
}
case .deselectUser(let user):
state.selectedUsers.removeAll { $0.userID == user.userID }
actionsSubject.send(.updateSelectedUsers(state.selectedUsers))
case .displayCameraPicker: case .displayCameraPicker:
actionsSubject.send(.displayCameraPicker) actionsSubject.send(.displayCameraPicker)
case .displayMediaPicker: case .displayMediaPicker:
actionsSubject.send(.displayMediaPicker) actionsSubject.send(.displayMediaPicker)
case .removeImage: case .removeImage:
actionsSubject.send(.removeImage) parameters.avatarImageMedia = nil
state.avatarURL = nil
case .updateAliasLocalPart(let aliasLocalPart): case .updateAliasLocalPart(let aliasLocalPart):
state.aliasLocalPart = aliasLocalPart.lowercased() state.aliasLocalPart = aliasLocalPart.lowercased()
// If this has been called this means that the user wants a custom address not necessarily reflecting the name // If this has been called this means that the user wants a custom address not necessarily reflecting the name
@@ -104,6 +91,32 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol
} }
} }
func updateAvatar(fileURL: URL) {
showLoadingIndicator()
Task { [weak self] in
guard let self else { return }
do {
guard case let .success(maxUploadSize) = await userSession.clientProxy.maxMediaUploadSize else {
MXLog.error("Failed to get max upload size")
userIndicatorController.alertInfo = AlertInfo(id: .init())
return
}
let mediaInfo = try await mediaUploadingPreprocessor.processMedia(at: fileURL, maxUploadSize: maxUploadSize).get()
switch mediaInfo {
case .image(_, let thumbnailURL, _):
parameters.avatarImageMedia = mediaInfo
state.avatarURL = thumbnailURL
default:
break
}
} catch {
userIndicatorController.alertInfo = AlertInfo(id: .init())
}
hideLoadingIndicator()
}
}
// MARK: - Private // MARK: - Private
private func setupBindings() { private func setupBindings() {
@@ -135,7 +148,6 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol
.sink { [weak self] state in .sink { [weak self] state in
guard let self else { return } guard let self else { return }
updateParameters(state: state) updateParameters(state: state)
actionsSubject.send(.updateDetails(createRoomParameters))
} }
.store(in: &cancellables) .store(in: &cancellables)
@@ -183,15 +195,15 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol
.store(in: &cancellables) .store(in: &cancellables)
} }
private func updateParameters(state: CreateRoomViewState) { private func updateParameters(state: CreateRoomScreenViewState) {
createRoomParameters.name = state.roomName parameters.name = state.roomName
createRoomParameters.topic = state.bindings.roomTopic parameters.topic = state.bindings.roomTopic
createRoomParameters.isRoomPrivate = state.bindings.isRoomPrivate parameters.isRoomPrivate = state.bindings.isRoomPrivate
createRoomParameters.isKnockingOnly = state.bindings.isKnockingOnly parameters.isKnockingOnly = state.bindings.isKnockingOnly
if state.isKnockingFeatureEnabled, !state.aliasLocalPart.isEmpty { if state.isKnockingFeatureEnabled, !state.aliasLocalPart.isEmpty {
createRoomParameters.aliasLocalPart = state.aliasLocalPart parameters.aliasLocalPart = state.aliasLocalPart
} else { } else {
createRoomParameters.aliasLocalPart = nil parameters.aliasLocalPart = nil
} }
} }
@@ -205,8 +217,8 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol
updateParameters(state: state) updateParameters(state: state)
// Better to double check the errors also when trying to create the room // Better to double check the errors also when trying to create the room
if state.isKnockingFeatureEnabled, !createRoomParameters.isRoomPrivate { if state.isKnockingFeatureEnabled, !parameters.isRoomPrivate {
guard let canonicalAlias = String.makeCanonicalAlias(aliasLocalPart: createRoomParameters.aliasLocalPart, guard let canonicalAlias = String.makeCanonicalAlias(aliasLocalPart: parameters.aliasLocalPart,
serverName: state.serverName), serverName: state.serverName),
isRoomAliasFormatValid(alias: canonicalAlias) else { isRoomAliasFormatValid(alias: canonicalAlias) else {
state.aliasErrors = [.invalidSymbols] state.aliasErrors = [.invalidSymbols]
@@ -226,7 +238,7 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol
} }
let avatarURL: URL? let avatarURL: URL?
if let media = createRoomParameters.avatarImageMedia { if let media = parameters.avatarImageMedia {
switch await userSession.clientProxy.uploadMedia(media) { switch await userSession.clientProxy.uploadMedia(media) {
case .success(let url): case .success(let url):
avatarURL = URL(string: url) avatarURL = URL(string: url)
@@ -251,17 +263,23 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol
avatarURL = nil avatarURL = nil
} }
switch await userSession.clientProxy.createRoom(name: createRoomParameters.name, switch await userSession.clientProxy.createRoom(name: parameters.name,
topic: createRoomParameters.topic.isBlank ? nil : createRoomParameters.topic, topic: parameters.topic.isBlank ? nil : parameters.topic,
isRoomPrivate: createRoomParameters.isRoomPrivate, isRoomPrivate: parameters.isRoomPrivate,
// As of right now we don't want to make private rooms with the knock rule // As of right now we don't want to make private rooms with the knock rule
isKnockingOnly: createRoomParameters.isRoomPrivate ? false : createRoomParameters.isKnockingOnly, isKnockingOnly: parameters.isRoomPrivate ? false : parameters.isKnockingOnly,
userIDs: state.selectedUsers.map(\.userID), userIDs: [], // The invite users screen is shown next so we don't need to invite anyone right now.
avatarURL: avatarURL, avatarURL: avatarURL,
aliasLocalPart: createRoomParameters.isRoomPrivate ? nil : createRoomParameters.aliasLocalPart) { aliasLocalPart: parameters.isRoomPrivate ? nil : parameters.aliasLocalPart) {
case .success(let roomId): case .success(let roomID):
guard case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(roomID) else {
state.bindings.alertInfo = AlertInfo(id: .failedCreatingRoom,
title: L10n.commonError,
message: L10n.screenStartChatErrorStartingChat)
return
}
analytics.trackCreatedRoom(isDM: false) analytics.trackCreatedRoom(isDM: false)
actionsSubject.send(.openRoom(withIdentifier: roomId)) actionsSubject.send(.createdRoom(roomProxy))
case .failure: case .failure:
state.bindings.alertInfo = AlertInfo(id: .failedCreatingRoom, state.bindings.alertInfo = AlertInfo(id: .failedCreatingRoom,
title: L10n.commonError, title: L10n.commonError,
@@ -271,7 +289,7 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol
// MARK: Loading indicator // MARK: Loading indicator
private static let loadingIndicatorIdentifier = "\(CreateRoomViewModel.self)-Loading" private static let loadingIndicatorIdentifier = "\(CreateRoomScreenViewModel.self)-Loading"
private func showLoadingIndicator() { private func showLoadingIndicator() {
userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,
@@ -7,9 +7,12 @@
// //
import Combine import Combine
import Foundation
@MainActor @MainActor
protocol CreateRoomViewModelProtocol { protocol CreateRoomScreenViewModelProtocol {
var actions: AnyPublisher<CreateRoomViewModelAction, Never> { get } var actions: AnyPublisher<CreateRoomScreenViewModelAction, Never> { get }
var context: CreateRoomViewModelType.Context { get } var context: CreateRoomScreenViewModelType.Context { get }
func updateAvatar(fileURL: URL)
} }
@@ -10,7 +10,7 @@ import Compound
import SwiftUI import SwiftUI
struct CreateRoomScreen: View { struct CreateRoomScreen: View {
@ObservedObject var context: CreateRoomViewModel.Context @ObservedObject var context: CreateRoomScreenViewModel.Context
@FocusState private var focus: Focus? @FocusState private var focus: Focus?
private enum Focus { private enum Focus {
@@ -52,7 +52,6 @@ struct CreateRoomScreen: View {
.navigationTitle(L10n.screenCreateRoomTitle) .navigationTitle(L10n.screenCreateRoomTitle)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { toolbar } .toolbar { toolbar }
.readFrame($frame)
.alert(item: $context.alertInfo) .alert(item: $context.alertInfo)
.shouldScrollOnKeyboardDidShow(focus == .alias, to: Focus.alias) .shouldScrollOnKeyboardDidShow(focus == .alias, to: Focus.alias)
} }
@@ -109,6 +108,7 @@ struct CreateRoomScreen: View {
} }
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.accessibilityIdentifier(A11yIdentifiers.createRoomScreen.roomAvatar)
.confirmationDialog("", isPresented: $context.showAttachmentConfirmationDialog) { .confirmationDialog("", isPresented: $context.showAttachmentConfirmationDialog) {
Button(L10n.actionTakePhoto) { Button(L10n.actionTakePhoto) {
context.send(viewAction: .displayCameraPicker) context.send(viewAction: .displayCameraPicker)
@@ -116,6 +116,8 @@ struct CreateRoomScreen: View {
Button(L10n.actionChoosePhoto) { Button(L10n.actionChoosePhoto) {
context.send(viewAction: .displayMediaPicker) context.send(viewAction: .displayMediaPicker)
} }
.accessibilityIdentifier(A11yIdentifiers.createRoomScreen.mediaPicker)
if context.viewState.avatarURL != nil { if context.viewState.avatarURL != nil {
Button(L10n.actionRemove, role: .destructive) { Button(L10n.actionRemove, role: .destructive) {
context.send(viewAction: .removeImage) context.send(viewAction: .removeImage)
@@ -134,32 +136,9 @@ struct CreateRoomScreen: View {
} header: { } header: {
Text(L10n.screenCreateRoomTopicLabel) Text(L10n.screenCreateRoomTopicLabel)
.compoundListSectionHeader() .compoundListSectionHeader()
} footer: {
if !context.viewState.selectedUsers.isEmpty {
selectedUsersSection
}
} }
} }
@State private var frame: CGRect = .zero
@ScaledMetric private var invitedUserCellWidth: CGFloat = 72
private var selectedUsersSection: some View {
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 16) {
ForEach(context.viewState.selectedUsers, id: \.userID) { user in
InviteUsersScreenSelectedItem(user: user, mediaProvider: context.mediaProvider) {
context.send(viewAction: .deselectUser(user))
}
.frame(width: invitedUserCellWidth)
}
}
.padding(.horizontal, ListRowPadding.horizontal)
.padding(.vertical, 22)
}
.frame(width: frame.width)
}
private var securitySection: some View { private var securitySection: some View {
Section { Section {
ListRow(label: .default(title: L10n.screenCreateRoomPrivateOptionTitle, ListRow(label: .default(title: L10n.screenCreateRoomPrivateOptionTitle,
@@ -223,6 +202,7 @@ struct CreateRoomScreen: View {
context.send(viewAction: .createRoom) context.send(viewAction: .createRoom)
} }
.disabled(!context.viewState.canCreateRoom) .disabled(!context.viewState.canCreateRoom)
.accessibilityIdentifier(A11yIdentifiers.createRoomScreen.create)
} }
} }
} }
@@ -232,65 +212,43 @@ struct CreateRoomScreen: View {
struct CreateRoom_Previews: PreviewProvider, TestablePreview { struct CreateRoom_Previews: PreviewProvider, TestablePreview {
static let viewModel = { static let viewModel = {
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "@userid:example.com")))) let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "@userid:example.com"))))
let parameters = CreateRoomFlowParameters() return CreateRoomScreenViewModel(userSession: userSession,
let selectedUsers: [UserProfileProxy] = [.mockAlice, .mockBob, .mockCharlie] initialParameters: .init(),
analytics: ServiceLocator.shared.analytics,
return CreateRoomViewModel(userSession: userSession, userIndicatorController: UserIndicatorControllerMock(),
createRoomParameters: .init(parameters), appSettings: ServiceLocator.shared.settings)
selectedUsers: .init(selectedUsers),
analytics: ServiceLocator.shared.analytics,
userIndicatorController: UserIndicatorControllerMock(),
appSettings: ServiceLocator.shared.settings)
}()
static let emtpyViewModel = {
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "@userid:example.com"))))
let parameters = CreateRoomFlowParameters()
return CreateRoomViewModel(userSession: userSession,
createRoomParameters: .init(parameters),
selectedUsers: .init([]),
analytics: ServiceLocator.shared.analytics,
userIndicatorController: UserIndicatorControllerMock(),
appSettings: ServiceLocator.shared.settings)
}() }()
static let publicRoomViewModel = { static let publicRoomViewModel = {
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userIDServerName: "example.org", userID: "@userid:example.com")))) let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userIDServerName: "example.org", userID: "@userid:example.com"))))
let parameters = CreateRoomFlowParameters(isRoomPrivate: false)
let selectedUsers: [UserProfileProxy] = [.mockAlice, .mockBob, .mockCharlie]
ServiceLocator.shared.settings.knockingEnabled = true ServiceLocator.shared.settings.knockingEnabled = true
return CreateRoomViewModel(userSession: userSession, return CreateRoomScreenViewModel(userSession: userSession,
createRoomParameters: .init(parameters), initialParameters: .init(isRoomPrivate: false),
selectedUsers: .init([]), analytics: ServiceLocator.shared.analytics,
analytics: ServiceLocator.shared.analytics, userIndicatorController: UserIndicatorControllerMock(),
userIndicatorController: UserIndicatorControllerMock(), appSettings: ServiceLocator.shared.settings)
appSettings: ServiceLocator.shared.settings)
}() }()
static let publicRoomInvalidAliasViewModel = { static let publicRoomInvalidAliasViewModel = {
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userIDServerName: "example.org", userID: "@userid:example.com")))) let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userIDServerName: "example.org", userID: "@userid:example.com"))))
let parameters = CreateRoomFlowParameters(isRoomPrivate: false, aliasLocalPart: "#:")
ServiceLocator.shared.settings.knockingEnabled = true ServiceLocator.shared.settings.knockingEnabled = true
return CreateRoomViewModel(userSession: userSession, return CreateRoomScreenViewModel(userSession: userSession,
createRoomParameters: .init(parameters), initialParameters: .init(isRoomPrivate: false, aliasLocalPart: "#:"),
selectedUsers: .init([]), analytics: ServiceLocator.shared.analytics,
analytics: ServiceLocator.shared.analytics, userIndicatorController: UserIndicatorControllerMock(),
userIndicatorController: UserIndicatorControllerMock(), appSettings: ServiceLocator.shared.settings)
appSettings: ServiceLocator.shared.settings)
}() }()
static let publicRoomExistingAliasViewModel = { static let publicRoomExistingAliasViewModel = {
let clientProxy = ClientProxyMock(.init(userIDServerName: "example.org", userID: "@userid:example.com")) let clientProxy = ClientProxyMock(.init(userIDServerName: "example.org", userID: "@userid:example.com"))
clientProxy.isAliasAvailableReturnValue = .success(false) clientProxy.isAliasAvailableReturnValue = .success(false)
let userSession = UserSessionMock(.init(clientProxy: clientProxy)) let userSession = UserSessionMock(.init(clientProxy: clientProxy))
let parameters = CreateRoomFlowParameters(isRoomPrivate: false, aliasLocalPart: "existing")
ServiceLocator.shared.settings.knockingEnabled = true ServiceLocator.shared.settings.knockingEnabled = true
return CreateRoomViewModel(userSession: userSession, return CreateRoomScreenViewModel(userSession: userSession,
createRoomParameters: .init(parameters), initialParameters: .init(isRoomPrivate: false, aliasLocalPart: "existing"),
selectedUsers: .init([]), analytics: ServiceLocator.shared.analytics,
analytics: ServiceLocator.shared.analytics, userIndicatorController: UserIndicatorControllerMock(),
userIndicatorController: UserIndicatorControllerMock(), appSettings: ServiceLocator.shared.settings)
appSettings: ServiceLocator.shared.settings)
}() }()
static var previews: some View { static var previews: some View {
@@ -298,27 +256,22 @@ struct CreateRoom_Previews: PreviewProvider, TestablePreview {
CreateRoomScreen(context: viewModel.context) CreateRoomScreen(context: viewModel.context)
} }
.previewDisplayName("Create Room") .previewDisplayName("Create Room")
NavigationStack {
CreateRoomScreen(context: emtpyViewModel.context)
}
.previewDisplayName("Create Room without users")
NavigationStack { NavigationStack {
CreateRoomScreen(context: publicRoomViewModel.context) CreateRoomScreen(context: publicRoomViewModel.context)
} }
.previewDisplayName("Create Public Room") .previewDisplayName("Create Public Room")
NavigationStack { NavigationStack {
CreateRoomScreen(context: publicRoomInvalidAliasViewModel.context) CreateRoomScreen(context: publicRoomInvalidAliasViewModel.context)
} }
.snapshotPreferences(expect: publicRoomExistingAliasViewModel.context.$viewState.map { state in .snapshotPreferences(expect: publicRoomInvalidAliasViewModel.context.$viewState.map { !$0.aliasErrors.isEmpty })
!state.aliasErrors.isEmpty
})
.previewDisplayName("Create Public Room, invalid alias") .previewDisplayName("Create Public Room, invalid alias")
NavigationStack { NavigationStack {
CreateRoomScreen(context: publicRoomExistingAliasViewModel.context) CreateRoomScreen(context: publicRoomExistingAliasViewModel.context)
} }
.snapshotPreferences(expect: publicRoomExistingAliasViewModel.context.$viewState.map { state in .snapshotPreferences(expect: publicRoomExistingAliasViewModel.context.$viewState.map { !$0.aliasErrors.isEmpty })
!state.aliasErrors.isEmpty
})
.previewDisplayName("Create Public Room, existing alias") .previewDisplayName("Create Public Room, existing alias")
} }
} }
@@ -11,8 +11,8 @@ import SwiftUI
struct InviteUsersScreenCoordinatorParameters { struct InviteUsersScreenCoordinatorParameters {
let userSession: UserSessionProtocol let userSession: UserSessionProtocol
let selectedUsers: CurrentValuePublisher<[UserProfileProxy], Never>? let roomProxy: JoinedRoomProxyProtocol
let roomType: InviteUsersScreenRoomType let isSkippable: Bool
let userDiscoveryService: UserDiscoveryServiceProtocol let userDiscoveryService: UserDiscoveryServiceProtocol
let userIndicatorController: UserIndicatorControllerProtocol let userIndicatorController: UserIndicatorControllerProtocol
let appSettings: AppSettings let appSettings: AppSettings
@@ -20,7 +20,6 @@ struct InviteUsersScreenCoordinatorParameters {
enum InviteUsersScreenCoordinatorAction { enum InviteUsersScreenCoordinatorAction {
case dismiss case dismiss
case proceed(selectedUsers: [UserProfileProxy])
} }
final class InviteUsersScreenCoordinator: CoordinatorProtocol { final class InviteUsersScreenCoordinator: CoordinatorProtocol {
@@ -34,8 +33,8 @@ final class InviteUsersScreenCoordinator: CoordinatorProtocol {
init(parameters: InviteUsersScreenCoordinatorParameters) { init(parameters: InviteUsersScreenCoordinatorParameters) {
viewModel = InviteUsersScreenViewModel(userSession: parameters.userSession, viewModel = InviteUsersScreenViewModel(userSession: parameters.userSession,
selectedUsers: parameters.selectedUsers, roomProxy: parameters.roomProxy,
roomType: parameters.roomType, isSkippable: parameters.isSkippable,
userDiscoveryService: parameters.userDiscoveryService, userDiscoveryService: parameters.userDiscoveryService,
userIndicatorController: parameters.userIndicatorController, userIndicatorController: parameters.userIndicatorController,
appSettings: parameters.appSettings) appSettings: parameters.appSettings)
@@ -47,8 +46,6 @@ final class InviteUsersScreenCoordinator: CoordinatorProtocol {
switch action { switch action {
case .dismiss: case .dismiss:
actionsSubject.send(.dismiss) actionsSubject.send(.dismiss)
case .proceed(let selectedUsers):
actionsSubject.send(.proceed(selectedUsers: selectedUsers))
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
@@ -16,7 +16,6 @@ enum InviteUsersScreenErrorType: Error {
enum InviteUsersScreenViewModelAction { enum InviteUsersScreenViewModelAction {
case dismiss case dismiss
case proceed(selectedUsers: [UserProfileProxy])
} }
enum InviteUsersScreenRoomType { enum InviteUsersScreenRoomType {
@@ -53,18 +52,18 @@ struct InviteUsersScreenViewState: BindableState {
membershipState[user.userID] membershipState[user.userID]
} }
let isCreatingRoom: Bool let isSkippable: Bool
var actionText: String { var actionText: String {
if isCreatingRoom { if isSkippable, selectedUsers.isEmpty {
return selectedUsers.isEmpty ? L10n.actionSkip : L10n.actionNext L10n.actionSkip
} else { } else {
return L10n.actionInvite L10n.actionInvite
} }
} }
var isActionDisabled: Bool { var isActionDisabled: Bool {
isCreatingRoom ? false : selectedUsers.isEmpty isSkippable ? false : selectedUsers.isEmpty
} }
} }
@@ -13,7 +13,7 @@ import SwiftUI
typealias InviteUsersScreenViewModelType = StateStoreViewModel<InviteUsersScreenViewState, InviteUsersScreenViewAction> typealias InviteUsersScreenViewModelType = StateStoreViewModel<InviteUsersScreenViewState, InviteUsersScreenViewAction>
class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScreenViewModelProtocol { class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScreenViewModelProtocol {
private let roomType: InviteUsersScreenRoomType private let roomProxy: JoinedRoomProxyProtocol
private let userDiscoveryService: UserDiscoveryServiceProtocol private let userDiscoveryService: UserDiscoveryServiceProtocol
private let userIndicatorController: UserIndicatorControllerProtocol private let userIndicatorController: UserIndicatorControllerProtocol
private let appSettings: AppSettings private let appSettings: AppSettings
@@ -25,22 +25,19 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
actionsSubject.eraseToAnyPublisher() actionsSubject.eraseToAnyPublisher()
} }
private let selectedUsers: CurrentValuePublisher<[UserProfileProxy], Never>?
init(userSession: UserSessionProtocol, init(userSession: UserSessionProtocol,
selectedUsers: CurrentValuePublisher<[UserProfileProxy], Never>?, roomProxy: JoinedRoomProxyProtocol,
roomType: InviteUsersScreenRoomType, isSkippable: Bool,
userDiscoveryService: UserDiscoveryServiceProtocol, userDiscoveryService: UserDiscoveryServiceProtocol,
userIndicatorController: UserIndicatorControllerProtocol, userIndicatorController: UserIndicatorControllerProtocol,
appSettings: AppSettings) { appSettings: AppSettings) {
self.roomType = roomType self.roomProxy = roomProxy
self.userDiscoveryService = userDiscoveryService self.userDiscoveryService = userDiscoveryService
self.userIndicatorController = userIndicatorController self.userIndicatorController = userIndicatorController
self.appSettings = appSettings self.appSettings = appSettings
self.selectedUsers = selectedUsers
super.init(initialViewState: InviteUsersScreenViewState(selectedUsers: selectedUsers?.value ?? [], super.init(initialViewState: InviteUsersScreenViewState(selectedUsers: [],
isCreatingRoom: roomType.isCreatingRoom), isSkippable: isSkippable),
mediaProvider: userSession.mediaProvider) mediaProvider: userSession.mediaProvider)
setupSubscriptions() setupSubscriptions()
@@ -62,12 +59,7 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
case .cancel: case .cancel:
actionsSubject.send(.dismiss) actionsSubject.send(.dismiss)
case .proceed: case .proceed:
switch roomType { inviteUsers(state.selectedUsers.map(\.userID), roomProxy: roomProxy)
case .draft:
actionsSubject.send(.proceed(selectedUsers: state.selectedUsers))
case .room(let roomProxy):
inviteUsers(state.selectedUsers.map(\.userID), roomProxy: roomProxy)
}
case .toggleUser(let user): case .toggleUser(let user):
toggleUser(user) toggleUser(user)
} }
@@ -78,7 +70,7 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
private func toggleUser(_ user: UserProfileProxy) { private func toggleUser(_ user: UserProfileProxy) {
if state.selectedUsers.contains(user) { if state.selectedUsers.contains(user) {
state.scrollToLastID = nil state.scrollToLastID = nil
state.selectedUsers.removeAll(where: { $0.userID == user.userID }) state.selectedUsers.removeAll { $0.userID == user.userID }
} else { } else {
state.scrollToLastID = user.userID state.scrollToLastID = user.userID
state.selectedUsers.append(user) state.selectedUsers.append(user)
@@ -87,15 +79,14 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
private func inviteUsers(_ users: [String], roomProxy: JoinedRoomProxyProtocol) { private func inviteUsers(_ users: [String], roomProxy: JoinedRoomProxyProtocol) {
if appSettings.enableKeyShareOnInvite { if appSettings.enableKeyShareOnInvite {
showLoader(title: L10n.screenRoomDetailsInvitePeoplePreparing, showLoadingIndicator(title: L10n.screenRoomDetailsInvitePeoplePreparing, message: L10n.screenRoomDetailsInvitePeopleDontClose)
message: L10n.screenRoomDetailsInvitePeopleDontClose)
} else { } else {
showLoader() showLoadingIndicator()
} }
Task { Task {
defer { defer {
hideLoader() hideLoadingIndicator()
actionsSubject.send(.dismiss) actionsSubject.send(.dismiss)
} }
@@ -122,7 +113,7 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
} }
private func buildMembershipStateIfNeeded(members: [RoomMemberProxyProtocol]) { private func buildMembershipStateIfNeeded(members: [RoomMemberProxyProtocol]) {
showLoader() showLoadingIndicator()
Task.detached { [members] in Task.detached { [members] in
// accessing RoomMember's properties is very slow. We need to do it in a background thread. // accessing RoomMember's properties is very slow. We need to do it in a background thread.
@@ -133,7 +124,7 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
Task { @MainActor in Task { @MainActor in
self.state.membershipState = membershipState self.state.membershipState = membershipState
self.hideLoader() self.hideLoadingIndicator()
} }
} }
} }
@@ -150,25 +141,13 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
self?.fetchUsers() self?.fetchUsers()
} }
.store(in: &cancellables) .store(in: &cancellables)
if let selectedUsers {
selectedUsers
.sink { [weak self] users in
self?.state.selectedUsers = users
}
.store(in: &cancellables)
}
} }
private func fetchMembersIfNeeded() { private func fetchMembersIfNeeded() {
guard case let .room(roomProxy) = roomType else {
return
}
Task { Task {
showLoader() showLoadingIndicator()
await roomProxy.updateMembers() await roomProxy.updateMembers()
hideLoader() hideLoadingIndicator()
} }
roomProxy.membersPublisher roomProxy.membersPublisher
@@ -211,8 +190,8 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
private let userIndicatorID = UUID().uuidString private let userIndicatorID = UUID().uuidString
private func showLoader(title: String = L10n.commonLoading, private func showLoadingIndicator(title: String = L10n.commonLoading,
message: String? = nil) { message: String? = nil) {
userIndicatorController.submitIndicator(UserIndicator(id: userIndicatorID, userIndicatorController.submitIndicator(UserIndicator(id: userIndicatorID,
type: .modal, type: .modal,
title: title, title: title,
@@ -221,18 +200,7 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
delay: .milliseconds(200)) delay: .milliseconds(200))
} }
private func hideLoader() { private func hideLoadingIndicator() {
userIndicatorController.retractIndicatorWithId(userIndicatorID) userIndicatorController.retractIndicatorWithId(userIndicatorID)
} }
} }
private extension InviteUsersScreenRoomType {
var isCreatingRoom: Bool {
switch self {
case .draft:
return true
case .room:
return false
}
}
}
@@ -32,6 +32,7 @@ struct InviteUsersScreen: View {
accessibilityFocusOnStart: true) accessibilityFocusOnStart: true)
.compoundSearchField() .compoundSearchField()
.alert(item: $context.alertInfo) .alert(item: $context.alertInfo)
.navigationBarBackButtonHidden(context.viewState.isSkippable)
} }
// MARK: - Private // MARK: - Private
@@ -126,7 +127,7 @@ struct InviteUsersScreen: View {
@ToolbarContentBuilder @ToolbarContentBuilder
private var toolbar: some ToolbarContent { private var toolbar: some ToolbarContent {
if !context.viewState.isCreatingRoom { if !context.viewState.isSkippable {
ToolbarItem(placement: .cancellationAction) { ToolbarItem(placement: .cancellationAction) {
Button(L10n.actionCancel) { Button(L10n.actionCancel) {
context.send(viewAction: .cancel) context.send(viewAction: .cancel)
@@ -155,8 +156,8 @@ struct InviteUsersScreen_Previews: PreviewProvider, TestablePreview {
let userDiscoveryService = UserDiscoveryServiceMock() let userDiscoveryService = UserDiscoveryServiceMock()
userDiscoveryService.searchProfilesWithReturnValue = .success([.mockAlice]) userDiscoveryService.searchProfilesWithReturnValue = .success([.mockAlice])
return InviteUsersScreenViewModel(userSession: UserSessionMock(.init()), return InviteUsersScreenViewModel(userSession: UserSessionMock(.init()),
selectedUsers: .init([]), roomProxy: JoinedRoomProxyMock(.init()),
roomType: .draft, isSkippable: true,
userDiscoveryService: userDiscoveryService, userDiscoveryService: userDiscoveryService,
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
appSettings: ServiceLocator.shared.settings) appSettings: ServiceLocator.shared.settings)
@@ -10,38 +10,26 @@ import Combine
import SwiftUI import SwiftUI
struct StartChatScreenCoordinatorParameters { struct StartChatScreenCoordinatorParameters {
let orientationManager: OrientationManagerProtocol
let userSession: UserSessionProtocol let userSession: UserSessionProtocol
let userIndicatorController: UserIndicatorControllerProtocol
weak var navigationStackCoordinator: NavigationStackCoordinator?
let userDiscoveryService: UserDiscoveryServiceProtocol let userDiscoveryService: UserDiscoveryServiceProtocol
let mediaUploadingPreprocessor: MediaUploadingPreprocessor let userIndicatorController: UserIndicatorControllerProtocol
let appSettings: AppSettings let appSettings: AppSettings
let analytics: AnalyticsService let analytics: AnalyticsService
} }
enum StartChatScreenCoordinatorAction { enum StartChatScreenCoordinatorAction {
case close case close
case openRoom(withIdentifier: String) case createRoom
case openRoom(roomID: String)
case openRoomDirectorySearch case openRoomDirectorySearch
} }
final class StartChatScreenCoordinator: CoordinatorProtocol { final class StartChatScreenCoordinator: CoordinatorProtocol {
private let parameters: StartChatScreenCoordinatorParameters private let parameters: StartChatScreenCoordinatorParameters
private var viewModel: StartChatScreenViewModelProtocol private var viewModel: StartChatScreenViewModelProtocol
private let actionsSubject: PassthroughSubject<StartChatScreenCoordinatorAction, Never> = .init()
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
private var createRoomParameters = CurrentValueSubject<CreateRoomFlowParameters, Never>(.init())
private var createRoomParametersPublisher: CurrentValuePublisher<CreateRoomFlowParameters, Never> {
createRoomParameters.asCurrentValuePublisher()
}
private let selectedUsers = CurrentValueSubject<[UserProfileProxy], Never>([])
private var selectedUsersPublisher: CurrentValuePublisher<[UserProfileProxy], Never> {
selectedUsers.asCurrentValuePublisher()
}
private let actionsSubject: PassthroughSubject<StartChatScreenCoordinatorAction, Never> = .init()
var actions: AnyPublisher<StartChatScreenCoordinatorAction, Never> { var actions: AnyPublisher<StartChatScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher() actionsSubject.eraseToAnyPublisher()
} }
@@ -63,10 +51,9 @@ final class StartChatScreenCoordinator: CoordinatorProtocol {
case .close: case .close:
actionsSubject.send(.close) actionsSubject.send(.close)
case .createRoom: case .createRoom:
// before creating a room we select the users we would like to invite in that room actionsSubject.send(.createRoom)
presentInviteUsersScreen() case .showRoom(let roomID):
case .showRoom(let identifier): actionsSubject.send(.openRoom(roomID: roomID))
actionsSubject.send(.openRoom(withIdentifier: identifier))
case .openRoomDirectorySearch: case .openRoomDirectorySearch:
actionsSubject.send(.openRoomDirectorySearch) actionsSubject.send(.openRoomDirectorySearch)
} }
@@ -79,126 +66,4 @@ final class StartChatScreenCoordinator: CoordinatorProtocol {
func toPresentable() -> AnyView { func toPresentable() -> AnyView {
AnyView(StartChatScreen(context: viewModel.context)) AnyView(StartChatScreen(context: viewModel.context))
} }
// MARK: - Private
private func presentInviteUsersScreen() {
let inviteParameters = InviteUsersScreenCoordinatorParameters(userSession: parameters.userSession,
selectedUsers: selectedUsersPublisher,
roomType: .draft,
userDiscoveryService: parameters.userDiscoveryService,
userIndicatorController: parameters.userIndicatorController,
appSettings: parameters.appSettings)
let coordinator = InviteUsersScreenCoordinator(parameters: inviteParameters)
coordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .dismiss:
fatalError("Not shown in this flow.")
case .proceed(let selectedUsers):
self.selectedUsers.send(selectedUsers)
openCreateRoomScreen()
}
}
.store(in: &cancellables)
parameters.navigationStackCoordinator?.push(coordinator) { [weak self] in
self?.createRoomParameters.send(.init())
self?.selectedUsers.send([])
}
}
private func openCreateRoomScreen() {
let createParameters = CreateRoomCoordinatorParameters(userSession: parameters.userSession,
userIndicatorController: parameters.userIndicatorController,
createRoomParameters: createRoomParametersPublisher,
selectedUsers: selectedUsers.value,
appSettings: parameters.appSettings,
analytics: parameters.analytics)
let coordinator = CreateRoomCoordinator(parameters: createParameters)
coordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .updateSelectedUsers(let users):
self.selectedUsers.send(users)
case .updateDetails(let details):
self.createRoomParameters.send(details)
case .openRoom(let identifier):
self.actionsSubject.send(.openRoom(withIdentifier: identifier))
case .displayMediaPickerWithMode(let mode):
self.displayMediaPickerWithMode(mode)
case .removeImage:
var parameters = self.createRoomParameters.value
parameters.avatarImageMedia = nil
self.createRoomParameters.send(parameters)
}
}
.store(in: &cancellables)
parameters.navigationStackCoordinator?.push(coordinator)
}
// MARK: - Private
private func displayMediaPickerWithMode(_ mode: MediaPickerScreenMode) {
let stackCoordinator = NavigationStackCoordinator()
let mediaPickerCoordinator = MediaPickerScreenCoordinator(mode: mode,
userIndicatorController: parameters.userIndicatorController,
orientationManager: parameters.orientationManager) { [weak self] action in
guard let self else { return }
switch action {
case .cancel:
parameters.navigationStackCoordinator?.setSheetCoordinator(nil)
case .selectedMediaAtURLs(let urls):
guard urls.count == 1,
let url = urls.first else {
fatalError("Received an invalid number of URLs")
}
processAvatar(from: url)
}
}
stackCoordinator.setRootCoordinator(mediaPickerCoordinator)
parameters.navigationStackCoordinator?.setSheetCoordinator(stackCoordinator)
}
private func processAvatar(from url: URL) {
parameters.navigationStackCoordinator?.setSheetCoordinator(nil)
showLoadingIndicator()
Task { [weak self] in
guard let self else { return }
do {
guard case let .success(maxUploadSize) = await parameters.userSession.clientProxy.maxMediaUploadSize else {
MXLog.error("Failed to get max upload size")
parameters.userIndicatorController.alertInfo = AlertInfo(id: .init())
return
}
let media = try await parameters.mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize).get()
var parameters = createRoomParameters.value
parameters.avatarImageMedia = media
createRoomParameters.send(parameters)
} catch {
parameters.userIndicatorController.alertInfo = AlertInfo(id: .init())
}
hideLoadingIndicator()
}
}
// MARK: Loading indicator
private static let loadingIndicatorIdentifier = "\(StartChatScreenCoordinator.self)-Loading"
private func showLoadingIndicator() {
parameters.userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,
type: .modal,
title: L10n.commonLoading,
persistent: true))
}
private func hideLoadingIndicator() {
parameters.userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier)
}
} }
@@ -16,7 +16,7 @@ enum StartChatScreenErrorType: Error {
enum StartChatScreenViewModelAction: Equatable { enum StartChatScreenViewModelAction: Equatable {
case close case close
case createRoom case createRoom
case showRoom(withIdentifier: String) case showRoom(roomID: String)
case openRoomDirectorySearch case openRoomDirectorySearch
} }
@@ -65,7 +65,7 @@ class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenVie
switch currentDirectRoom { switch currentDirectRoom {
case .success(.some(let roomId)): case .success(.some(let roomId)):
hideLoadingIndicator() hideLoadingIndicator()
actionsSubject.send(.showRoom(withIdentifier: roomId)) actionsSubject.send(.showRoom(roomID: roomId))
case .success: case .success:
hideLoadingIndicator() hideLoadingIndicator()
state.bindings.selectedUserToInvite = user state.bindings.selectedUserToInvite = user
@@ -190,7 +190,7 @@ class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenVie
switch await userSession.clientProxy.createDirectRoom(with: user.userID, expectedRoomName: user.displayName) { switch await userSession.clientProxy.createDirectRoom(with: user.userID, expectedRoomName: user.displayName) {
case .success(let roomId): case .success(let roomId):
analytics.trackCreatedRoom(isDM: true) analytics.trackCreatedRoom(isDM: true)
actionsSubject.send(.showRoom(withIdentifier: roomId)) actionsSubject.send(.showRoom(roomID: roomId))
case .failure: case .failure:
displayError() displayError()
} }
@@ -205,7 +205,7 @@ class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenVie
private func joinRoomByAddress() { private func joinRoomByAddress() {
if case let .addressFound(lastTestedAddress, roomID) = internalRoomAddressState, if case let .addressFound(lastTestedAddress, roomID) = internalRoomAddressState,
lastTestedAddress == state.bindings.roomAddress { lastTestedAddress == state.bindings.roomAddress {
actionsSubject.send(.showRoom(withIdentifier: roomID)) actionsSubject.send(.showRoom(roomID: roomID))
} else if let resolveAliasTask { } else if let resolveAliasTask {
// If the task is still running we wait for it to complete and we check the state again // If the task is still running we wait for it to complete and we check the state again
showLoadingIndicator(delay: .milliseconds(250)) showLoadingIndicator(delay: .milliseconds(250))
@@ -1,19 +0,0 @@
//
// Copyright 2025 Element Creations Ltd.
// Copyright 2023-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 Foundation
/// This parameters are only used in the create room flow for having persisted informations between screens
struct CreateRoomFlowParameters {
var name = ""
var topic = ""
var isRoomPrivate = true
var isKnockingOnly = false
var avatarImageMedia: MediaInfo?
var aliasLocalPart: String?
}
@@ -648,67 +648,48 @@ class MockScreen: Identifiable {
retainedState.append(coordinator) retainedState.append(coordinator)
coordinator.start() coordinator.start()
return navigationStackCoordinator return navigationStackCoordinator
case .startChat: case .startChatFlow:
let navigationStackCoordinator = NavigationStackCoordinator()
let userDiscoveryMock = UserDiscoveryServiceMock()
userDiscoveryMock.searchProfilesWithReturnValue = .success([])
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "@mock:client.com"))))
let parameters: StartChatScreenCoordinatorParameters = .init(orientationManager: OrientationManagerMock(),
userSession: userSession,
userIndicatorController: UserIndicatorControllerMock(),
navigationStackCoordinator: navigationStackCoordinator,
userDiscoveryService: userDiscoveryMock,
mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: ServiceLocator.shared.settings),
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics)
let coordinator = StartChatScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .startChatWithSearchResults:
let navigationStackCoordinator = NavigationStackCoordinator()
let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com")) let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com"))
let userDiscoveryMock = UserDiscoveryServiceMock() clientProxy.createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartReturnValue = .success("!new-room:client.com")
userDiscoveryMock.searchProfilesWithReturnValue = .success([.mockBob, .mockBobby]) clientProxy.roomForIdentifierClosure = { roomID in .joined(JoinedRoomProxyMock(.init(id: roomID, members: []))) }
let userSession = UserSessionMock(.init(clientProxy: clientProxy))
let coordinator = StartChatScreenCoordinator(parameters: .init(orientationManager: OrientationManagerMock(), let userDiscoveryService = UserDiscoveryServiceMock()
userSession: userSession, userDiscoveryService.searchProfilesWithReturnValue = .success([.mockBob, .mockBobby])
userIndicatorController: UserIndicatorControllerMock(),
navigationStackCoordinator: navigationStackCoordinator,
userDiscoveryService: userDiscoveryMock,
mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: ServiceLocator.shared.settings),
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics))
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .createRoom:
let navigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com")) let flowCoordinator = StartChatFlowCoordinator(userDiscoveryService: userDiscoveryService,
let mockUserSession = UserSessionMock(.init(clientProxy: clientProxy)) navigationStackCoordinator: navigationStackCoordinator,
let createRoomParameters = CreateRoomFlowParameters() flowParameters: CommonFlowParameters(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
let selectedUsers: [UserProfileProxy] = [.mockAlice, .mockBob, .mockCharlie] bugReportService: BugReportServiceMock(.init()),
let parameters = CreateRoomCoordinatorParameters(userSession: mockUserSession, elementCallService: ElementCallServiceMock(.init()),
userIndicatorController: UserIndicatorControllerMock(), timelineControllerFactory: TimelineControllerFactoryMock(.init()),
createRoomParameters: .init(createRoomParameters), emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
selectedUsers: .init(selectedUsers), linkMetadataProvider: LinkMetadataProvider(),
appSettings: ServiceLocator.shared.settings, appMediator: AppMediatorMock.default,
analytics: ServiceLocator.shared.analytics) appSettings: ServiceLocator.shared.settings,
let coordinator = CreateRoomCoordinator(parameters: parameters) appHooks: AppHooks(),
navigationStackCoordinator.setRootCoordinator(coordinator) analytics: ServiceLocator.shared.analytics,
return navigationStackCoordinator userIndicatorController: UserIndicatorControllerMock(),
case .createRoomNoUsers: notificationManager: NotificationManagerMock(),
let navigationStackCoordinator = NavigationStackCoordinator() stateMachineFactory: StateMachineFactory()))
let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com")) flowCoordinator.actionsPublisher
let mockUserSession = UserSessionMock(.init(clientProxy: clientProxy)) .sink { [weak self] action in
let createRoomParameters = CreateRoomFlowParameters() guard let self else { return }
let parameters = CreateRoomCoordinatorParameters(userSession: mockUserSession, switch action {
userIndicatorController: UserIndicatorControllerMock(), case .finished(let roomID):
createRoomParameters: .init(createRoomParameters), navigationRootCoordinator.setSheetCoordinator(nil)
selectedUsers: .init([]), case .showRoomDirectory:
appSettings: ServiceLocator.shared.settings, break // The test doesn't cover this.
analytics: ServiceLocator.shared.analytics) }
let coordinator = CreateRoomCoordinator(parameters: parameters) }
navigationStackCoordinator.setRootCoordinator(coordinator) .store(in: &cancellables)
return navigationStackCoordinator
retainedState.append(flowCoordinator)
flowCoordinator.start()
// Use a sheet on top the the placeholder so we can test the dismissal.
navigationRootCoordinator.setSheetCoordinator(navigationStackCoordinator)
return PlaceholderScreenCoordinator(hideBrandChrome: false)
case .createPoll: case .createPoll:
let navigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = PollFormScreenCoordinator(parameters: .init(mode: .new, let coordinator = PollFormScreenCoordinator(parameters: .init(mode: .new,
@@ -22,8 +22,6 @@ enum UITestsScreenIdentifier: String {
case multipleProvidersAuthenticationFlow case multipleProvidersAuthenticationFlow
case bugReport case bugReport
case createPoll case createPoll
case createRoom
case createRoomNoUsers
case encryptionSettings case encryptionSettings
case encryptionSettingsOutOfSync case encryptionSettingsOutOfSync
case encryptionReset case encryptionReset
@@ -44,8 +42,7 @@ enum UITestsScreenIdentifier: String {
case roomWithUndisclosedPolls case roomWithUndisclosedPolls
case serverSelection case serverSelection
case sessionVerification case sessionVerification
case startChat case startChatFlow
case startChatWithSearchResults
case userSessionScreen case userSessionScreen
case userSessionScreenReply case userSessionScreenReply
case userSessionSpacesFlow case userSessionSpacesFlow
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e1624d4be85be4801b7a407ed8d09c815d98b1eb4422d4fc8095795e3a73cd17 oid sha256:27b070d92b8846ab416aeb3964d3b698b15aae6a6a5e03f103546bc00b57a291
size 144918 size 144359
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:5d668d5fd1d22b0fc782dcf22c1d674fae87d61b289451887e48011ea2c288a9 oid sha256:96418567c90c7e7a42b4a7129cc803ce0f50de55cb14021fe8c9b5042810c4e4
size 173173 size 171338
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:8e3e2883875feedec7a243f676918e3d6f4d05826702a8f2c2807fc5a0bc7bdf oid sha256:209bc72d50cf549621b1bb690349290aa9f12fd79501cc3089b1dc0a525495c2
size 96282 size 95602
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:fd0da3fb9239fe4f131b4a19057bd49bda67b72ba7caf17d8f86cb9f6a239fc0 oid sha256:a9f24fc08ec1f408042f6311ddada15f01413f93806bba7742b417743c8cf011
size 125320 size 125251
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:27b070d92b8846ab416aeb3964d3b698b15aae6a6a5e03f103546bc00b57a291
size 144359
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:96418567c90c7e7a42b4a7129cc803ce0f50de55cb14021fe8c9b5042810c4e4
size 171338
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:209bc72d50cf549621b1bb690349290aa9f12fd79501cc3089b1dc0a525495c2
size 95602
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a9f24fc08ec1f408042f6311ddada15f01413f93806bba7742b417743c8cf011
size 125251
@@ -1,33 +0,0 @@
//
// Copyright 2025 Element Creations Ltd.
// Copyright 2022-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 XCTest
@MainActor
class CreateRoomScreenUITests: XCTestCase {
func testLanding() async throws {
let app = Application.launch(.createRoom)
try await app.assertScreenshot()
}
func testLandingWithoutUsers() async throws {
let app = Application.launch(.createRoomNoUsers)
try await app.assertScreenshot()
}
func testLongInputNameText() async throws {
let app = Application.launch(.createRoom)
// typeText sometimes misses letters but it's faster than typing one letter at a time
// repeat the same letter enough times to avoid that but also to work on iPads
app.textFields[A11yIdentifiers.createRoomScreen.roomName].tap()
app.textFields[A11yIdentifiers.createRoomScreen.roomName].typeText(.init(repeating: "x", count: 200))
app.textFields[A11yIdentifiers.createRoomScreen.roomName].typeText("\n")
try await app.assertScreenshot()
}
}
@@ -1,34 +0,0 @@
//
// Copyright 2025 Element Creations Ltd.
// Copyright 2022-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 XCTest
@MainActor
class StartChatScreenUITests: XCTestCase {
func testLanding() async throws {
let app = Application.launch(.startChat)
try await app.assertScreenshot()
}
func testSearchWithNoResults() async throws {
let app = Application.launch(.startChat)
let searchField = app.searchFields.firstMatch
searchField.clearAndTypeText("None\n", app: app)
XCTAssert(app.staticTexts[A11yIdentifiers.startChatScreen.searchNoResults].waitForExistence(timeout: 1.0))
try await app.assertScreenshot()
}
func testSearchWithResults() async throws {
let app = Application.launch(.startChatWithSearchResults)
let searchField = app.searchFields.firstMatch
searchField.clearAndTypeText("Bob\n", app: app)
XCTAssertFalse(app.staticTexts[A11yIdentifiers.startChatScreen.searchNoResults].waitForExistence(timeout: 1.0))
XCTAssertEqual(app.collectionViews.firstMatch.cells.count, 2)
try await app.assertScreenshot()
}
}
+73
View File
@@ -0,0 +1,73 @@
//
// Copyright 2025 Element Creations Ltd.
// Copyright 2022-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 XCTest
@MainActor
class StartChatTests: XCTestCase {
enum Step {
static let startChat = 1
static let startChatWithResults = 2
static let createRoom = 3
static let createRoomAvatarPicker = 4
static let createRoomFilled = 5
static let inviteUsers = 6
static let inviteUsersWithResults = 7
static let inviteUsersSelectedResults = 8
static let dismissed = 99
}
func testFlow() async throws {
let app = Application.launch(.startChatFlow)
try await app.assertScreenshot(step: Step.startChat)
let startChatSearchField = app.searchFields.firstMatch
startChatSearchField.clearAndTypeText("Bob\n", app: app)
XCTAssertFalse(app.staticTexts[A11yIdentifiers.startChatScreen.searchNoResults].waitForExistence(timeout: 1.0))
XCTAssertEqual(app.collectionViews.firstMatch.cells.count, 2)
try await app.assertScreenshot(step: Step.startChatWithResults)
startChatSearchField.clearAndTypeText("\n", app: app)
app.buttons[A11yIdentifiers.startChatScreen.createRoom].firstMatch.tap()
XCTAssertTrue(app.textFields[A11yIdentifiers.createRoomScreen.roomName].waitForExistence(timeout: 1.0))
try await app.assertScreenshot(step: Step.createRoom)
app.buttons[A11yIdentifiers.createRoomScreen.roomAvatar].tap()
app.popovers.buttons.element(boundBy: 2).tap() // There are 2 buttons with the accessibility identifier, so use the index to get the right one.
let cancelButton = app.buttons["Cancel"]
XCTAssertTrue(cancelButton.waitForExistence(timeout: 1.0))
try await app.assertScreenshot(step: Step.createRoomAvatarPicker)
cancelButton.tap()
// typeText sometimes misses letters but it's faster than typing one letter at a time
// repeat the same letter enough times to avoid that but also to work on iPads
app.textFields[A11yIdentifiers.createRoomScreen.roomName].tap()
app.textFields[A11yIdentifiers.createRoomScreen.roomName].typeText(.init(repeating: "x", count: 200))
app.textFields[A11yIdentifiers.createRoomScreen.roomName].typeText("\n")
try await app.assertScreenshot(step: Step.createRoomFilled)
app.buttons[A11yIdentifiers.createRoomScreen.create].tap()
XCTAssertTrue(app.buttons[A11yIdentifiers.inviteUsersScreen.proceed].waitForExistence(timeout: 1.0))
try await app.assertScreenshot(step: Step.inviteUsers)
let inviteUsersSearchField = app.searchFields.firstMatch
inviteUsersSearchField.clearAndTypeText("Bob\n", app: app)
let cells = app.collectionViews.firstMatch.cells
XCTAssertEqual(cells.count, 2)
try await app.assertScreenshot(step: Step.inviteUsersWithResults)
cells.element(boundBy: 0).tap()
// Selecting the first member has inserted the horizontal collection view above the list.
// So to select the second member we now need to increment the index by 2 instead of 1.
cells.element(boundBy: 2).tap()
try await app.assertScreenshot(step: Step.inviteUsersSelectedResults)
app.buttons[A11yIdentifiers.inviteUsersScreen.proceed].tap()
try await app.assertScreenshot(step: Step.dismissed)
}
}
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:71edb284338fe1a3d118141aa237617536b72a31d13333e7148dd2d33ccff1b1
size 152332
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9c6fcc3f0eea9bb87f6b5fa2c5b652496ca44eee846d3231e4c1446a1a3da2e3
size 188761
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c7a86c8bd9795786c49bace089439a3e8e0677bae255eba332b2175e89ab3f2b
size 131545
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:660beea7152721b0ba27876b1f7d9e34035c21abc188c3ab3953552655041dca
size 160050
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2e8ebe176c64660475fdb46016a66ddaa502d30d1c60f6ccc4a52c05ca5412ed
size 149193
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4afad7f48503b599fb30a0e4c593079249d6c70f64770d53ce63398a89d245af
size 183810
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a1f07788317f9ed52b340b6e3233d50be4f82a047e6233d6abc656c6e1ba0c65
size 167782
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9543933266405ba35494e5bac5e1b403b0f388657b36b1689a3f27834dd27133
size 164831
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5201d5b8492dadaa23efd16a5e74ca96e23ce972abe4cd76a20533246a9381af
size 198236
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d0f8aff85f5930db29bc103b67ae2ea3bd793e6da755b74b3ec4bc3294a24664
size 2332509
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:be0bc089efe8628afef744ec9e171a1d3f54b5ac81f3515369a58237c553da0a
size 196815
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:02b9e0a8a3ae860c6afe7541021ec11529fb810f13ed7468ed4fbb87d7277e3e
size 147056
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6e2ec6a46ee642ad8235dc6cb777330bdeb8a240138409fa88fa9d1f3cc7516b
size 166249
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:abe91d1332e9da181baa07576006a411ad36aa23dcd664341f9c1cf1e28c4a57
size 175690
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:048a07df37bc493d91f67b5e1379ef571db047ab35780b28c23ee1990429846f
size 222243
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:10eb8f7c10d0eb6a493c33fc223a96276897f5a8db969d7e07694c9872b47485
size 122115
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5bebd60c5ddaaf7d4af54563587c51f44816ac8fe5a621632a14374ca639b66b
size 114369
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f6a481a1eebb2bdc2a51b69da7867d330fd15b465ec66dda9a7775ea81bebc27
size 170910
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c0f51cb1e35bb75ae45f2f909e45f809febe00c86f7b7d88e63e9cdfb322043b
size 2458946
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ab217745f60435fc6251221d5650a25e4ce00153cf78ed72021e05c666d19703
size 166171
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7899f024d4aa043b2e318ef188bb992301aa02bc4a92fd5b59215e907272bde2
size 88945
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b788bbcc940303b16942de95cfcbb142e04ecc60634e409f091a20cf3fe8cef1
size 116322
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:125138db4c413fa5ffc104694cb5400373d86faee69d0c755e2ffe992211f0ab
size 128890
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:47d79566040b9e990cd222dc056d22de86723171364d60a0fdc3592dff9bb48e
size 374177
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d3e4caed4cef8b0ad70f747bb98a7e205c0b4f1c3df6083f341a9ac5bebe6e31
size 108047
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:63a31b32e399472a8206f234665a11bb929403b5902856b85cd4f19373d0fd0a
size 120156
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3156d4c72c601cfbe7584cd73f516a4a32a95e6149dbd7ef31fdd4bba7d14fbf
size 88278
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8744cb36bbdd2afb7809f2682e00d6a3f2d1f3e2e6dfa329a50e97c76d8e9e63
size 90212
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b97677b7315b1d9298468da64358299ef0674644d3042fe23d9e10396131c92a
size 101718
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dd670a1f21b71bf5ddc7d317054145c2a049eb16fa09d6cd6f8be589d5ec45e9
size 112321
@@ -13,38 +13,28 @@ import XCTest
@MainActor @MainActor
class CreateRoomScreenViewModelTests: XCTestCase { class CreateRoomScreenViewModelTests: XCTestCase {
var viewModel: CreateRoomViewModelProtocol! var viewModel: CreateRoomScreenViewModelProtocol!
var clientProxy: ClientProxyMock! var clientProxy: ClientProxyMock!
var userSession: UserSessionMock! var userSession: UserSessionMock!
private let usersSubject = CurrentValueSubject<[UserProfileProxy], Never>([]) private let usersSubject = CurrentValueSubject<[UserProfileProxy], Never>([])
var context: CreateRoomViewModel.Context { var context: CreateRoomScreenViewModel.Context {
viewModel.context viewModel.context
} }
override func setUpWithError() throws { override func setUpWithError() throws {
clientProxy = ClientProxyMock(.init(userIDServerName: "matrix.org", userID: "@a:b.com")) clientProxy = ClientProxyMock(.init(userIDServerName: "matrix.org", userID: "@a:b.com"))
clientProxy.roomForIdentifierClosure = { roomID in .joined(JoinedRoomProxyMock(.init(id: roomID))) }
userSession = UserSessionMock(.init(clientProxy: clientProxy)) userSession = UserSessionMock(.init(clientProxy: clientProxy))
let parameters = CreateRoomFlowParameters()
ServiceLocator.shared.settings.knockingEnabled = true ServiceLocator.shared.settings.knockingEnabled = true
let viewModel = CreateRoomViewModel(userSession: userSession, let viewModel = CreateRoomScreenViewModel(userSession: userSession,
createRoomParameters: .init(parameters), analytics: ServiceLocator.shared.analytics,
selectedUsers: [.mockAlice, .mockBob, .mockCharlie], userIndicatorController: UserIndicatorControllerMock(),
analytics: ServiceLocator.shared.analytics, appSettings: ServiceLocator.shared.settings)
userIndicatorController: UserIndicatorControllerMock(),
appSettings: ServiceLocator.shared.settings)
self.viewModel = viewModel self.viewModel = viewModel
} }
func testDeselectUser() {
XCTAssertFalse(context.viewState.selectedUsers.isEmpty)
XCTAssertEqual(context.viewState.selectedUsers.count, 3)
XCTAssertEqual(context.viewState.selectedUsers.first?.userID, UserProfileProxy.mockAlice.userID)
context.send(viewAction: .deselectUser(.mockAlice))
XCTAssertNotEqual(context.viewState.selectedUsers.first?.userID, UserProfileProxy.mockAlice.userID)
}
func testDefaultSecurity() { func testDefaultSecurity() {
XCTAssertTrue(context.viewState.bindings.isRoomPrivate) XCTAssertTrue(context.viewState.bindings.isRoomPrivate)
} }
@@ -65,7 +55,7 @@ class CreateRoomScreenViewModelTests: XCTestCase {
// When creating the room. // When creating the room.
clientProxy.createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartReturnValue = .success("1") clientProxy.createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartReturnValue = .success("1")
let deferred = deferFulfillment(viewModel.actions) { action in let deferred = deferFulfillment(viewModel.actions) { action in
guard case .openRoom("1") = action else { return false } guard case .createdRoom(let roomProxy) = action, roomProxy.id == "1" else { return false }
return true return true
} }
context.send(viewAction: .createRoom) context.send(viewAction: .createRoom)
@@ -21,7 +21,10 @@ class InviteUsersScreenViewModelTests: XCTestCase {
} }
func testSelectUser() { func testSelectUser() {
setupWithRoomType(roomType: .draft) let roomProxy = JoinedRoomProxyMock(.init(name: "newroom", members: []))
roomProxy.inviteUserIDReturnValue = .success(())
setupViewModel(roomProxy: roomProxy, isSkippable: true)
XCTAssertTrue(context.viewState.selectedUsers.isEmpty) XCTAssertTrue(context.viewState.selectedUsers.isEmpty)
context.send(viewAction: .toggleUser(.mockAlice)) context.send(viewAction: .toggleUser(.mockAlice))
XCTAssertTrue(context.viewState.selectedUsers.count == 1) XCTAssertTrue(context.viewState.selectedUsers.count == 1)
@@ -29,7 +32,10 @@ class InviteUsersScreenViewModelTests: XCTestCase {
} }
func testReselectUser() { func testReselectUser() {
setupWithRoomType(roomType: .draft) let roomProxy = JoinedRoomProxyMock(.init(name: "newroom", members: []))
roomProxy.inviteUserIDReturnValue = .success(())
setupViewModel(roomProxy: roomProxy, isSkippable: true)
XCTAssertTrue(context.viewState.selectedUsers.isEmpty) XCTAssertTrue(context.viewState.selectedUsers.isEmpty)
context.send(viewAction: .toggleUser(.mockAlice)) context.send(viewAction: .toggleUser(.mockAlice))
XCTAssertEqual(context.viewState.selectedUsers.count, 1) XCTAssertEqual(context.viewState.selectedUsers.count, 1)
@@ -39,7 +45,10 @@ class InviteUsersScreenViewModelTests: XCTestCase {
} }
func testDeselectUser() { func testDeselectUser() {
setupWithRoomType(roomType: .draft) let roomProxy = JoinedRoomProxyMock(.init(name: "newroom", members: []))
roomProxy.inviteUserIDReturnValue = .success(())
setupViewModel(roomProxy: roomProxy, isSkippable: true)
XCTAssertTrue(context.viewState.selectedUsers.isEmpty) XCTAssertTrue(context.viewState.selectedUsers.isEmpty)
context.send(viewAction: .toggleUser(.mockAlice)) context.send(viewAction: .toggleUser(.mockAlice))
XCTAssertEqual(context.viewState.selectedUsers.count, 1) XCTAssertEqual(context.viewState.selectedUsers.count, 1)
@@ -52,7 +61,7 @@ class InviteUsersScreenViewModelTests: XCTestCase {
let mockedMembers: [RoomMemberProxyMock] = [.mockAlice, .mockBob] let mockedMembers: [RoomMemberProxyMock] = [.mockAlice, .mockBob]
let roomProxy = JoinedRoomProxyMock(.init(name: "test", members: mockedMembers)) let roomProxy = JoinedRoomProxyMock(.init(name: "test", members: mockedMembers))
roomProxy.inviteUserIDReturnValue = .success(()) roomProxy.inviteUserIDReturnValue = .success(())
setupWithRoomType(roomType: .room(roomProxy: roomProxy)) setupViewModel(roomProxy: roomProxy, isSkippable: false)
let deferredState = deferFulfillment(viewModel.context.$viewState) { state in let deferredState = deferFulfillment(viewModel.context.$viewState) { state in
state.isUserSelected(.mockAlice) state.isUserSelected(.mockAlice)
@@ -66,8 +75,6 @@ class InviteUsersScreenViewModelTests: XCTestCase {
switch action { switch action {
case .dismiss: case .dismiss:
return true return true
default:
return false
} }
} }
@@ -77,12 +84,12 @@ class InviteUsersScreenViewModelTests: XCTestCase {
XCTAssertEqual(roomProxy.inviteUserIDReceivedInvocations, [RoomMemberProxyMock.mockAlice.userID]) XCTAssertEqual(roomProxy.inviteUserIDReceivedInvocations, [RoomMemberProxyMock.mockAlice.userID])
} }
private func setupWithRoomType(roomType: InviteUsersScreenRoomType) { private func setupViewModel(roomProxy: JoinedRoomProxyProtocol, isSkippable: Bool) {
userDiscoveryService = UserDiscoveryServiceMock() userDiscoveryService = UserDiscoveryServiceMock()
userDiscoveryService.searchProfilesWithReturnValue = .success([]) userDiscoveryService.searchProfilesWithReturnValue = .success([])
let viewModel = InviteUsersScreenViewModel(userSession: UserSessionMock(.init()), let viewModel = InviteUsersScreenViewModel(userSession: UserSessionMock(.init()),
selectedUsers: nil, roomProxy: roomProxy,
roomType: roomType, isSkippable: isSkippable,
userDiscoveryService: userDiscoveryService, userDiscoveryService: userDiscoveryService,
userIndicatorController: UserIndicatorControllerMock(), userIndicatorController: UserIndicatorControllerMock(),
appSettings: ServiceLocator.shared.settings) appSettings: ServiceLocator.shared.settings)
@@ -56,7 +56,7 @@ class StartChatScreenViewModelTests: XCTestCase {
try await deferredViewState.fulfill() try await deferredViewState.fulfill()
let deferredAction = deferFulfillment(viewModel.actions) { action in let deferredAction = deferFulfillment(viewModel.actions) { action in
action == .showRoom(withIdentifier: "id") action == .showRoom(roomID: "id")
} }
context.send(viewAction: .joinRoomByAddress) context.send(viewAction: .joinRoomByAddress)
try await deferredAction.fulfill() try await deferredAction.fulfill()