From b3d4ed0274c41cb16aae22be52f2595a9a705840 Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Thu, 30 Oct 2025 16:12:56 +0000 Subject: [PATCH] Introduce a StartChatFlowCoordinator instead of handing a navigation stack to the Screen Coordinator. (#4674) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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… --- ElementX.xcodeproj/project.pbxproj | 100 ++++---- .../ChatsFlowCoordinator.swift | 74 +++--- .../ChatsFlowCoordinatorStateMachine.swift | 20 +- .../RoomFlowCoordinator.swift | 8 +- .../RoomMembersFlowCoordinator.swift | 6 +- .../StartChatFlowCoordinator.swift | 237 ++++++++++++++++++ .../Sources/Mocks/JoinedRoomProxyMock.swift | 1 + .../Other/AccessibilityIdentifiers.swift | 3 + .../CreateRoom/CreateRoomCoordinator.swift | 71 ------ .../CreateRoomScreenCoordinator.swift | 62 +++++ .../CreateRoomScreenModels.swift} | 23 +- .../CreateRoomScreenViewModel.swift} | 144 ++++++----- .../CreateRoomScreenViewModelProtocol.swift} | 9 +- .../View/CreateRoomScreen.swift | 107 +++----- .../InviteUsersScreenCoordinator.swift | 11 +- .../InviteUsersScreenModels.swift | 11 +- .../InviteUsersScreenViewModel.swift | 68 ++--- .../View/InviteUsersScreen.swift | 7 +- .../StartChatScreenCoordinator.swift | 149 +---------- .../StartChatScreenModels.swift | 2 +- .../StartChatScreenViewModel.swift | 6 +- .../CreateRoom/CreateRoomFlowParameters.swift | 19 -- .../UITests/UITestsAppCoordinator.swift | 99 +++----- .../UITests/UITestsScreenIdentifier.swift | 5 +- .../createRoom.Create-Room-iPad-en-GB.png | 4 +- .../createRoom.Create-Room-iPad-pseudo.png | 4 +- ...createRoom.Create-Room-iPhone-16-en-GB.png | 4 +- ...reateRoom.Create-Room-iPhone-16-pseudo.png | 4 +- ...m.Create-Room-without-users-iPad-en-GB.png | 3 - ....Create-Room-without-users-iPad-pseudo.png | 3 - ...ate-Room-without-users-iPhone-16-en-GB.png | 3 - ...te-Room-without-users-iPhone-16-pseudo.png | 3 - UITests/Sources/CreateRoomScreenTests.swift | 33 --- UITests/Sources/StartChatScreenTests.swift | 34 --- UITests/Sources/StartChatTests.swift | 73 ++++++ ...RoomScreen.testLanding-iPad-26-0-en-GB.png | 3 - ...omScreen.testLanding-iPhone-26-0-en-GB.png | 3 - ...estLandingWithoutUsers-iPad-26-0-en-GB.png | 3 - ...tLandingWithoutUsers-iPhone-26-0-en-GB.png | 3 - ....testLongInputNameText-iPad-26-0-en-GB.png | 3 - ...estLongInputNameText-iPhone-26-0-en-GB.png | 3 - .../startChat.testFlow-iPad-26-0-en-GB-1.png | 3 + .../startChat.testFlow-iPad-26-0-en-GB-2.png | 3 + .../startChat.testFlow-iPad-26-0-en-GB-3.png | 3 + .../startChat.testFlow-iPad-26-0-en-GB-4.png | 3 + .../startChat.testFlow-iPad-26-0-en-GB-5.png | 3 + .../startChat.testFlow-iPad-26-0-en-GB-6.png | 3 + .../startChat.testFlow-iPad-26-0-en-GB-7.png | 3 + .../startChat.testFlow-iPad-26-0-en-GB-8.png | 3 + .../startChat.testFlow-iPad-26-0-en-GB-99.png | 3 + ...startChat.testFlow-iPhone-26-0-en-GB-1.png | 3 + ...startChat.testFlow-iPhone-26-0-en-GB-2.png | 3 + ...startChat.testFlow-iPhone-26-0-en-GB-3.png | 3 + ...startChat.testFlow-iPhone-26-0-en-GB-4.png | 3 + ...startChat.testFlow-iPhone-26-0-en-GB-5.png | 3 + ...startChat.testFlow-iPhone-26-0-en-GB-6.png | 3 + ...startChat.testFlow-iPhone-26-0-en-GB-7.png | 3 + ...startChat.testFlow-iPhone-26-0-en-GB-8.png | 3 + ...tartChat.testFlow-iPhone-26-0-en-GB-99.png | 3 + ...ChatScreen.testLanding-iPad-26-0-en-GB.png | 3 - ...atScreen.testLanding-iPhone-26-0-en-GB.png | 3 - ...estSearchWithNoResults-iPad-26-0-en-GB.png | 3 - ...tSearchWithNoResults-iPhone-26-0-en-GB.png | 3 - ....testSearchWithResults-iPad-26-0-en-GB.png | 3 - ...estSearchWithResults-iPhone-26-0-en-GB.png | 3 - .../Sources/CreateRoomViewModelTests.swift | 26 +- .../Sources/InviteUsersViewModelTests.swift | 25 +- .../Sources/StartChatViewModelTests.swift | 2 +- 68 files changed, 764 insertions(+), 789 deletions(-) create mode 100644 ElementX/Sources/FlowCoordinators/StartChatFlowCoordinator.swift delete mode 100644 ElementX/Sources/Screens/CreateRoom/CreateRoomCoordinator.swift create mode 100644 ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenCoordinator.swift rename ElementX/Sources/Screens/{CreateRoom/CreateRoomModels.swift => CreateRoomScreen/CreateRoomScreenModels.swift} (75%) rename ElementX/Sources/Screens/{CreateRoom/CreateRoomViewModel.swift => CreateRoomScreen/CreateRoomScreenViewModel.swift} (68%) rename ElementX/Sources/Screens/{CreateRoom/CreateRoomViewModelProtocol.swift => CreateRoomScreen/CreateRoomScreenViewModelProtocol.swift} (51%) rename ElementX/Sources/Screens/{CreateRoom => CreateRoomScreen}/View/CreateRoomScreen.swift (70%) delete mode 100644 ElementX/Sources/Services/CreateRoom/CreateRoomFlowParameters.swift delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-without-users-iPad-en-GB.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-without-users-iPad-pseudo.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-without-users-iPhone-16-en-GB.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-without-users-iPhone-16-pseudo.png delete mode 100644 UITests/Sources/CreateRoomScreenTests.swift delete mode 100644 UITests/Sources/StartChatScreenTests.swift create mode 100644 UITests/Sources/StartChatTests.swift delete mode 100644 UITests/Sources/__Snapshots__/Application/createRoomScreen.testLanding-iPad-26-0-en-GB.png delete mode 100644 UITests/Sources/__Snapshots__/Application/createRoomScreen.testLanding-iPhone-26-0-en-GB.png delete mode 100644 UITests/Sources/__Snapshots__/Application/createRoomScreen.testLandingWithoutUsers-iPad-26-0-en-GB.png delete mode 100644 UITests/Sources/__Snapshots__/Application/createRoomScreen.testLandingWithoutUsers-iPhone-26-0-en-GB.png delete mode 100644 UITests/Sources/__Snapshots__/Application/createRoomScreen.testLongInputNameText-iPad-26-0-en-GB.png delete mode 100644 UITests/Sources/__Snapshots__/Application/createRoomScreen.testLongInputNameText-iPhone-26-0-en-GB.png create mode 100644 UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-1.png create mode 100644 UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-2.png create mode 100644 UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-3.png create mode 100644 UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-4.png create mode 100644 UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-5.png create mode 100644 UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-6.png create mode 100644 UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-7.png create mode 100644 UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-8.png create mode 100644 UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-99.png create mode 100644 UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-1.png create mode 100644 UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-2.png create mode 100644 UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-3.png create mode 100644 UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-4.png create mode 100644 UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-5.png create mode 100644 UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-6.png create mode 100644 UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-7.png create mode 100644 UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-8.png create mode 100644 UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-99.png delete mode 100644 UITests/Sources/__Snapshots__/Application/startChatScreen.testLanding-iPad-26-0-en-GB.png delete mode 100644 UITests/Sources/__Snapshots__/Application/startChatScreen.testLanding-iPhone-26-0-en-GB.png delete mode 100644 UITests/Sources/__Snapshots__/Application/startChatScreen.testSearchWithNoResults-iPad-26-0-en-GB.png delete mode 100644 UITests/Sources/__Snapshots__/Application/startChatScreen.testSearchWithNoResults-iPhone-26-0-en-GB.png delete mode 100644 UITests/Sources/__Snapshots__/Application/startChatScreen.testSearchWithResults-iPad-26-0-en-GB.png delete mode 100644 UITests/Sources/__Snapshots__/Application/startChatScreen.testSearchWithResults-iPhone-26-0-en-GB.png diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 1cea7a429..0b2286e60 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -78,7 +78,6 @@ 0B05A35FF5D1ED9E8A0B41A7 /* AuthenticationStartScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65DDCF8E41759890355ACBC /* AuthenticationStartScreenViewModelProtocol.swift */; }; 0BAF83521871E69D222EE8E4 /* ClientBuilderHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC0CD1CAFD3F8B057F9AEA5 /* ClientBuilderHook.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 */; }; 0C1E537A49ABB386F7554D4A /* HighlightedTimelineItemModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59B7CC77B82C6C67DE3AD869 /* HighlightedTimelineItemModifier.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 */; }; 21AFEFB8CEFE56A3811A1F5B /* VoiceMessageCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 283974987DA7EC61D2AB57D9 /* VoiceMessageCacheTests.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 */; }; 22B380C579C148BA0BFB5952 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3557ACB95D0F666EF5AF0CE /* Secrets.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 */; }; 2835FD52F3F618D07F799B3D /* Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7310D8DFE01AF45F0689C3AA /* Publisher.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 */; }; - 292827744227DF61C930BDDB /* CreateRoomScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB0D6CB491777E7FC6B5BA12 /* CreateRoomScreen.swift */; }; 2932570AA418974979D16DED /* UserPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E6DE144D887A254F4CAF203 /* UserPreference.swift */; }; 29491EE7AE37E239E839C5A3 /* LocationSharingScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BEBF0E59F25E842EDB6FD11 /* LocationSharingScreenModels.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 */; }; 4764FC9A843D1F9865EDC29C /* EditRoomAddressScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4048547AC50ADCF201684E87 /* EditRoomAddressScreen.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 */; }; 48416BBEB8DDF3E4DED0EDB6 /* ElementCallServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FC8B21E86B137BE4E91F82A /* ElementCallServiceProtocol.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 */; }; 5618ED25F092DF5712003829 /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.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 */; }; 56DACDD379A86A1F5DEFE7BE /* AuthenticationServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E75948AA1FE1D1A7809931F /* AuthenticationServiceProtocol.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 */; }; 9D9690D2FD4CD26FF670620F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C75EF87651B00A176AB08E97 /* AppDelegate.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 */; }; 9DD84E014ADFB2DD813022D5 /* RoomDetailsEditScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00E5B2CBEF8F96424F095508 /* RoomDetailsEditScreenViewModelTests.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 */; }; 9FBE1FB20171012260A32492 /* TimelineSenderAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D53FCCE44F96E0BC411A6CF0 /* TimelineSenderAvatarView.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 */; }; A021827B528F1EDC9101CA58 /* AppCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBC776F301D374A3298C69DA /* AppCoordinatorProtocol.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 */; }; A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */; }; A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; }; + A7DB75E090542331F6668A23 /* CreateRoomScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF19027E7FFA5E63D148873A /* CreateRoomScreenViewModel.swift */; }; A808DC3F72D15C6C5A52317E /* TimelineItemDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCDA016D05107DED3B9495CB /* TimelineItemDebugView.swift */; }; A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6E30BB748F3F480F077969 /* RoomMemberDetailsScreenModels.swift */; }; A851635B3255C6DC07034A12 /* RoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */; }; @@ -1080,7 +1079,6 @@ C280BD841DA2EB6B5BC84F84 /* MapTilerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1F2AAA3F0F2B72D2FFE4D0 /* MapTilerConfiguration.swift */; }; C2879369106A419A5071F1F8 /* VoiceMessageRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B0A96B8FE4849227945067 /* VoiceMessageRecorder.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 */; }; C3522917C0C367C403429EEC /* CoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B251F5B4511D1CA0BA8361FE /* CoordinatorProtocol.swift */; }; 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 */; }; CE4B342F9DD747CF4BEDB5AB /* TestablePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43F773904F87FF5ADFE4DD1 /* TestablePreview.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 */; }; CEAEA57B7665C8E790599A78 /* BlockedUsersScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 240610DF32F3213BEC5611D7 /* BlockedUsersScreenViewModelTests.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 */; }; D33AC79A50DFC26D2498DD28 /* FileRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5098DA7799946A61E34A2373 /* FileRoomTimelineItem.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 */; }; D43F0503EF2CBC55272538FE /* SDKGeneratedMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F079B5DBD0D85FEA687AAE /* SDKGeneratedMocks.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 */; }; DA7E867F5EAFF8E20B2EE3B6 /* SecureBackupScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3D16709ADD4F4BCC710B1E /* SecureBackupScreenModels.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 */; }; DB5200B87C4CE9DF0024AC4E /* SpaceServiceProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C57DB49B8426AA721BF85D83 /* SpaceServiceProxyProtocol.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 */; }; F8E725D42023ECA091349245 /* AudioRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57EAAF82432B0B53881CF826 /* AudioRoomTimelineItem.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 */; }; F996259EAE68664BC345C197 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9DBE1C69F3A60D93B2203F /* Application.swift */; }; F99FB21EFC6D99D247FE7CBE /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 19CD5B074D7DD44AF4C58BB6 /* SwiftState */; }; @@ -1390,7 +1390,6 @@ FA71CD334F2D2289BEF0D749 /* SecureBackupRecoveryKeyScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A2FCA3D0F239B9E911B966B /* SecureBackupRecoveryKeyScreen.swift */; }; FA9C427FFB11B1AA2DCC5602 /* RoomProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47111410B6E659A697D472B5 /* RoomProxyProtocol.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 */; }; FBD402E3170EB1ED0D1AA672 /* EncryptionKeyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2355398E4A55DA5A89128AD1 /* EncryptionKeyProvider.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 = ""; }; 033DB41C51865A2E83174E87 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = ""; }; 035177BCD8E8308B098AC3C2 /* WindowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowManager.swift; sourceTree = ""; }; + 0367E0AF1163F42292D788BD /* StartChatTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatTests.swift; sourceTree = ""; }; 0376C429FAB1687C3D905F3E /* MockCoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCoder.swift; sourceTree = ""; }; 03DD998E523D4EC93C7ED703 /* RoomNotificationSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenViewModelProtocol.swift; sourceTree = ""; }; 03FABD73FD8086EFAB699F42 /* MediaUploadPreviewScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModelTests.swift; sourceTree = ""; }; @@ -1556,6 +1556,7 @@ 0A459AE4B6566B2FA99E86B2 /* TimelineItemBubbledStylerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemBubbledStylerView.swift; sourceTree = ""; }; 0A81FD0C60175FA081EB19AD /* EventTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventTimelineItem.swift; sourceTree = ""; }; 0AB7A0C06CB527A1095DEB33 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = da; path = da.lproj/Localizable.stringsdict; sourceTree = ""; }; + 0AC5E094DFE18C3AB7619D34 /* CreateRoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomScreenViewModelProtocol.swift; sourceTree = ""; }; 0B0E0B55E2EE75AF67029924 /* SwipeToReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeToReplyView.swift; sourceTree = ""; }; 0B32BBA8887BD7A5C4ECF16F /* RoomModerationRole.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomModerationRole.swift; sourceTree = ""; }; 0B987FC3FDBAA0E1C5AA235C /* PaginationIndicatorRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationIndicatorRoomTimelineItem.swift; sourceTree = ""; }; @@ -1569,6 +1570,7 @@ 0CB569EAA5017B5B23970655 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Localizable.strings; sourceTree = ""; }; 0CCC6C31102E1D8B9106DEDE /* AppLockSetupBiometricsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupBiometricsScreenViewModelProtocol.swift; sourceTree = ""; }; 0D0B159AFFBBD8ECFD0E37FA /* LoginScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenModels.swift; sourceTree = ""; }; + 0D432771B389EC486E7F90C6 /* CreateRoomScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomScreenModels.swift; sourceTree = ""; }; 0D879FC4E881E748BB9B34DC /* RoomChangePermissionsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangePermissionsScreenCoordinator.swift; sourceTree = ""; }; 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 = ""; }; @@ -1615,7 +1617,6 @@ 1562EAF6231151A675BED7A9 /* RoomDirectorySearchScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchScreenCoordinator.swift; sourceTree = ""; }; 15748C254911E3654C93B0ED /* MentionBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionBuilder.swift; sourceTree = ""; }; 1575947B7A6FE08C57FE5EE4 /* NetworkMonitorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitorProtocol.swift; sourceTree = ""; }; - 15A657D96779D1DEB8EF1327 /* CreateRoomViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomViewModel.swift; sourceTree = ""; }; 161CD412E75F4086F422AE39 /* SessionVerificationScreenStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenStateMachine.swift; sourceTree = ""; }; 1627F2D56477BD331F6D732C /* RoomHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomHeaderView.swift; sourceTree = ""; }; 166D45E1861A73B232109843 /* RoomDetailsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenViewModelTests.swift; sourceTree = ""; }; @@ -1686,7 +1687,6 @@ 218AB05B4E3889731959C5F1 /* EventBasedTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBasedTimelineItemProtocol.swift; sourceTree = ""; }; 21BA866267F84BF4350B0CB7 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "pt-BR"; path = "pt-BR.lproj/Localizable.stringsdict"; sourceTree = ""; }; 21DD8599815136EFF5B73F38 /* UserFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserFlowTests.swift; sourceTree = ""; }; - 2214C32EA81FA9168D923D4C /* CreateRoomScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomScreenTests.swift; sourceTree = ""; }; 226A56746AD99ABDA97189E1 /* SpaceServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceServiceProxy.swift; sourceTree = ""; }; 22730A30C50AC2E3D5BA8642 /* InviteUsersScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenViewModelProtocol.swift; sourceTree = ""; }; 227AC5D71A4CE43512062243 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = ""; }; @@ -1786,7 +1786,6 @@ 3339B1DDB1341E833D2555BC /* AVMetadataMachineReadableCodeObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVMetadataMachineReadableCodeObject.swift; sourceTree = ""; }; 33AE897D86784CCA5E4E9227 /* ElementCallService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallService.swift; sourceTree = ""; }; 33E49C5C6F802B4D94CA78D1 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; - 340179A0FC1AD4AEDA7FC134 /* CreateRoomViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomViewModelProtocol.swift; sourceTree = ""; }; 342BEBC3C5FC3F9943C41C4C /* TemplateScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenViewModelProtocol.swift; sourceTree = ""; }; 345172AD4377E83A44BD864F /* MessageComposerTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageComposerTextField.swift; sourceTree = ""; }; 347D708104CCEF771428C9A3 /* PollFormScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollFormScreenViewModelTests.swift; sourceTree = ""; }; @@ -1817,7 +1816,6 @@ 3AB34956C87731AB094DB33A /* URLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLTests.swift; sourceTree = ""; }; 3AD253E7EFF88F308D644272 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/SAS.strings"; sourceTree = ""; }; 3B5E97E9615A158C76B2AB77 /* DateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTests.swift; sourceTree = ""; }; - 3B927A399A5418DA40A5CA15 /* StartChatScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreenTests.swift; sourceTree = ""; }; 3BAC027034248429A438886B /* AppMediatorMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppMediatorMock.swift; sourceTree = ""; }; 3BC1B7CB061C9865B2B91B56 /* QRCodeLoginScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeLoginScreenViewModel.swift; sourceTree = ""; }; 3BDCCD2F6B405C14B9BCE94E /* JoinRoomScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRoomScreenCoordinator.swift; sourceTree = ""; }; @@ -1894,6 +1892,7 @@ 471BB7276C97AF60B3A5463B /* RoomDirectorySearchProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchProxy.swift; sourceTree = ""; }; 475D47D0BFE961B02BAC5D49 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = id; path = id.lproj/Localizable.stringsdict; sourceTree = ""; }; 475EB595D7527E9A8A14043E /* uz */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uz; path = uz.lproj/Localizable.strings; sourceTree = ""; }; + 477A8C656B7B26E99C35927F /* StartChatFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatFlowCoordinator.swift; sourceTree = ""; }; 47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenModels.swift; sourceTree = ""; }; 47EBB5D698CE9A25BB553A2D /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; }; 47F29139BC2A804CE5E0757E /* MediaUploadPreviewScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModel.swift; sourceTree = ""; }; @@ -2028,6 +2027,7 @@ 646B50583A2CE6DA67F7739A /* SpaceScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceScreen.swift; sourceTree = ""; }; 648DD1C10E4957CB791FE0B8 /* OverridableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverridableAvatarImage.swift; sourceTree = ""; }; 6493AC9979CEB1410302BFE3 /* RoomDetailsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenCoordinator.swift; sourceTree = ""; }; + 64AC8FCE224D4185F28636FF /* CreateRoomScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomScreenCoordinator.swift; sourceTree = ""; }; 64F49FB9EE2913234F06CE68 /* MediaPickerScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPickerScreenCoordinator.swift; sourceTree = ""; }; 6509708F54FC883604DFDC95 /* TimelineViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineViewModelTests.swift; sourceTree = ""; }; 653610CB5F9776EAAAB98155 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fr; path = fr.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -2137,7 +2137,6 @@ 7B19B2BCC779ED934E0BBC2A /* AudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayer.swift; sourceTree = ""; }; 7B25F959A434BB9923A3223F /* ExpiringTaskRunner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpiringTaskRunner.swift; sourceTree = ""; }; 7B3D16709ADD4F4BCC710B1E /* SecureBackupScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupScreenModels.swift; sourceTree = ""; }; - 7B849D2FF2CC12BA411A1651 /* CreateRoomModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomModels.swift; sourceTree = ""; }; 7B9FCA1CFD07B8CF9BD21266 /* FlowCoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowCoordinatorProtocol.swift; sourceTree = ""; }; 7BBADF8010C813D905C172CE /* SecurityAndPrivacyScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityAndPrivacyScreenModels.swift; sourceTree = ""; }; 7BD5523BDEDB247E29228476 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; @@ -2272,7 +2271,6 @@ 933B074F006F8E930DB98B4E /* TimelineMediaFrame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMediaFrame.swift; sourceTree = ""; }; 9342F5D6729627B6393AF853 /* ServerConfirmationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenModels.swift; sourceTree = ""; }; 9349F590E35CE514A71E6764 /* LoginHomeserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginHomeserver.swift; sourceTree = ""; }; - 935C2FB18EFB8EEE96B26330 /* CreateRoomFlowParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomFlowParameters.swift; sourceTree = ""; }; 93C713D124FE915ABF47A6B7 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = ""; }; 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModelTests.swift; sourceTree = ""; }; 93E1FF0DFBB3768F79FDBF6D /* AVMetadataMachineReadableCodeObjectExtensionsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVMetadataMachineReadableCodeObjectExtensionsTest.swift; sourceTree = ""; }; @@ -2547,7 +2545,6 @@ C5AEB5907E24092D741718AF /* ResolveVerifiedUserSendFailureScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolveVerifiedUserSendFailureScreenCoordinator.swift; sourceTree = ""; }; C5F06F2F09B2EDD067DC2174 /* NotificationSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreen.swift; sourceTree = ""; }; C616D90B1E2F033CAA325439 /* StaticLocationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLocationScreenViewModelProtocol.swift; sourceTree = ""; }; - C618CA2B6C8758B06C88013C /* CreateRoomCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomCoordinator.swift; sourceTree = ""; }; C687844F60BFF532D49A994C /* AnalyticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsTests.swift; sourceTree = ""; }; C6A9F49B3EE59147AF2F70BB /* SeparatorRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineItem.swift; sourceTree = ""; }; C705E605EF57C19DBE86FFA1 /* PlaceholderAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderAvatarImage.swift; sourceTree = ""; }; @@ -2592,6 +2589,7 @@ CDE3F3911FF7CC639BDE5844 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; CEE20623EB4A9B88FB29F2BA /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/SAS.strings; sourceTree = ""; }; CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = ""; }; + CF19027E7FFA5E63D148873A /* CreateRoomScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomScreenViewModel.swift; sourceTree = ""; }; CF847A34FC4C8C937CD39E08 /* LabsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabsScreenViewModelProtocol.swift; sourceTree = ""; }; CFFA5E881D281810AB428EA3 /* RoomPowerLevelsProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPowerLevelsProxy.swift; sourceTree = ""; }; D01FD1171FF40E34D707FD00 /* BigIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BigIcon.swift; sourceTree = ""; }; @@ -2766,6 +2764,7 @@ EF1593DD87F974F8509BB619 /* ElementAnimations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementAnimations.swift; sourceTree = ""; }; EF36FC3DE25B20B7FA91F1FD /* SpaceServiceProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceServiceProxyMock.swift; sourceTree = ""; }; EF98A02DED04075F7CF0C721 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = ""; }; + EFD1CFB730C7417925264F17 /* CreateRoomScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomScreen.swift; sourceTree = ""; }; EFF7BF82A950B91BC5469E91 /* ViewFrameReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewFrameReader.swift; sourceTree = ""; }; EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportServiceTests.swift; sourceTree = ""; }; F0096BC5DA86AF6B6E5742AC /* RoomPermissionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPermissionsTests.swift; sourceTree = ""; }; @@ -2819,7 +2818,6 @@ FA723686F23EF45E2B398FBC /* TestablePreviewsDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestablePreviewsDictionary.swift; sourceTree = ""; }; FA7BB497B2F539C17E88F6B7 /* NotificationSettingsEditScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenViewModelProtocol.swift; sourceTree = ""; }; FABAC5C4373B0EC24D399663 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/SAS.strings"; sourceTree = ""; }; - FB0D6CB491777E7FC6B5BA12 /* CreateRoomScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomScreen.swift; sourceTree = ""; }; FB7BAD55A4E2B8E5828CD64C /* SoftLogoutScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutScreenViewModel.swift; sourceTree = ""; }; FB9EABCA9348DFA27439A809 /* WaveformCursorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaveformCursorView.swift; sourceTree = ""; }; FBB0328F2887BF0A65BC5D49 /* NotificationSettingsEditScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreen.swift; sourceTree = ""; }; @@ -3055,7 +3053,6 @@ 0ED3F5C21537519389C07644 /* BugReport */, 8039515BAA53B7C3275AC64A /* Client */, 8B5E91450E85A9689931B221 /* ComposerDraft */, - 8C3BAE06B336D97DABBE2509 /* CreateRoom */, 92E99C57D7F92ED16F73282C /* ElementCall */, 39557ADF21345E18F3865B9E /* Emojis */, CA555F7C7CA382ACACF0D82B /* Keychain */, @@ -4305,6 +4302,7 @@ 5A4EF5724C0F894911AF7811 /* SpaceExplorerFlowCoordinator.swift */, EDDE826EAB1BAB80C1104980 /* SpaceFlowCoordinator.swift */, BDE3EDEA7E64D68FEB828F83 /* SpaceSettingsFlowCoordinator.swift */, + 477A8C656B7B26E99C35927F /* StartChatFlowCoordinator.swift */, C99FDEEB71173C4C6FA2734C /* UserSessionFlowCoordinator.swift */, ); path = FlowCoordinators; @@ -4962,6 +4960,18 @@ path = View; sourceTree = ""; }; + 821EB0D1C0019E3C7BBAEDBB /* CreateRoomScreen */ = { + isa = PBXGroup; + children = ( + 64AC8FCE224D4185F28636FF /* CreateRoomScreenCoordinator.swift */, + 0D432771B389EC486E7F90C6 /* CreateRoomScreenModels.swift */, + CF19027E7FFA5E63D148873A /* CreateRoomScreenViewModel.swift */, + 0AC5E094DFE18C3AB7619D34 /* CreateRoomScreenViewModelProtocol.swift */, + A7694BCE812C4D7B2B1B42DF /* View */, + ); + path = CreateRoomScreen; + sourceTree = ""; + }; 823ED0EC3F1B6CF47D284011 /* Tools */ = { isa = PBXGroup; children = ( @@ -5117,14 +5127,6 @@ path = ComposerDraft; sourceTree = ""; }; - 8C3BAE06B336D97DABBE2509 /* CreateRoom */ = { - isa = PBXGroup; - children = ( - 935C2FB18EFB8EEE96B26330 /* CreateRoomFlowParameters.swift */, - ); - path = CreateRoom; - sourceTree = ""; - }; 8F074E22FD93E64211971845 /* Onboarding */ = { isa = PBXGroup; children = ( @@ -5252,7 +5254,6 @@ E8495F37D6245AD0CFA1F60B /* AppLockTests.swift */, 37F46CC4FD89ECF4CF26391A /* AuthenticationFlowCoordinatorTests.swift */, 27DF257F5D968E5DD719583C /* BugReportTests.swift */, - 2214C32EA81FA9168D923D4C /* CreateRoomScreenTests.swift */, 89BB11A792EF6F70B95B467E /* EncryptionResetTests.swift */, 57AD14D3ADADE8F6A10F9E88 /* EncryptionSettingsTests.swift */, DCDAB580109C09A6AA97AF7E /* PollFormScreenTests.swift */, @@ -5261,7 +5262,7 @@ D97A4E73EA97CA08D2BB9806 /* RoomScreenTests.swift */, 6640DB5B9171D163E6742639 /* ServerSelectionTests.swift */, D8AA084E10B80D64449C02A9 /* SessionVerificationTests.swift */, - 3B927A399A5418DA40A5CA15 /* StartChatScreenTests.swift */, + 0367E0AF1163F42292D788BD /* StartChatTests.swift */, F899D02CF26EA7675EEBE74C /* UserSessionScreenTests.swift */, ); path = Sources; @@ -5522,6 +5523,14 @@ path = View; sourceTree = ""; }; + A7694BCE812C4D7B2B1B42DF /* View */ = { + isa = PBXGroup; + children = ( + EFD1CFB730C7417925264F17 /* CreateRoomScreen.swift */, + ); + path = View; + sourceTree = ""; + }; A78C2592419CA4C76FBA8FD2 /* Application */ = { isa = PBXGroup; children = ( @@ -5839,18 +5848,6 @@ path = MapLibre; sourceTree = ""; }; - C18958141C8ED6D778F779A4 /* CreateRoom */ = { - isa = PBXGroup; - children = ( - C618CA2B6C8758B06C88013C /* CreateRoomCoordinator.swift */, - 7B849D2FF2CC12BA411A1651 /* CreateRoomModels.swift */, - 15A657D96779D1DEB8EF1327 /* CreateRoomViewModel.swift */, - 340179A0FC1AD4AEDA7FC134 /* CreateRoomViewModelProtocol.swift */, - F84100ED0C09031BAB7BB77E /* View */, - ); - path = CreateRoom; - sourceTree = ""; - }; C1CD278862878F9545608040 /* SessionVerificationScreen */ = { isa = PBXGroup; children = ( @@ -6233,7 +6230,7 @@ 53FB148CD26AFB6A5B9E20B3 /* BugReportScreen */, 1185EECDD07495D65AC84AFC /* CallScreen */, 90DC2E28718955ED87AD1456 /* CreatePollScreen */, - C18958141C8ED6D778F779A4 /* CreateRoom */, + 821EB0D1C0019E3C7BBAEDBB /* CreateRoomScreen */, 3E1CCC4B607946CE90B4A827 /* DeclineAndBlockScreen */, 45F2BCFD6E9A6F040CC20582 /* EditRoomAddressScreen */, F5A65D1D3B83593598DC278D /* EmojiPickerScreen */, @@ -6486,14 +6483,6 @@ path = IdentityConfirmedScreen; sourceTree = ""; }; - F84100ED0C09031BAB7BB77E /* View */ = { - isa = PBXGroup; - children = ( - FB0D6CB491777E7FC6B5BA12 /* CreateRoomScreen.swift */, - ); - path = View; - sourceTree = ""; - }; FA1D480A302295CFC3582543 /* View */ = { isa = PBXGroup; children = ( @@ -7718,12 +7707,11 @@ EA6613B29BA671F39CE1B1D2 /* ConfirmationDialog.swift in Sources */, AC7AA215D60FBC307F984028 /* Consumable.swift in Sources */, C3522917C0C367C403429EEC /* CoordinatorProtocol.swift in Sources */, - 564BF06B3E93D6DD55F903B2 /* CreateRoomCoordinator.swift in Sources */, - C32765D740C81AD4C42E8F50 /* CreateRoomFlowParameters.swift in Sources */, - FB53CD9B74A15B3B94F9F788 /* CreateRoomModels.swift in Sources */, - 292827744227DF61C930BDDB /* CreateRoomScreen.swift in Sources */, - 0BE4D5CBF86956410F071F91 /* CreateRoomViewModel.swift in Sources */, - 4799A852132F1744E2825994 /* CreateRoomViewModelProtocol.swift in Sources */, + CE8296D4AD30DDC6D0C67A74 /* CreateRoomScreen.swift in Sources */, + D38E59C48BE5499A48D12031 /* CreateRoomScreenCoordinator.swift in Sources */, + 9DBF6524DFD8143A4D6A17F0 /* CreateRoomScreenModels.swift in Sources */, + A7DB75E090542331F6668A23 /* CreateRoomScreenViewModel.swift in Sources */, + DB01FDD050A43B72C5425549 /* CreateRoomScreenViewModelProtocol.swift in Sources */, 565868808A1DA565707394ED /* CurrentValuePublisher.swift in Sources */, A722F426FD81FC67706BB1E0 /* CustomLayoutLabelStyle.swift in Sources */, 12C867E85E6D12EEDFD0B127 /* CustomStringConvertible.swift in Sources */, @@ -8346,6 +8334,7 @@ 9DB4B303ECC05F0F33582594 /* SpacesAnnouncementSheetView.swift in Sources */, DF004A5B2EABBD0574D06A04 /* SplashScreenCoordinator.swift in Sources */, E1C67E5D9E22135A8FEBBD60 /* StackedAvatarsView.swift in Sources */, + 9FC79DA30AE0E1502DAEBD51 /* StartChatFlowCoordinator.swift in Sources */, 3DAF325D8AE461F7CDB282BD /* StartChatScreen.swift in Sources */, 6CD61FAF03E8986523C2ABB8 /* StartChatScreenCoordinator.swift in Sources */, C051475DFF4C8EBDDF4DC8E4 /* StartChatScreenModels.swift in Sources */, @@ -8541,7 +8530,6 @@ CB07184D37D5D65327A5A693 /* AuthenticationFlowCoordinatorTests.swift in Sources */, 9EF72884B74BC39B01640098 /* BugReportTests.swift in Sources */, 94D0F36A87E596A93C0C178A /* Bundle.swift in Sources */, - 28AB1614E749D1147A2AC6C2 /* CreateRoomScreenTests.swift in Sources */, 8D24671992A1C1753B211221 /* EncryptionResetTests.swift in Sources */, E5E43A0CA99AF5BA11B194A2 /* EncryptionSettingsTests.swift in Sources */, A950C95855C474F75B30CA7B /* PollFormScreenTests.swift in Sources */, @@ -8550,7 +8538,7 @@ 94EB81606839C175CA6C2ADB /* RoomScreenTests.swift in Sources */, E9985DCD1B0D026D7E8BF809 /* ServerSelectionTests.swift in Sources */, D5C2DA52162A978743A183F2 /* SessionVerificationTests.swift in Sources */, - F9788AE0B9EA19F1190ED14F /* StartChatScreenTests.swift in Sources */, + 221A7F92116722322AE150AE /* StartChatTests.swift in Sources */, 54AE8860D668AFD96E7E177B /* UITestsScreenIdentifier.swift in Sources */, 84EFCB95F9DA2979C8042B26 /* UITestsSignalling.swift in Sources */, B22D857D1E8FCA6DD74A58E3 /* UserSessionScreenTests.swift in Sources */, diff --git a/ElementX/Sources/FlowCoordinators/ChatsFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/ChatsFlowCoordinator.swift index 07495850a..560b40d23 100644 --- a/ElementX/Sources/FlowCoordinators/ChatsFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/ChatsFlowCoordinator.swift @@ -36,9 +36,10 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol { // periphery:ignore - retaining purpose private var bugReportFlowCoordinator: BugReportFlowCoordinator? - // periphery:ignore - retaining purpose private var encryptionResetFlowCoordinator: EncryptionResetFlowCoordinator? + // periphery:ignore - retaining purpose + private var startChatFlowCoordinator: StartChatFlowCoordinator? // periphery:ignore - retaining purpose private var globalSearchScreenCoordinator: GlobalSearchScreenCoordinator? @@ -223,12 +224,12 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol { case (.roomList, .startEncryptionResetFlow, .encryptionResetFlow): startEncryptionResetFlow(animated: animated) case (.encryptionResetFlow, .finishedEncryptionResetFlow, .roomList): - break + encryptionResetFlowCoordinator = nil - case (.roomList, .showStartChatScreen, .startChatScreen): - presentStartChat(animated: animated) - case (.startChatScreen, .dismissedStartChatScreen, .roomList): - break + case (.roomList, .startStartChatFlow, .startChatFlow): + startStartChatFlow(animated: animated) + case (.startChatFlow, .finishedStartChatFlow, .roomList): + startChatFlowCoordinator = nil case (.roomList, .showRoomDirectorySearchScreen, .roomDirectorySearchScreen): presentRoomDirectorySearch() @@ -398,7 +399,7 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol { case .presentEncryptionResetScreen: stateMachine.processEvent(.startEncryptionResetFlow) case .presentStartChatScreen: - stateMachine.processEvent(.showStartChatScreen) + stateMachine.processEvent(.startStartChatFlow) case .presentGlobalSearch: presentGlobalSearch() case .logout: @@ -565,39 +566,34 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol { // MARK: Start Chat - private func presentStartChat(animated: Bool) { - let startChatNavigationStackCoordinator = NavigationStackCoordinator() - - let userDiscoveryService = UserDiscoveryService(clientProxy: userSession.clientProxy) - let parameters = StartChatScreenCoordinatorParameters(orientationManager: flowParameters.windowManager, - userSession: userSession, - userIndicatorController: flowParameters.userIndicatorController, - navigationStackCoordinator: startChatNavigationStackCoordinator, - userDiscoveryService: userDiscoveryService, - mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: flowParameters.appSettings), - appSettings: flowParameters.appSettings, - analytics: flowParameters.analytics) + private func startStartChatFlow(animated: Bool) { + let navigationStackCoordinator = NavigationStackCoordinator() + let coordinator = StartChatFlowCoordinator(userDiscoveryService: UserDiscoveryService(clientProxy: userSession.clientProxy), + navigationStackCoordinator: navigationStackCoordinator, + flowParameters: flowParameters) - let coordinator = StartChatScreenCoordinator(parameters: parameters) - coordinator.actions.sink { [weak self] action in - guard let self else { return } - switch action { - case .close: - navigationSplitCoordinator.setSheetCoordinator(nil) - case .openRoom(let roomID): - navigationSplitCoordinator.setSheetCoordinator(nil) - stateMachine.processEvent(.selectRoom(roomID: roomID, via: [], entryPoint: .room)) - case .openRoomDirectorySearch: - navigationSplitCoordinator.setSheetCoordinator(nil) - stateMachine.processEvent(.showRoomDirectorySearchScreen) + coordinator.actionsPublisher + .sink { [weak self] action in + guard let self else { return } + switch action { + case .finished(let roomID): + navigationSplitCoordinator.setSheetCoordinator(nil) + + if let roomID { + stateMachine.processEvent(.selectRoom(roomID: roomID, via: [], entryPoint: .room)) + } + case .showRoomDirectory: + navigationSplitCoordinator.setSheetCoordinator(nil) + stateMachine.processEvent(.showRoomDirectorySearchScreen) + } } - } - .store(in: &cancellables) - - startChatNavigationStackCoordinator.setRootCoordinator(coordinator) - - navigationSplitCoordinator.setSheetCoordinator(startChatNavigationStackCoordinator, animated: animated) { [weak self] in - self?.stateMachine.processEvent(.dismissedStartChatScreen) + .store(in: &cancellables) + + startChatFlowCoordinator = coordinator + coordinator.start() + + navigationSplitCoordinator.setSheetCoordinator(navigationStackCoordinator, animated: animated) { [weak self] in + self?.stateMachine.processEvent(.finishedStartChatFlow) } } @@ -639,10 +635,8 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol { guard let self else { return } switch action { case .resetComplete: - encryptionResetFlowCoordinator = nil navigationSplitCoordinator.setSheetCoordinator(nil) case .cancel: - encryptionResetFlowCoordinator = nil navigationSplitCoordinator.setSheetCoordinator(nil) } } diff --git a/ElementX/Sources/FlowCoordinators/ChatsFlowCoordinatorStateMachine.swift b/ElementX/Sources/FlowCoordinators/ChatsFlowCoordinatorStateMachine.swift index 7b7a756f8..61859f6e7 100644 --- a/ElementX/Sources/FlowCoordinators/ChatsFlowCoordinatorStateMachine.swift +++ b/ElementX/Sources/FlowCoordinators/ChatsFlowCoordinatorStateMachine.swift @@ -33,8 +33,8 @@ class ChatsFlowCoordinatorStateMachine { /// Showing the encryption reset flow. case encryptionResetFlow(detailState: DetailState?) - /// Showing the start chat screen - case startChatScreen(detailState: DetailState?) + /// Showing the start chat flow + case startChatFlow(detailState: DetailState?) /// Showing the logout flows case logoutConfirmationScreen(detailState: DetailState?) @@ -61,7 +61,7 @@ class ChatsFlowCoordinatorStateMachine { .feedbackScreen(let detailState), .recoveryKeyScreen(let detailState), .encryptionResetFlow(let detailState), - .startChatScreen(let detailState), + .startChatFlow(let detailState), .logoutConfirmationScreen(let detailState), .roomDirectorySearchScreen(let detailState), .reportRoomScreen(let detailState), @@ -111,10 +111,10 @@ class ChatsFlowCoordinatorStateMachine { /// The encryption reset flow is complete and has been dismissed. case finishedEncryptionResetFlow - /// Request the start of the start chat flow - case showStartChatScreen - /// Start chat has been dismissed - case dismissedStartChatScreen + /// Request the start of the start chat flow. + case startStartChatFlow + /// The Start Chat flow is complete and has been dismissed. + case finishedStartChatFlow /// Request presentation of the room directory search screen. case showRoomDirectorySearchScreen @@ -177,9 +177,9 @@ class ChatsFlowCoordinatorStateMachine { case (.encryptionResetFlow(let detailState), .finishedEncryptionResetFlow): return .roomList(detailState: detailState) - case (.roomList(let detailState), .showStartChatScreen): - return .startChatScreen(detailState: detailState) - case (.startChatScreen(let detailState), .dismissedStartChatScreen): + case (.roomList(let detailState), .startStartChatFlow): + return .startChatFlow(detailState: detailState) + case (.startChatFlow(let detailState), .finishedStartChatFlow): return .roomList(detailState: detailState) case (.roomList(let detailState), .showRoomDirectorySearchScreen): diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index 4fbe65919..789f34292 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -1267,12 +1267,10 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { } private func presentInviteUsersScreen() { - let selectedUsersSubject: CurrentValueSubject<[UserProfileProxy], Never> = .init([]) - let stackCoordinator = NavigationStackCoordinator() let inviteParameters = InviteUsersScreenCoordinatorParameters(userSession: userSession, - selectedUsers: .init(selectedUsersSubject), - roomType: .room(roomProxy: roomProxy), + roomProxy: roomProxy, + isSkippable: false, userDiscoveryService: UserDiscoveryService(clientProxy: userSession.clientProxy), userIndicatorController: flowParameters.userIndicatorController, appSettings: flowParameters.appSettings) @@ -1286,8 +1284,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { switch action { case .dismiss: navigationStackCoordinator.setSheetCoordinator(nil) - case .proceed: - fatalError("Not handled in this flow.") } } .store(in: &cancellables) diff --git a/ElementX/Sources/FlowCoordinators/RoomMembersFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomMembersFlowCoordinator.swift index b1c48a603..a93437061 100644 --- a/ElementX/Sources/FlowCoordinators/RoomMembersFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomMembersFlowCoordinator.swift @@ -255,8 +255,8 @@ final class RoomMembersFlowCoordinator: FlowCoordinatorProtocol { private func presentInviteUsersScreen() { let stackCoordinator = NavigationStackCoordinator() let inviteParameters = InviteUsersScreenCoordinatorParameters(userSession: flowParameters.userSession, - selectedUsers: nil, - roomType: .room(roomProxy: roomProxy), + roomProxy: roomProxy, + isSkippable: false, userDiscoveryService: UserDiscoveryService(clientProxy: flowParameters.userSession.clientProxy), userIndicatorController: flowParameters.userIndicatorController, appSettings: flowParameters.appSettings) @@ -270,8 +270,6 @@ final class RoomMembersFlowCoordinator: FlowCoordinatorProtocol { switch action { case .dismiss: navigationStackCoordinator.setSheetCoordinator(nil) - case .proceed: - fatalError("Not handled in this flow.") } } .store(in: &cancellables) diff --git a/ElementX/Sources/FlowCoordinators/StartChatFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/StartChatFlowCoordinator.swift new file mode 100644 index 000000000..6f4629178 --- /dev/null +++ b/ElementX/Sources/FlowCoordinators/StartChatFlowCoordinator.swift @@ -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 + private var cancellables: Set = [] + + private let actionsSubject: PassthroughSubject = .init() + var actionsPublisher: AnyPublisher { + 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) + } +} diff --git a/ElementX/Sources/Mocks/JoinedRoomProxyMock.swift b/ElementX/Sources/Mocks/JoinedRoomProxyMock.swift index a4a18caa1..c04caf81e 100644 --- a/ElementX/Sources/Mocks/JoinedRoomProxyMock.swift +++ b/ElementX/Sources/Mocks/JoinedRoomProxyMock.swift @@ -123,6 +123,7 @@ extension JoinedRoomProxyMock { powerLevelsReturnValue = .success(powerLevelsProxyMock) + inviteUserIDReturnValue = .success(()) kickUserReasonReturnValue = .success(()) banUserReasonReturnValue = .success(()) unbanUserReturnValue = .success(()) diff --git a/ElementX/Sources/Other/AccessibilityIdentifiers.swift b/ElementX/Sources/Other/AccessibilityIdentifiers.swift index 7b57d831e..2e45f9b39 100644 --- a/ElementX/Sources/Other/AccessibilityIdentifiers.swift +++ b/ElementX/Sources/Other/AccessibilityIdentifiers.swift @@ -270,8 +270,11 @@ enum A11yIdentifiers { } struct CreateRoomScreen { + let create = "create_room-create" + let roomAvatar = "create_room-room_avatar" let roomName = "create_room-room_name" let roomTopic = "create_room-room_topic" + let mediaPicker = "create_room-media_picker" } struct PollFormScreen { diff --git a/ElementX/Sources/Screens/CreateRoom/CreateRoomCoordinator.swift b/ElementX/Sources/Screens/CreateRoom/CreateRoomCoordinator.swift deleted file mode 100644 index 192323af3..000000000 --- a/ElementX/Sources/Screens/CreateRoom/CreateRoomCoordinator.swift +++ /dev/null @@ -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 - 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 = .init() - private var cancellables = Set() - - var actions: AnyPublisher { - 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)) - } -} diff --git a/ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenCoordinator.swift b/ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenCoordinator.swift new file mode 100644 index 000000000..a97908818 --- /dev/null +++ b/ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenCoordinator.swift @@ -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 = .init() + private var cancellables = Set() + + var actions: AnyPublisher { + 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) + } +} diff --git a/ElementX/Sources/Screens/CreateRoom/CreateRoomModels.swift b/ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenModels.swift similarity index 75% rename from ElementX/Sources/Screens/CreateRoom/CreateRoomModels.swift rename to ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenModels.swift index 6ba679725..ff1e876b9 100644 --- a/ElementX/Sources/Screens/CreateRoom/CreateRoomModels.swift +++ b/ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenModels.swift @@ -16,28 +16,24 @@ enum CreateRoomScreenErrorType: Error { case unknown } -enum CreateRoomViewModelAction { - case openRoom(withIdentifier: String) - case updateSelectedUsers([UserProfileProxy]) - case updateDetails(CreateRoomFlowParameters) +enum CreateRoomScreenViewModelAction { + case createdRoom(JoinedRoomProxyProtocol) case displayMediaPicker case displayCameraPicker - case removeImage } -struct CreateRoomViewState: BindableState { +struct CreateRoomScreenViewState: BindableState { var roomName: String let serverName: String let isKnockingFeatureEnabled: Bool - var selectedUsers: [UserProfileProxy] var aliasLocalPart: String - var bindings: CreateRoomViewStateBindings + var bindings: CreateRoomScreenViewStateBindings var avatarURL: URL? var canCreateRoom: Bool { !roomName.isEmpty && aliasErrors.isEmpty } - var aliasErrors: Set = [] + var aliasErrors: Set = [] var aliasErrorDescription: String? { if aliasErrors.contains(.alreadyExists) { L10n.errorRoomAddressAlreadyExists @@ -49,7 +45,7 @@ struct CreateRoomViewState: BindableState { } } -struct CreateRoomViewStateBindings { +struct CreateRoomScreenViewStateBindings { var roomTopic: String var isRoomPrivate: Bool var isKnockingOnly: Bool @@ -59,9 +55,8 @@ struct CreateRoomViewStateBindings { var alertInfo: AlertInfo? } -enum CreateRoomViewAction { +enum CreateRoomScreenViewAction { case createRoom - case deselectUser(UserProfileProxy) case displayCameraPicker case displayMediaPicker case removeImage @@ -69,12 +64,12 @@ enum CreateRoomViewAction { case updateAliasLocalPart(String) } -enum CreateRoomAliasErrorState { +enum CreateRoomScreenAliasErrorState { case alreadyExists case invalidSymbols } -extension Set { +extension Set { var errorDescription: String? { if contains(.alreadyExists) { return L10n.errorRoomAddressAlreadyExists diff --git a/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift b/ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenViewModel.swift similarity index 68% rename from ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift rename to ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenViewModel.swift index 69ad6afe7..bf5f5fd2b 100644 --- a/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift +++ b/ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenViewModel.swift @@ -10,83 +10,70 @@ import Combine import MatrixRustSDK import SwiftUI -typealias CreateRoomViewModelType = StateStoreViewModel +typealias CreateRoomScreenViewModelType = StateStoreViewModel -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 var createRoomParameters: CreateRoomFlowParameters + private var parameters: Parameters + private let mediaUploadingPreprocessor: MediaUploadingPreprocessor private let analytics: AnalyticsService private let userIndicatorController: UserIndicatorControllerProtocol private var syncNameAndAlias = true @CancellableTask private var checkAliasAvailabilityTask: Task? - private var actionsSubject: PassthroughSubject = .init() + private var actionsSubject: PassthroughSubject = .init() - var actions: AnyPublisher { + var actions: AnyPublisher { actionsSubject.eraseToAnyPublisher() } init(userSession: UserSessionProtocol, - createRoomParameters: CurrentValuePublisher, - selectedUsers: [UserProfileProxy], + initialParameters: Parameters = .init(), analytics: AnalyticsService, userIndicatorController: UserIndicatorControllerProtocol, appSettings: AppSettings) { - let parameters = createRoomParameters.value - self.userSession = userSession - self.createRoomParameters = parameters + parameters = initialParameters + mediaUploadingPreprocessor = MediaUploadingPreprocessor(appSettings: appSettings) self.analytics = analytics self.userIndicatorController = userIndicatorController - let bindings = CreateRoomViewStateBindings(roomTopic: parameters.topic, - isRoomPrivate: parameters.isRoomPrivate, - isKnockingOnly: appSettings.knockingEnabled ? parameters.isKnockingOnly : false) + let bindings = CreateRoomScreenViewStateBindings(roomTopic: parameters.topic, + isRoomPrivate: parameters.isRoomPrivate, + isKnockingOnly: appSettings.knockingEnabled ? parameters.isKnockingOnly : false) - super.init(initialViewState: CreateRoomViewState(roomName: parameters.name, - serverName: userSession.clientProxy.userIDServerName ?? "", - isKnockingFeatureEnabled: appSettings.knockingEnabled, - selectedUsers: selectedUsers, - aliasLocalPart: parameters.aliasLocalPart ?? roomAliasNameFromRoomDisplayName(roomName: parameters.name), - bindings: bindings), + super.init(initialViewState: CreateRoomScreenViewState(roomName: parameters.name, + serverName: userSession.clientProxy.userIDServerName ?? "", + isKnockingFeatureEnabled: appSettings.knockingEnabled, + aliasLocalPart: parameters.aliasLocalPart ?? roomAliasNameFromRoomDisplayName(roomName: parameters.name), + bindings: bindings), 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() } // MARK: - Public - override func process(viewAction: CreateRoomViewAction) { + override func process(viewAction: CreateRoomScreenViewAction) { switch viewAction { case .createRoom: - Task { - await createRoom() - } - case .deselectUser(let user): - state.selectedUsers.removeAll { $0.userID == user.userID } - actionsSubject.send(.updateSelectedUsers(state.selectedUsers)) + Task { await createRoom() } case .displayCameraPicker: actionsSubject.send(.displayCameraPicker) case .displayMediaPicker: actionsSubject.send(.displayMediaPicker) case .removeImage: - actionsSubject.send(.removeImage) + parameters.avatarImageMedia = nil + state.avatarURL = nil case .updateAliasLocalPart(let aliasLocalPart): state.aliasLocalPart = aliasLocalPart.lowercased() // 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 private func setupBindings() { @@ -135,7 +148,6 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol .sink { [weak self] state in guard let self else { return } updateParameters(state: state) - actionsSubject.send(.updateDetails(createRoomParameters)) } .store(in: &cancellables) @@ -183,15 +195,15 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol .store(in: &cancellables) } - private func updateParameters(state: CreateRoomViewState) { - createRoomParameters.name = state.roomName - createRoomParameters.topic = state.bindings.roomTopic - createRoomParameters.isRoomPrivate = state.bindings.isRoomPrivate - createRoomParameters.isKnockingOnly = state.bindings.isKnockingOnly + private func updateParameters(state: CreateRoomScreenViewState) { + parameters.name = state.roomName + parameters.topic = state.bindings.roomTopic + parameters.isRoomPrivate = state.bindings.isRoomPrivate + parameters.isKnockingOnly = state.bindings.isKnockingOnly if state.isKnockingFeatureEnabled, !state.aliasLocalPart.isEmpty { - createRoomParameters.aliasLocalPart = state.aliasLocalPart + parameters.aliasLocalPart = state.aliasLocalPart } else { - createRoomParameters.aliasLocalPart = nil + parameters.aliasLocalPart = nil } } @@ -205,8 +217,8 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol updateParameters(state: state) // Better to double check the errors also when trying to create the room - if state.isKnockingFeatureEnabled, !createRoomParameters.isRoomPrivate { - guard let canonicalAlias = String.makeCanonicalAlias(aliasLocalPart: createRoomParameters.aliasLocalPart, + if state.isKnockingFeatureEnabled, !parameters.isRoomPrivate { + guard let canonicalAlias = String.makeCanonicalAlias(aliasLocalPart: parameters.aliasLocalPart, serverName: state.serverName), isRoomAliasFormatValid(alias: canonicalAlias) else { state.aliasErrors = [.invalidSymbols] @@ -226,7 +238,7 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol } let avatarURL: URL? - if let media = createRoomParameters.avatarImageMedia { + if let media = parameters.avatarImageMedia { switch await userSession.clientProxy.uploadMedia(media) { case .success(let url): avatarURL = URL(string: url) @@ -251,17 +263,23 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol avatarURL = nil } - switch await userSession.clientProxy.createRoom(name: createRoomParameters.name, - topic: createRoomParameters.topic.isBlank ? nil : createRoomParameters.topic, - isRoomPrivate: createRoomParameters.isRoomPrivate, + switch await userSession.clientProxy.createRoom(name: parameters.name, + topic: parameters.topic.isBlank ? nil : parameters.topic, + isRoomPrivate: parameters.isRoomPrivate, // As of right now we don't want to make private rooms with the knock rule - isKnockingOnly: createRoomParameters.isRoomPrivate ? false : createRoomParameters.isKnockingOnly, - userIDs: state.selectedUsers.map(\.userID), + isKnockingOnly: parameters.isRoomPrivate ? false : parameters.isKnockingOnly, + userIDs: [], // The invite users screen is shown next so we don't need to invite anyone right now. avatarURL: avatarURL, - aliasLocalPart: createRoomParameters.isRoomPrivate ? nil : createRoomParameters.aliasLocalPart) { - case .success(let roomId): + aliasLocalPart: parameters.isRoomPrivate ? nil : parameters.aliasLocalPart) { + 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) - actionsSubject.send(.openRoom(withIdentifier: roomId)) + actionsSubject.send(.createdRoom(roomProxy)) case .failure: state.bindings.alertInfo = AlertInfo(id: .failedCreatingRoom, title: L10n.commonError, @@ -271,7 +289,7 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol // MARK: Loading indicator - private static let loadingIndicatorIdentifier = "\(CreateRoomViewModel.self)-Loading" + private static let loadingIndicatorIdentifier = "\(CreateRoomScreenViewModel.self)-Loading" private func showLoadingIndicator() { userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, diff --git a/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModelProtocol.swift b/ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenViewModelProtocol.swift similarity index 51% rename from ElementX/Sources/Screens/CreateRoom/CreateRoomViewModelProtocol.swift rename to ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenViewModelProtocol.swift index 84092ded1..ccfd46328 100644 --- a/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModelProtocol.swift +++ b/ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenViewModelProtocol.swift @@ -7,9 +7,12 @@ // import Combine +import Foundation @MainActor -protocol CreateRoomViewModelProtocol { - var actions: AnyPublisher { get } - var context: CreateRoomViewModelType.Context { get } +protocol CreateRoomScreenViewModelProtocol { + var actions: AnyPublisher { get } + var context: CreateRoomScreenViewModelType.Context { get } + + func updateAvatar(fileURL: URL) } diff --git a/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift b/ElementX/Sources/Screens/CreateRoomScreen/View/CreateRoomScreen.swift similarity index 70% rename from ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift rename to ElementX/Sources/Screens/CreateRoomScreen/View/CreateRoomScreen.swift index f21079e9a..c2d96c4de 100644 --- a/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift +++ b/ElementX/Sources/Screens/CreateRoomScreen/View/CreateRoomScreen.swift @@ -10,7 +10,7 @@ import Compound import SwiftUI struct CreateRoomScreen: View { - @ObservedObject var context: CreateRoomViewModel.Context + @ObservedObject var context: CreateRoomScreenViewModel.Context @FocusState private var focus: Focus? private enum Focus { @@ -52,7 +52,6 @@ struct CreateRoomScreen: View { .navigationTitle(L10n.screenCreateRoomTitle) .navigationBarTitleDisplayMode(.inline) .toolbar { toolbar } - .readFrame($frame) .alert(item: $context.alertInfo) .shouldScrollOnKeyboardDidShow(focus == .alias, to: Focus.alias) } @@ -109,6 +108,7 @@ struct CreateRoomScreen: View { } } .buttonStyle(.plain) + .accessibilityIdentifier(A11yIdentifiers.createRoomScreen.roomAvatar) .confirmationDialog("", isPresented: $context.showAttachmentConfirmationDialog) { Button(L10n.actionTakePhoto) { context.send(viewAction: .displayCameraPicker) @@ -116,6 +116,8 @@ struct CreateRoomScreen: View { Button(L10n.actionChoosePhoto) { context.send(viewAction: .displayMediaPicker) } + .accessibilityIdentifier(A11yIdentifiers.createRoomScreen.mediaPicker) + if context.viewState.avatarURL != nil { Button(L10n.actionRemove, role: .destructive) { context.send(viewAction: .removeImage) @@ -134,32 +136,9 @@ struct CreateRoomScreen: View { } header: { Text(L10n.screenCreateRoomTopicLabel) .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 { Section { ListRow(label: .default(title: L10n.screenCreateRoomPrivateOptionTitle, @@ -223,6 +202,7 @@ struct CreateRoomScreen: View { context.send(viewAction: .createRoom) } .disabled(!context.viewState.canCreateRoom) + .accessibilityIdentifier(A11yIdentifiers.createRoomScreen.create) } } } @@ -232,65 +212,43 @@ struct CreateRoomScreen: View { struct CreateRoom_Previews: PreviewProvider, TestablePreview { static let viewModel = { let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "@userid:example.com")))) - let parameters = CreateRoomFlowParameters() - let selectedUsers: [UserProfileProxy] = [.mockAlice, .mockBob, .mockCharlie] - - return CreateRoomViewModel(userSession: userSession, - createRoomParameters: .init(parameters), - 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) + return CreateRoomScreenViewModel(userSession: userSession, + initialParameters: .init(), + analytics: ServiceLocator.shared.analytics, + userIndicatorController: UserIndicatorControllerMock(), + appSettings: ServiceLocator.shared.settings) }() static let publicRoomViewModel = { 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 - return CreateRoomViewModel(userSession: userSession, - createRoomParameters: .init(parameters), - selectedUsers: .init([]), - analytics: ServiceLocator.shared.analytics, - userIndicatorController: UserIndicatorControllerMock(), - appSettings: ServiceLocator.shared.settings) + return CreateRoomScreenViewModel(userSession: userSession, + initialParameters: .init(isRoomPrivate: false), + analytics: ServiceLocator.shared.analytics, + userIndicatorController: UserIndicatorControllerMock(), + appSettings: ServiceLocator.shared.settings) }() static let publicRoomInvalidAliasViewModel = { 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 - return CreateRoomViewModel(userSession: userSession, - createRoomParameters: .init(parameters), - selectedUsers: .init([]), - analytics: ServiceLocator.shared.analytics, - userIndicatorController: UserIndicatorControllerMock(), - appSettings: ServiceLocator.shared.settings) + return CreateRoomScreenViewModel(userSession: userSession, + initialParameters: .init(isRoomPrivate: false, aliasLocalPart: "#:"), + analytics: ServiceLocator.shared.analytics, + userIndicatorController: UserIndicatorControllerMock(), + appSettings: ServiceLocator.shared.settings) }() static let publicRoomExistingAliasViewModel = { let clientProxy = ClientProxyMock(.init(userIDServerName: "example.org", userID: "@userid:example.com")) clientProxy.isAliasAvailableReturnValue = .success(false) let userSession = UserSessionMock(.init(clientProxy: clientProxy)) - let parameters = CreateRoomFlowParameters(isRoomPrivate: false, aliasLocalPart: "existing") ServiceLocator.shared.settings.knockingEnabled = true - return CreateRoomViewModel(userSession: userSession, - createRoomParameters: .init(parameters), - selectedUsers: .init([]), - analytics: ServiceLocator.shared.analytics, - userIndicatorController: UserIndicatorControllerMock(), - appSettings: ServiceLocator.shared.settings) + return CreateRoomScreenViewModel(userSession: userSession, + initialParameters: .init(isRoomPrivate: false, aliasLocalPart: "existing"), + analytics: ServiceLocator.shared.analytics, + userIndicatorController: UserIndicatorControllerMock(), + appSettings: ServiceLocator.shared.settings) }() static var previews: some View { @@ -298,27 +256,22 @@ struct CreateRoom_Previews: PreviewProvider, TestablePreview { CreateRoomScreen(context: viewModel.context) } .previewDisplayName("Create Room") - NavigationStack { - CreateRoomScreen(context: emtpyViewModel.context) - } - .previewDisplayName("Create Room without users") + NavigationStack { CreateRoomScreen(context: publicRoomViewModel.context) } .previewDisplayName("Create Public Room") + NavigationStack { CreateRoomScreen(context: publicRoomInvalidAliasViewModel.context) } - .snapshotPreferences(expect: publicRoomExistingAliasViewModel.context.$viewState.map { state in - !state.aliasErrors.isEmpty - }) + .snapshotPreferences(expect: publicRoomInvalidAliasViewModel.context.$viewState.map { !$0.aliasErrors.isEmpty }) .previewDisplayName("Create Public Room, invalid alias") + NavigationStack { CreateRoomScreen(context: publicRoomExistingAliasViewModel.context) } - .snapshotPreferences(expect: publicRoomExistingAliasViewModel.context.$viewState.map { state in - !state.aliasErrors.isEmpty - }) + .snapshotPreferences(expect: publicRoomExistingAliasViewModel.context.$viewState.map { !$0.aliasErrors.isEmpty }) .previewDisplayName("Create Public Room, existing alias") } } diff --git a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenCoordinator.swift b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenCoordinator.swift index 63031f823..6eb0733a2 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenCoordinator.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenCoordinator.swift @@ -11,8 +11,8 @@ import SwiftUI struct InviteUsersScreenCoordinatorParameters { let userSession: UserSessionProtocol - let selectedUsers: CurrentValuePublisher<[UserProfileProxy], Never>? - let roomType: InviteUsersScreenRoomType + let roomProxy: JoinedRoomProxyProtocol + let isSkippable: Bool let userDiscoveryService: UserDiscoveryServiceProtocol let userIndicatorController: UserIndicatorControllerProtocol let appSettings: AppSettings @@ -20,7 +20,6 @@ struct InviteUsersScreenCoordinatorParameters { enum InviteUsersScreenCoordinatorAction { case dismiss - case proceed(selectedUsers: [UserProfileProxy]) } final class InviteUsersScreenCoordinator: CoordinatorProtocol { @@ -34,8 +33,8 @@ final class InviteUsersScreenCoordinator: CoordinatorProtocol { init(parameters: InviteUsersScreenCoordinatorParameters) { viewModel = InviteUsersScreenViewModel(userSession: parameters.userSession, - selectedUsers: parameters.selectedUsers, - roomType: parameters.roomType, + roomProxy: parameters.roomProxy, + isSkippable: parameters.isSkippable, userDiscoveryService: parameters.userDiscoveryService, userIndicatorController: parameters.userIndicatorController, appSettings: parameters.appSettings) @@ -47,8 +46,6 @@ final class InviteUsersScreenCoordinator: CoordinatorProtocol { switch action { case .dismiss: actionsSubject.send(.dismiss) - case .proceed(let selectedUsers): - actionsSubject.send(.proceed(selectedUsers: selectedUsers)) } } .store(in: &cancellables) diff --git a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenModels.swift b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenModels.swift index 5e3c66efc..78ce349ed 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenModels.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenModels.swift @@ -16,7 +16,6 @@ enum InviteUsersScreenErrorType: Error { enum InviteUsersScreenViewModelAction { case dismiss - case proceed(selectedUsers: [UserProfileProxy]) } enum InviteUsersScreenRoomType { @@ -53,18 +52,18 @@ struct InviteUsersScreenViewState: BindableState { membershipState[user.userID] } - let isCreatingRoom: Bool + let isSkippable: Bool var actionText: String { - if isCreatingRoom { - return selectedUsers.isEmpty ? L10n.actionSkip : L10n.actionNext + if isSkippable, selectedUsers.isEmpty { + L10n.actionSkip } else { - return L10n.actionInvite + L10n.actionInvite } } var isActionDisabled: Bool { - isCreatingRoom ? false : selectedUsers.isEmpty + isSkippable ? false : selectedUsers.isEmpty } } diff --git a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift index 7f00bab5d..1629e1366 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift @@ -13,7 +13,7 @@ import SwiftUI typealias InviteUsersScreenViewModelType = StateStoreViewModel class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScreenViewModelProtocol { - private let roomType: InviteUsersScreenRoomType + private let roomProxy: JoinedRoomProxyProtocol private let userDiscoveryService: UserDiscoveryServiceProtocol private let userIndicatorController: UserIndicatorControllerProtocol private let appSettings: AppSettings @@ -25,22 +25,19 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr actionsSubject.eraseToAnyPublisher() } - private let selectedUsers: CurrentValuePublisher<[UserProfileProxy], Never>? - init(userSession: UserSessionProtocol, - selectedUsers: CurrentValuePublisher<[UserProfileProxy], Never>?, - roomType: InviteUsersScreenRoomType, + roomProxy: JoinedRoomProxyProtocol, + isSkippable: Bool, userDiscoveryService: UserDiscoveryServiceProtocol, userIndicatorController: UserIndicatorControllerProtocol, appSettings: AppSettings) { - self.roomType = roomType + self.roomProxy = roomProxy self.userDiscoveryService = userDiscoveryService self.userIndicatorController = userIndicatorController self.appSettings = appSettings - self.selectedUsers = selectedUsers - super.init(initialViewState: InviteUsersScreenViewState(selectedUsers: selectedUsers?.value ?? [], - isCreatingRoom: roomType.isCreatingRoom), + super.init(initialViewState: InviteUsersScreenViewState(selectedUsers: [], + isSkippable: isSkippable), mediaProvider: userSession.mediaProvider) setupSubscriptions() @@ -62,12 +59,7 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr case .cancel: actionsSubject.send(.dismiss) case .proceed: - switch roomType { - case .draft: - actionsSubject.send(.proceed(selectedUsers: state.selectedUsers)) - case .room(let roomProxy): - inviteUsers(state.selectedUsers.map(\.userID), roomProxy: roomProxy) - } + inviteUsers(state.selectedUsers.map(\.userID), roomProxy: roomProxy) case .toggleUser(let user): toggleUser(user) } @@ -78,7 +70,7 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr private func toggleUser(_ user: UserProfileProxy) { if state.selectedUsers.contains(user) { state.scrollToLastID = nil - state.selectedUsers.removeAll(where: { $0.userID == user.userID }) + state.selectedUsers.removeAll { $0.userID == user.userID } } else { state.scrollToLastID = user.userID state.selectedUsers.append(user) @@ -87,15 +79,14 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr private func inviteUsers(_ users: [String], roomProxy: JoinedRoomProxyProtocol) { if appSettings.enableKeyShareOnInvite { - showLoader(title: L10n.screenRoomDetailsInvitePeoplePreparing, - message: L10n.screenRoomDetailsInvitePeopleDontClose) + showLoadingIndicator(title: L10n.screenRoomDetailsInvitePeoplePreparing, message: L10n.screenRoomDetailsInvitePeopleDontClose) } else { - showLoader() + showLoadingIndicator() } Task { defer { - hideLoader() + hideLoadingIndicator() actionsSubject.send(.dismiss) } @@ -122,7 +113,7 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr } private func buildMembershipStateIfNeeded(members: [RoomMemberProxyProtocol]) { - showLoader() + showLoadingIndicator() Task.detached { [members] in // 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 self.state.membershipState = membershipState - self.hideLoader() + self.hideLoadingIndicator() } } } @@ -150,25 +141,13 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr self?.fetchUsers() } .store(in: &cancellables) - - if let selectedUsers { - selectedUsers - .sink { [weak self] users in - self?.state.selectedUsers = users - } - .store(in: &cancellables) - } } private func fetchMembersIfNeeded() { - guard case let .room(roomProxy) = roomType else { - return - } - Task { - showLoader() + showLoadingIndicator() await roomProxy.updateMembers() - hideLoader() + hideLoadingIndicator() } roomProxy.membersPublisher @@ -211,8 +190,8 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr private let userIndicatorID = UUID().uuidString - private func showLoader(title: String = L10n.commonLoading, - message: String? = nil) { + private func showLoadingIndicator(title: String = L10n.commonLoading, + message: String? = nil) { userIndicatorController.submitIndicator(UserIndicator(id: userIndicatorID, type: .modal, title: title, @@ -221,18 +200,7 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr delay: .milliseconds(200)) } - private func hideLoader() { + private func hideLoadingIndicator() { userIndicatorController.retractIndicatorWithId(userIndicatorID) } } - -private extension InviteUsersScreenRoomType { - var isCreatingRoom: Bool { - switch self { - case .draft: - return true - case .room: - return false - } - } -} diff --git a/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift b/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift index 2d1928f85..5f29b6657 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift @@ -32,6 +32,7 @@ struct InviteUsersScreen: View { accessibilityFocusOnStart: true) .compoundSearchField() .alert(item: $context.alertInfo) + .navigationBarBackButtonHidden(context.viewState.isSkippable) } // MARK: - Private @@ -126,7 +127,7 @@ struct InviteUsersScreen: View { @ToolbarContentBuilder private var toolbar: some ToolbarContent { - if !context.viewState.isCreatingRoom { + if !context.viewState.isSkippable { ToolbarItem(placement: .cancellationAction) { Button(L10n.actionCancel) { context.send(viewAction: .cancel) @@ -155,8 +156,8 @@ struct InviteUsersScreen_Previews: PreviewProvider, TestablePreview { let userDiscoveryService = UserDiscoveryServiceMock() userDiscoveryService.searchProfilesWithReturnValue = .success([.mockAlice]) return InviteUsersScreenViewModel(userSession: UserSessionMock(.init()), - selectedUsers: .init([]), - roomType: .draft, + roomProxy: JoinedRoomProxyMock(.init()), + isSkippable: true, userDiscoveryService: userDiscoveryService, userIndicatorController: UserIndicatorControllerMock(), appSettings: ServiceLocator.shared.settings) diff --git a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift index c48f7db52..e6701b206 100644 --- a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift +++ b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift @@ -10,38 +10,26 @@ import Combine import SwiftUI struct StartChatScreenCoordinatorParameters { - let orientationManager: OrientationManagerProtocol let userSession: UserSessionProtocol - let userIndicatorController: UserIndicatorControllerProtocol - weak var navigationStackCoordinator: NavigationStackCoordinator? let userDiscoveryService: UserDiscoveryServiceProtocol - let mediaUploadingPreprocessor: MediaUploadingPreprocessor + let userIndicatorController: UserIndicatorControllerProtocol let appSettings: AppSettings let analytics: AnalyticsService } enum StartChatScreenCoordinatorAction { case close - case openRoom(withIdentifier: String) + case createRoom + case openRoom(roomID: String) case openRoomDirectorySearch } final class StartChatScreenCoordinator: CoordinatorProtocol { private let parameters: StartChatScreenCoordinatorParameters private var viewModel: StartChatScreenViewModelProtocol - private let actionsSubject: PassthroughSubject = .init() private var cancellables = Set() - - private var createRoomParameters = CurrentValueSubject(.init()) - private var createRoomParametersPublisher: CurrentValuePublisher { - createRoomParameters.asCurrentValuePublisher() - } - - private let selectedUsers = CurrentValueSubject<[UserProfileProxy], Never>([]) - private var selectedUsersPublisher: CurrentValuePublisher<[UserProfileProxy], Never> { - selectedUsers.asCurrentValuePublisher() - } + private let actionsSubject: PassthroughSubject = .init() var actions: AnyPublisher { actionsSubject.eraseToAnyPublisher() } @@ -63,10 +51,9 @@ final class StartChatScreenCoordinator: CoordinatorProtocol { case .close: actionsSubject.send(.close) case .createRoom: - // before creating a room we select the users we would like to invite in that room - presentInviteUsersScreen() - case .showRoom(let identifier): - actionsSubject.send(.openRoom(withIdentifier: identifier)) + actionsSubject.send(.createRoom) + case .showRoom(let roomID): + actionsSubject.send(.openRoom(roomID: roomID)) case .openRoomDirectorySearch: actionsSubject.send(.openRoomDirectorySearch) } @@ -79,126 +66,4 @@ final class StartChatScreenCoordinator: CoordinatorProtocol { func toPresentable() -> AnyView { 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) - } } diff --git a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenModels.swift b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenModels.swift index cb45cd014..5261615dd 100644 --- a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenModels.swift +++ b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenModels.swift @@ -16,7 +16,7 @@ enum StartChatScreenErrorType: Error { enum StartChatScreenViewModelAction: Equatable { case close case createRoom - case showRoom(withIdentifier: String) + case showRoom(roomID: String) case openRoomDirectorySearch } diff --git a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenViewModel.swift b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenViewModel.swift index 00cc1c326..6d7e21a3a 100644 --- a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenViewModel.swift +++ b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenViewModel.swift @@ -65,7 +65,7 @@ class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenVie switch currentDirectRoom { case .success(.some(let roomId)): hideLoadingIndicator() - actionsSubject.send(.showRoom(withIdentifier: roomId)) + actionsSubject.send(.showRoom(roomID: roomId)) case .success: hideLoadingIndicator() state.bindings.selectedUserToInvite = user @@ -190,7 +190,7 @@ class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenVie switch await userSession.clientProxy.createDirectRoom(with: user.userID, expectedRoomName: user.displayName) { case .success(let roomId): analytics.trackCreatedRoom(isDM: true) - actionsSubject.send(.showRoom(withIdentifier: roomId)) + actionsSubject.send(.showRoom(roomID: roomId)) case .failure: displayError() } @@ -205,7 +205,7 @@ class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenVie private func joinRoomByAddress() { if case let .addressFound(lastTestedAddress, roomID) = internalRoomAddressState, lastTestedAddress == state.bindings.roomAddress { - actionsSubject.send(.showRoom(withIdentifier: roomID)) + actionsSubject.send(.showRoom(roomID: roomID)) } else if let resolveAliasTask { // If the task is still running we wait for it to complete and we check the state again showLoadingIndicator(delay: .milliseconds(250)) diff --git a/ElementX/Sources/Services/CreateRoom/CreateRoomFlowParameters.swift b/ElementX/Sources/Services/CreateRoom/CreateRoomFlowParameters.swift deleted file mode 100644 index cb2ec25ba..000000000 --- a/ElementX/Sources/Services/CreateRoom/CreateRoomFlowParameters.swift +++ /dev/null @@ -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? -} diff --git a/ElementX/Sources/UITests/UITestsAppCoordinator.swift b/ElementX/Sources/UITests/UITestsAppCoordinator.swift index d237ba62b..477f71686 100644 --- a/ElementX/Sources/UITests/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITests/UITestsAppCoordinator.swift @@ -648,67 +648,48 @@ class MockScreen: Identifiable { retainedState.append(coordinator) coordinator.start() return navigationStackCoordinator - case .startChat: - 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() + case .startChatFlow: let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com")) - let userDiscoveryMock = UserDiscoveryServiceMock() - userDiscoveryMock.searchProfilesWithReturnValue = .success([.mockBob, .mockBobby]) - let userSession = UserSessionMock(.init(clientProxy: clientProxy)) - let coordinator = StartChatScreenCoordinator(parameters: .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)) - navigationStackCoordinator.setRootCoordinator(coordinator) - return navigationStackCoordinator - case .createRoom: + clientProxy.createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartReturnValue = .success("!new-room:client.com") + clientProxy.roomForIdentifierClosure = { roomID in .joined(JoinedRoomProxyMock(.init(id: roomID, members: []))) } + + let userDiscoveryService = UserDiscoveryServiceMock() + userDiscoveryService.searchProfilesWithReturnValue = .success([.mockBob, .mockBobby]) + let navigationStackCoordinator = NavigationStackCoordinator() - let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com")) - let mockUserSession = UserSessionMock(.init(clientProxy: clientProxy)) - let createRoomParameters = CreateRoomFlowParameters() - let selectedUsers: [UserProfileProxy] = [.mockAlice, .mockBob, .mockCharlie] - let parameters = CreateRoomCoordinatorParameters(userSession: mockUserSession, - userIndicatorController: UserIndicatorControllerMock(), - createRoomParameters: .init(createRoomParameters), - selectedUsers: .init(selectedUsers), - appSettings: ServiceLocator.shared.settings, - analytics: ServiceLocator.shared.analytics) - let coordinator = CreateRoomCoordinator(parameters: parameters) - navigationStackCoordinator.setRootCoordinator(coordinator) - return navigationStackCoordinator - case .createRoomNoUsers: - let navigationStackCoordinator = NavigationStackCoordinator() - let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com")) - let mockUserSession = UserSessionMock(.init(clientProxy: clientProxy)) - let createRoomParameters = CreateRoomFlowParameters() - let parameters = CreateRoomCoordinatorParameters(userSession: mockUserSession, - userIndicatorController: UserIndicatorControllerMock(), - createRoomParameters: .init(createRoomParameters), - selectedUsers: .init([]), - appSettings: ServiceLocator.shared.settings, - analytics: ServiceLocator.shared.analytics) - let coordinator = CreateRoomCoordinator(parameters: parameters) - navigationStackCoordinator.setRootCoordinator(coordinator) - return navigationStackCoordinator + let flowCoordinator = StartChatFlowCoordinator(userDiscoveryService: userDiscoveryService, + navigationStackCoordinator: navigationStackCoordinator, + flowParameters: CommonFlowParameters(userSession: UserSessionMock(.init(clientProxy: clientProxy)), + bugReportService: BugReportServiceMock(.init()), + elementCallService: ElementCallServiceMock(.init()), + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), + linkMetadataProvider: LinkMetadataProvider(), + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings, + appHooks: AppHooks(), + analytics: ServiceLocator.shared.analytics, + userIndicatorController: UserIndicatorControllerMock(), + notificationManager: NotificationManagerMock(), + stateMachineFactory: StateMachineFactory())) + flowCoordinator.actionsPublisher + .sink { [weak self] action in + guard let self else { return } + switch action { + case .finished(let roomID): + navigationRootCoordinator.setSheetCoordinator(nil) + case .showRoomDirectory: + break // The test doesn't cover this. + } + } + .store(in: &cancellables) + + 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: let navigationStackCoordinator = NavigationStackCoordinator() let coordinator = PollFormScreenCoordinator(parameters: .init(mode: .new, diff --git a/ElementX/Sources/UITests/UITestsScreenIdentifier.swift b/ElementX/Sources/UITests/UITestsScreenIdentifier.swift index 32b31bdf0..c1d6aafbb 100644 --- a/ElementX/Sources/UITests/UITestsScreenIdentifier.swift +++ b/ElementX/Sources/UITests/UITestsScreenIdentifier.swift @@ -22,8 +22,6 @@ enum UITestsScreenIdentifier: String { case multipleProvidersAuthenticationFlow case bugReport case createPoll - case createRoom - case createRoomNoUsers case encryptionSettings case encryptionSettingsOutOfSync case encryptionReset @@ -44,8 +42,7 @@ enum UITestsScreenIdentifier: String { case roomWithUndisclosedPolls case serverSelection case sessionVerification - case startChat - case startChatWithSearchResults + case startChatFlow case userSessionScreen case userSessionScreenReply case userSessionSpacesFlow diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-iPad-en-GB.png index 67d2d6a54..ec973c045 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1624d4be85be4801b7a407ed8d09c815d98b1eb4422d4fc8095795e3a73cd17 -size 144918 +oid sha256:27b070d92b8846ab416aeb3964d3b698b15aae6a6a5e03f103546bc00b57a291 +size 144359 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-iPad-pseudo.png index e633b1d15..3eb3e7792 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5d668d5fd1d22b0fc782dcf22c1d674fae87d61b289451887e48011ea2c288a9 -size 173173 +oid sha256:96418567c90c7e7a42b4a7129cc803ce0f50de55cb14021fe8c9b5042810c4e4 +size 171338 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-iPhone-16-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-iPhone-16-en-GB.png index b6cef5548..0adcfd83f 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-iPhone-16-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-iPhone-16-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8e3e2883875feedec7a243f676918e3d6f4d05826702a8f2c2807fc5a0bc7bdf -size 96282 +oid sha256:209bc72d50cf549621b1bb690349290aa9f12fd79501cc3089b1dc0a525495c2 +size 95602 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-iPhone-16-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-iPhone-16-pseudo.png index 7ecb1399f..e6eacf2ba 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-iPhone-16-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-iPhone-16-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd0da3fb9239fe4f131b4a19057bd49bda67b72ba7caf17d8f86cb9f6a239fc0 -size 125320 +oid sha256:a9f24fc08ec1f408042f6311ddada15f01413f93806bba7742b417743c8cf011 +size 125251 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-without-users-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-without-users-iPad-en-GB.png deleted file mode 100644 index ec973c045..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-without-users-iPad-en-GB.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:27b070d92b8846ab416aeb3964d3b698b15aae6a6a5e03f103546bc00b57a291 -size 144359 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-without-users-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-without-users-iPad-pseudo.png deleted file mode 100644 index 3eb3e7792..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-without-users-iPad-pseudo.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:96418567c90c7e7a42b4a7129cc803ce0f50de55cb14021fe8c9b5042810c4e4 -size 171338 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-without-users-iPhone-16-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-without-users-iPhone-16-en-GB.png deleted file mode 100644 index 0adcfd83f..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-without-users-iPhone-16-en-GB.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:209bc72d50cf549621b1bb690349290aa9f12fd79501cc3089b1dc0a525495c2 -size 95602 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-without-users-iPhone-16-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-without-users-iPhone-16-pseudo.png deleted file mode 100644 index e6eacf2ba..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/createRoom.Create-Room-without-users-iPhone-16-pseudo.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a9f24fc08ec1f408042f6311ddada15f01413f93806bba7742b417743c8cf011 -size 125251 diff --git a/UITests/Sources/CreateRoomScreenTests.swift b/UITests/Sources/CreateRoomScreenTests.swift deleted file mode 100644 index 7c186400a..000000000 --- a/UITests/Sources/CreateRoomScreenTests.swift +++ /dev/null @@ -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() - } -} diff --git a/UITests/Sources/StartChatScreenTests.swift b/UITests/Sources/StartChatScreenTests.swift deleted file mode 100644 index ddf8f3002..000000000 --- a/UITests/Sources/StartChatScreenTests.swift +++ /dev/null @@ -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() - } -} diff --git a/UITests/Sources/StartChatTests.swift b/UITests/Sources/StartChatTests.swift new file mode 100644 index 000000000..752d646c4 --- /dev/null +++ b/UITests/Sources/StartChatTests.swift @@ -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) + } +} diff --git a/UITests/Sources/__Snapshots__/Application/createRoomScreen.testLanding-iPad-26-0-en-GB.png b/UITests/Sources/__Snapshots__/Application/createRoomScreen.testLanding-iPad-26-0-en-GB.png deleted file mode 100644 index fa210c82a..000000000 --- a/UITests/Sources/__Snapshots__/Application/createRoomScreen.testLanding-iPad-26-0-en-GB.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:71edb284338fe1a3d118141aa237617536b72a31d13333e7148dd2d33ccff1b1 -size 152332 diff --git a/UITests/Sources/__Snapshots__/Application/createRoomScreen.testLanding-iPhone-26-0-en-GB.png b/UITests/Sources/__Snapshots__/Application/createRoomScreen.testLanding-iPhone-26-0-en-GB.png deleted file mode 100644 index bbaa20431..000000000 --- a/UITests/Sources/__Snapshots__/Application/createRoomScreen.testLanding-iPhone-26-0-en-GB.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9c6fcc3f0eea9bb87f6b5fa2c5b652496ca44eee846d3231e4c1446a1a3da2e3 -size 188761 diff --git a/UITests/Sources/__Snapshots__/Application/createRoomScreen.testLandingWithoutUsers-iPad-26-0-en-GB.png b/UITests/Sources/__Snapshots__/Application/createRoomScreen.testLandingWithoutUsers-iPad-26-0-en-GB.png deleted file mode 100644 index a2a60647f..000000000 --- a/UITests/Sources/__Snapshots__/Application/createRoomScreen.testLandingWithoutUsers-iPad-26-0-en-GB.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c7a86c8bd9795786c49bace089439a3e8e0677bae255eba332b2175e89ab3f2b -size 131545 diff --git a/UITests/Sources/__Snapshots__/Application/createRoomScreen.testLandingWithoutUsers-iPhone-26-0-en-GB.png b/UITests/Sources/__Snapshots__/Application/createRoomScreen.testLandingWithoutUsers-iPhone-26-0-en-GB.png deleted file mode 100644 index 66bacf53f..000000000 --- a/UITests/Sources/__Snapshots__/Application/createRoomScreen.testLandingWithoutUsers-iPhone-26-0-en-GB.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:660beea7152721b0ba27876b1f7d9e34035c21abc188c3ab3953552655041dca -size 160050 diff --git a/UITests/Sources/__Snapshots__/Application/createRoomScreen.testLongInputNameText-iPad-26-0-en-GB.png b/UITests/Sources/__Snapshots__/Application/createRoomScreen.testLongInputNameText-iPad-26-0-en-GB.png deleted file mode 100644 index 4a27cfa13..000000000 --- a/UITests/Sources/__Snapshots__/Application/createRoomScreen.testLongInputNameText-iPad-26-0-en-GB.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2e8ebe176c64660475fdb46016a66ddaa502d30d1c60f6ccc4a52c05ca5412ed -size 149193 diff --git a/UITests/Sources/__Snapshots__/Application/createRoomScreen.testLongInputNameText-iPhone-26-0-en-GB.png b/UITests/Sources/__Snapshots__/Application/createRoomScreen.testLongInputNameText-iPhone-26-0-en-GB.png deleted file mode 100644 index fbe23b938..000000000 --- a/UITests/Sources/__Snapshots__/Application/createRoomScreen.testLongInputNameText-iPhone-26-0-en-GB.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4afad7f48503b599fb30a0e4c593079249d6c70f64770d53ce63398a89d245af -size 183810 diff --git a/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-1.png b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-1.png new file mode 100644 index 000000000..b4bf66780 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a1f07788317f9ed52b340b6e3233d50be4f82a047e6233d6abc656c6e1ba0c65 +size 167782 diff --git a/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-2.png b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-2.png new file mode 100644 index 000000000..a10b1aaed --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9543933266405ba35494e5bac5e1b403b0f388657b36b1689a3f27834dd27133 +size 164831 diff --git a/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-3.png b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-3.png new file mode 100644 index 000000000..618bdb9cc --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5201d5b8492dadaa23efd16a5e74ca96e23ce972abe4cd76a20533246a9381af +size 198236 diff --git a/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-4.png b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-4.png new file mode 100644 index 000000000..bdd35755b --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0f8aff85f5930db29bc103b67ae2ea3bd793e6da755b74b3ec4bc3294a24664 +size 2332509 diff --git a/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-5.png b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-5.png new file mode 100644 index 000000000..779944146 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be0bc089efe8628afef744ec9e171a1d3f54b5ac81f3515369a58237c553da0a +size 196815 diff --git a/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-6.png b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-6.png new file mode 100644 index 000000000..d27b1e266 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:02b9e0a8a3ae860c6afe7541021ec11529fb810f13ed7468ed4fbb87d7277e3e +size 147056 diff --git a/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-7.png b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-7.png new file mode 100644 index 000000000..1d0112e21 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-7.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e2ec6a46ee642ad8235dc6cb777330bdeb8a240138409fa88fa9d1f3cc7516b +size 166249 diff --git a/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-8.png b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-8.png new file mode 100644 index 000000000..995ea078f --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:abe91d1332e9da181baa07576006a411ad36aa23dcd664341f9c1cf1e28c4a57 +size 175690 diff --git a/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-99.png b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-99.png new file mode 100644 index 000000000..85cd35443 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPad-26-0-en-GB-99.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:048a07df37bc493d91f67b5e1379ef571db047ab35780b28c23ee1990429846f +size 222243 diff --git a/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-1.png b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-1.png new file mode 100644 index 000000000..2925a9de5 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10eb8f7c10d0eb6a493c33fc223a96276897f5a8db969d7e07694c9872b47485 +size 122115 diff --git a/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-2.png b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-2.png new file mode 100644 index 000000000..edc27153a --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5bebd60c5ddaaf7d4af54563587c51f44816ac8fe5a621632a14374ca639b66b +size 114369 diff --git a/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-3.png b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-3.png new file mode 100644 index 000000000..6d9350d21 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6a481a1eebb2bdc2a51b69da7867d330fd15b465ec66dda9a7775ea81bebc27 +size 170910 diff --git a/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-4.png b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-4.png new file mode 100644 index 000000000..edf0561f2 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c0f51cb1e35bb75ae45f2f909e45f809febe00c86f7b7d88e63e9cdfb322043b +size 2458946 diff --git a/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-5.png b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-5.png new file mode 100644 index 000000000..aaf8f730c --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab217745f60435fc6251221d5650a25e4ce00153cf78ed72021e05c666d19703 +size 166171 diff --git a/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-6.png b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-6.png new file mode 100644 index 000000000..f29130dbd --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7899f024d4aa043b2e318ef188bb992301aa02bc4a92fd5b59215e907272bde2 +size 88945 diff --git a/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-7.png b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-7.png new file mode 100644 index 000000000..d52bcdf99 --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-7.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b788bbcc940303b16942de95cfcbb142e04ecc60634e409f091a20cf3fe8cef1 +size 116322 diff --git a/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-8.png b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-8.png new file mode 100644 index 000000000..0695a545d --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:125138db4c413fa5ffc104694cb5400373d86faee69d0c755e2ffe992211f0ab +size 128890 diff --git a/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-99.png b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-99.png new file mode 100644 index 000000000..cd49d748f --- /dev/null +++ b/UITests/Sources/__Snapshots__/Application/startChat.testFlow-iPhone-26-0-en-GB-99.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47d79566040b9e990cd222dc056d22de86723171364d60a0fdc3592dff9bb48e +size 374177 diff --git a/UITests/Sources/__Snapshots__/Application/startChatScreen.testLanding-iPad-26-0-en-GB.png b/UITests/Sources/__Snapshots__/Application/startChatScreen.testLanding-iPad-26-0-en-GB.png deleted file mode 100644 index bc76ff000..000000000 --- a/UITests/Sources/__Snapshots__/Application/startChatScreen.testLanding-iPad-26-0-en-GB.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d3e4caed4cef8b0ad70f747bb98a7e205c0b4f1c3df6083f341a9ac5bebe6e31 -size 108047 diff --git a/UITests/Sources/__Snapshots__/Application/startChatScreen.testLanding-iPhone-26-0-en-GB.png b/UITests/Sources/__Snapshots__/Application/startChatScreen.testLanding-iPhone-26-0-en-GB.png deleted file mode 100644 index e5f4e5784..000000000 --- a/UITests/Sources/__Snapshots__/Application/startChatScreen.testLanding-iPhone-26-0-en-GB.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:63a31b32e399472a8206f234665a11bb929403b5902856b85cd4f19373d0fd0a -size 120156 diff --git a/UITests/Sources/__Snapshots__/Application/startChatScreen.testSearchWithNoResults-iPad-26-0-en-GB.png b/UITests/Sources/__Snapshots__/Application/startChatScreen.testSearchWithNoResults-iPad-26-0-en-GB.png deleted file mode 100644 index 07afa78d3..000000000 --- a/UITests/Sources/__Snapshots__/Application/startChatScreen.testSearchWithNoResults-iPad-26-0-en-GB.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3156d4c72c601cfbe7584cd73f516a4a32a95e6149dbd7ef31fdd4bba7d14fbf -size 88278 diff --git a/UITests/Sources/__Snapshots__/Application/startChatScreen.testSearchWithNoResults-iPhone-26-0-en-GB.png b/UITests/Sources/__Snapshots__/Application/startChatScreen.testSearchWithNoResults-iPhone-26-0-en-GB.png deleted file mode 100644 index a81a1f226..000000000 --- a/UITests/Sources/__Snapshots__/Application/startChatScreen.testSearchWithNoResults-iPhone-26-0-en-GB.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8744cb36bbdd2afb7809f2682e00d6a3f2d1f3e2e6dfa329a50e97c76d8e9e63 -size 90212 diff --git a/UITests/Sources/__Snapshots__/Application/startChatScreen.testSearchWithResults-iPad-26-0-en-GB.png b/UITests/Sources/__Snapshots__/Application/startChatScreen.testSearchWithResults-iPad-26-0-en-GB.png deleted file mode 100644 index 39a4ba31f..000000000 --- a/UITests/Sources/__Snapshots__/Application/startChatScreen.testSearchWithResults-iPad-26-0-en-GB.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b97677b7315b1d9298468da64358299ef0674644d3042fe23d9e10396131c92a -size 101718 diff --git a/UITests/Sources/__Snapshots__/Application/startChatScreen.testSearchWithResults-iPhone-26-0-en-GB.png b/UITests/Sources/__Snapshots__/Application/startChatScreen.testSearchWithResults-iPhone-26-0-en-GB.png deleted file mode 100644 index 979213ebd..000000000 --- a/UITests/Sources/__Snapshots__/Application/startChatScreen.testSearchWithResults-iPhone-26-0-en-GB.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dd670a1f21b71bf5ddc7d317054145c2a049eb16fa09d6cd6f8be589d5ec45e9 -size 112321 diff --git a/UnitTests/Sources/CreateRoomViewModelTests.swift b/UnitTests/Sources/CreateRoomViewModelTests.swift index efc32002f..05a82c207 100644 --- a/UnitTests/Sources/CreateRoomViewModelTests.swift +++ b/UnitTests/Sources/CreateRoomViewModelTests.swift @@ -13,38 +13,28 @@ import XCTest @MainActor class CreateRoomScreenViewModelTests: XCTestCase { - var viewModel: CreateRoomViewModelProtocol! + var viewModel: CreateRoomScreenViewModelProtocol! var clientProxy: ClientProxyMock! var userSession: UserSessionMock! private let usersSubject = CurrentValueSubject<[UserProfileProxy], Never>([]) - var context: CreateRoomViewModel.Context { + var context: CreateRoomScreenViewModel.Context { viewModel.context } override func setUpWithError() throws { clientProxy = ClientProxyMock(.init(userIDServerName: "matrix.org", userID: "@a:b.com")) + clientProxy.roomForIdentifierClosure = { roomID in .joined(JoinedRoomProxyMock(.init(id: roomID))) } userSession = UserSessionMock(.init(clientProxy: clientProxy)) - let parameters = CreateRoomFlowParameters() ServiceLocator.shared.settings.knockingEnabled = true - let viewModel = CreateRoomViewModel(userSession: userSession, - createRoomParameters: .init(parameters), - selectedUsers: [.mockAlice, .mockBob, .mockCharlie], - analytics: ServiceLocator.shared.analytics, - userIndicatorController: UserIndicatorControllerMock(), - appSettings: ServiceLocator.shared.settings) + let viewModel = CreateRoomScreenViewModel(userSession: userSession, + analytics: ServiceLocator.shared.analytics, + userIndicatorController: UserIndicatorControllerMock(), + appSettings: ServiceLocator.shared.settings) 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() { XCTAssertTrue(context.viewState.bindings.isRoomPrivate) } @@ -65,7 +55,7 @@ class CreateRoomScreenViewModelTests: XCTestCase { // When creating the room. clientProxy.createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartReturnValue = .success("1") 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 } context.send(viewAction: .createRoom) diff --git a/UnitTests/Sources/InviteUsersViewModelTests.swift b/UnitTests/Sources/InviteUsersViewModelTests.swift index 206a57008..656653db5 100644 --- a/UnitTests/Sources/InviteUsersViewModelTests.swift +++ b/UnitTests/Sources/InviteUsersViewModelTests.swift @@ -21,7 +21,10 @@ class InviteUsersScreenViewModelTests: XCTestCase { } 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) context.send(viewAction: .toggleUser(.mockAlice)) XCTAssertTrue(context.viewState.selectedUsers.count == 1) @@ -29,7 +32,10 @@ class InviteUsersScreenViewModelTests: XCTestCase { } 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) context.send(viewAction: .toggleUser(.mockAlice)) XCTAssertEqual(context.viewState.selectedUsers.count, 1) @@ -39,7 +45,10 @@ class InviteUsersScreenViewModelTests: XCTestCase { } 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) context.send(viewAction: .toggleUser(.mockAlice)) XCTAssertEqual(context.viewState.selectedUsers.count, 1) @@ -52,7 +61,7 @@ class InviteUsersScreenViewModelTests: XCTestCase { let mockedMembers: [RoomMemberProxyMock] = [.mockAlice, .mockBob] let roomProxy = JoinedRoomProxyMock(.init(name: "test", members: mockedMembers)) roomProxy.inviteUserIDReturnValue = .success(()) - setupWithRoomType(roomType: .room(roomProxy: roomProxy)) + setupViewModel(roomProxy: roomProxy, isSkippable: false) let deferredState = deferFulfillment(viewModel.context.$viewState) { state in state.isUserSelected(.mockAlice) @@ -66,8 +75,6 @@ class InviteUsersScreenViewModelTests: XCTestCase { switch action { case .dismiss: return true - default: - return false } } @@ -77,12 +84,12 @@ class InviteUsersScreenViewModelTests: XCTestCase { XCTAssertEqual(roomProxy.inviteUserIDReceivedInvocations, [RoomMemberProxyMock.mockAlice.userID]) } - private func setupWithRoomType(roomType: InviteUsersScreenRoomType) { + private func setupViewModel(roomProxy: JoinedRoomProxyProtocol, isSkippable: Bool) { userDiscoveryService = UserDiscoveryServiceMock() userDiscoveryService.searchProfilesWithReturnValue = .success([]) let viewModel = InviteUsersScreenViewModel(userSession: UserSessionMock(.init()), - selectedUsers: nil, - roomType: roomType, + roomProxy: roomProxy, + isSkippable: isSkippable, userDiscoveryService: userDiscoveryService, userIndicatorController: UserIndicatorControllerMock(), appSettings: ServiceLocator.shared.settings) diff --git a/UnitTests/Sources/StartChatViewModelTests.swift b/UnitTests/Sources/StartChatViewModelTests.swift index 2b14b860f..bbe6cb7f8 100644 --- a/UnitTests/Sources/StartChatViewModelTests.swift +++ b/UnitTests/Sources/StartChatViewModelTests.swift @@ -56,7 +56,7 @@ class StartChatScreenViewModelTests: XCTestCase { try await deferredViewState.fulfill() let deferredAction = deferFulfillment(viewModel.actions) { action in - action == .showRoom(withIdentifier: "id") + action == .showRoom(roomID: "id") } context.send(viewAction: .joinRoomByAddress) try await deferredAction.fulfill()