From fead5ff191ec95bc3e141c2a1c8309ca8b1c642f Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Tue, 5 Aug 2025 11:06:26 +0100 Subject: [PATCH] Initial setup for the SpaceListScreen. (#4380) --- .../Sources/GeneratedAccessibilityTests.swift | 4 + ElementX.xcodeproj/project.pbxproj | 40 +++++++ .../UserSessionFlowCoordinator.swift | 7 +- .../TestablePreviewsDictionary.swift | 1 + .../SpaceListScreenCoordinator.swift | 54 ++++++++++ .../SpaceListScreenModels.swift | 33 ++++++ .../SpaceListScreenViewModel.swift | 47 ++++++++ .../SpaceListScreenViewModelProtocol.swift | 14 +++ .../View/SpaceListScreen.swift | 101 ++++++++++++++++++ .../Sources/GeneratedPreviewTests.swift | 6 ++ .../spaceListScreen.iPad-en-GB-0.png | 3 + .../spaceListScreen.iPad-pseudo-0.png | 3 + .../spaceListScreen.iPhone-16-en-GB-0.png | 3 + .../spaceListScreen.iPhone-16-pseudo-0.png | 3 + .../ElementX/TemplateScreenCoordinator.swift | 2 +- .../ElementX/TemplateScreenModels.swift | 2 +- .../ElementX/TemplateScreenViewModel.swift | 2 +- .../TemplateScreenViewModelProtocol.swift | 2 +- .../ElementX/View/TemplateScreen.swift | 2 +- .../SpaceListScreenViewModelTests.swift | 33 ++++++ 20 files changed, 356 insertions(+), 6 deletions(-) create mode 100644 ElementX/Sources/Screens/SpaceListScreen/SpaceListScreenCoordinator.swift create mode 100644 ElementX/Sources/Screens/SpaceListScreen/SpaceListScreenModels.swift create mode 100644 ElementX/Sources/Screens/SpaceListScreen/SpaceListScreenViewModel.swift create mode 100644 ElementX/Sources/Screens/SpaceListScreen/SpaceListScreenViewModelProtocol.swift create mode 100644 ElementX/Sources/Screens/SpaceListScreen/View/SpaceListScreen.swift create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/spaceListScreen.iPad-en-GB-0.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/spaceListScreen.iPad-pseudo-0.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/spaceListScreen.iPhone-16-en-GB-0.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/spaceListScreen.iPhone-16-pseudo-0.png create mode 100644 UnitTests/Sources/SpaceListScreenViewModelTests.swift diff --git a/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift b/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift index a99fa66dc..43c5323e8 100644 --- a/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift +++ b/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift @@ -583,6 +583,10 @@ extension AccessibilityTests { try await performAccessibilityAudit(named: "SoftLogoutScreen_Previews") } + func testSpaceListScreen() async throws { + try await performAccessibilityAudit(named: "SpaceListScreen_Previews") + } + func testSplashScreen() async throws { try await performAccessibilityAudit(named: "SplashScreen_Previews") } diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 44a3d412f..c1bc7e7e7 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -144,6 +144,7 @@ 182D532B736178A1DED9F76E /* ReportRoomScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FCAE847556719BBE7A0882 /* ReportRoomScreenModels.swift */; }; 18386B777FDA74E4B3282D4F /* TimelineItemThreadSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98C6A082F2B2A15E1B9BE280 /* TimelineItemThreadSummary.swift */; }; 18867F4F1C8991EEC56EA932 /* UTType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 897DF5E9A70CE05A632FC8AF /* UTType.swift */; }; + 188B0E4EA6CE5711A0566087 /* SpaceListScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 752814FC7CE5A730B04F8142 /* SpaceListScreenViewModel.swift */; }; 18978C9438206828C1D5AF2A /* test_animated_image.gif in Resources */ = {isa = PBXBuildFile; fileRef = 53FD6D3D38F556CEAA280C58 /* test_animated_image.gif */; }; 18E3786918486D4C9726BC84 /* FormButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89FBFC09F9DAFF1E4BA97849 /* FormButtonStyles.swift */; }; 18FDE4ED6D83B0771452B43D /* RoomSelectionScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F104596B0620CEFE5DFD31B1 /* RoomSelectionScreenCoordinator.swift */; }; @@ -637,6 +638,7 @@ 784592335560C2E91D32D177 /* DeveloperOptionsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06B098A612DCB5A7358EECD5 /* DeveloperOptionsScreenModels.swift */; }; 785613C0C092B532198EB3BB /* TimelineStartRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44ECC9D66400727DFFEE12E8 /* TimelineStartRoomTimelineView.swift */; }; 7856DE3EA4580AE0329986EB /* ComposerDraftServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E600B315B920B9687F8EE1B /* ComposerDraftServiceMock.swift */; }; + 789C3667354AE2DD9646ECB8 /* SpaceListScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 005CA6F5F5088A8868DC6CED /* SpaceListScreen.swift */; }; 78A3D84BA47DAC69B4D0A34C /* CollapsibleRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBF273BC2BFB9F3EEFA988B /* CollapsibleRoomTimelineView.swift */; }; 795A854F63301DC6B46217B9 /* AccessibilityIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BB8DDE245ED86C489BA983 /* AccessibilityIdentifiers.swift */; }; 79741C1953269FF1A211D246 /* RoomPollsHistoryScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0E14FF533D25A0692F7CEB0 /* RoomPollsHistoryScreenViewModel.swift */; }; @@ -688,6 +690,7 @@ 80DEA2A4B20F9E279EAE6B2B /* UserProfile+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD01F7FC2BBAC7351948595 /* UserProfile+Mock.swift */; }; 80F1B442DB5E2C362ACDD8E2 /* ZoomTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018194CAFBE80720FECCEDEE /* ZoomTransition.swift */; }; 80F6C8EFCA4564B67F0D34B0 /* DeactivateAccountScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77F75B3E9F99864048A422A /* DeactivateAccountScreenViewModelTests.swift */; }; + 8182E5914C729ABA646B1B5F /* SpaceListScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 503AADD7C9021C4E0ED92323 /* SpaceListScreenCoordinator.swift */; }; 81A7C020CB5F6232242A8414 /* UserSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F36C0A6D59717193F49EA986 /* UserSessionTests.swift */; }; 8285FF4B2C2331758C437FF7 /* ReportContentScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 713B48DBF65DE4B0DD445D66 /* ReportContentScreenViewModelProtocol.swift */; }; 828EA5009557C2B9DCD4CA0F /* UserDiscoverySection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */; }; @@ -779,6 +782,7 @@ 91C6AC0E9D2B9C0C76CC6AD4 /* RoomDirectorySearchScreenScreenModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3984C93B8E9B10C92DADF9EE /* RoomDirectorySearchScreenScreenModelProtocol.swift */; }; 91D1A46A733EC24C081DD353 /* SessionVerificationRequestDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A1265FAF2C0AF1C30605BE7 /* SessionVerificationRequestDetailsView.swift */; }; 92012C96039BC8C2CAEBA9E2 /* AuthenticationServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671C338B7259DC5774816885 /* AuthenticationServiceTests.swift */; }; + 920DC020F18ABC88175114D3 /* SpaceListScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5557DDA438841AF5DC003D0B /* SpaceListScreenViewModelTests.swift */; }; 9219640F4D980CFC5FE855AD /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 536E72DCBEEC4A1FE66CFDCE /* target.yml */; }; 92720AB0DA9AB5EEF1DAF56B /* SecureBackupLogoutConfirmationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC017C3CB6B0F7C63F460F2 /* SecureBackupLogoutConfirmationScreenViewModel.swift */; }; 9278EC51D24E57445B290521 /* AudioSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB284643AF7AB131E307DCE0 /* AudioSessionProtocol.swift */; }; @@ -1283,6 +1287,7 @@ F4996C82A4B3A5FF0C8EDD03 /* RoomListFilterModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = E06AAD6D9D3F5833E7A5A2F9 /* RoomListFilterModels.swift */; }; F4C005F006FC3657B9F0A31D /* BugReportHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25586C0ADB814FEE9897DCAA /* BugReportHook.swift */; }; F4D5A2A8304ED61621BF02D4 /* test_audio.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 66B96842BF5F8ACA1AC84C55 /* test_audio.mp3 */; }; + F50C34684647D8357A460BC8 /* SpaceListScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB311E22D08E85017E15FC0 /* SpaceListScreenModels.swift */; }; F519DE17A3A0F760307B2E6D /* InviteUsersScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D155E09BF961BBA8F85263 /* InviteUsersScreenViewModel.swift */; }; F54E2D6CAD96E1AC15BC526F /* MessageForwardingScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E60332509665C00179ACF6 /* MessageForwardingScreenViewModel.swift */; }; F5D2270B5021D521C0D22E11 /* FlowCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B9FCA1CFD07B8CF9BD21266 /* FlowCoordinatorProtocol.swift */; }; @@ -1307,6 +1312,7 @@ F86102DC2C68BBBB0521BAAE /* SoftLogoutScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BB385E148DE55C85C0A02D6 /* SoftLogoutScreenModels.swift */; }; F8B2F5CBCF2A0E0798E8D646 /* TimelineViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5B00A014307CE37B2812CD /* TimelineViewModelProtocol.swift */; }; F8C87130FD999F7F1076208C /* RoomChangePermissionsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89AAEA70CFF3284920811941 /* RoomChangePermissionsScreen.swift */; }; + F8D8E142445CB4628F7DC533 /* SpaceListScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6949BC5A16D9BDD932FF93D /* SpaceListScreenViewModelProtocol.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 */; }; @@ -1429,6 +1435,7 @@ /* Begin PBXFileReference section */ 002399C6CB875C4EBB01CBC0 /* MediaEventsTimelineScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaEventsTimelineScreen.swift; sourceTree = ""; }; 00245D40CD90FD71D6A05239 /* EmojiPickerScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreen.swift; sourceTree = ""; }; + 005CA6F5F5088A8868DC6CED /* SpaceListScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceListScreen.swift; sourceTree = ""; }; 00AFC5F08734C2EA4EE79C59 /* IdentityConfirmationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreen.swift; sourceTree = ""; }; 00E5B2CBEF8F96424F095508 /* RoomDetailsEditScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenViewModelTests.swift; sourceTree = ""; }; 011AFA4990C585D157829679 /* DeclineAndBlockScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeclineAndBlockScreenViewModel.swift; sourceTree = ""; }; @@ -1502,6 +1509,7 @@ 0DF5CBAF69BDF5DF31C661E1 /* IntentionalMentions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentionalMentions.swift; sourceTree = ""; }; 0E8BDC092D817B68CD9040C5 /* UserSessionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStore.swift; sourceTree = ""; }; 0E95B3BDB80531C85CD50AE6 /* InvitedRoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitedRoomProxy.swift; sourceTree = ""; }; + 0EB311E22D08E85017E15FC0 /* SpaceListScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceListScreenModels.swift; sourceTree = ""; }; 0EE9EAF0309A2A1D67D8FAF5 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sv; path = sv.lproj/Localizable.stringsdict; sourceTree = ""; }; 0F5567A7EF6F2AB9473236F6 /* DocumentPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentPicker.swift; sourceTree = ""; }; 0F64447FF544298A6A3BEF85 /* NotificationSettingsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreenModels.swift; sourceTree = ""; }; @@ -1844,6 +1852,7 @@ 4FCB2126C091EEF2454B4D56 /* RoomFlowCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomFlowCoordinatorTests.swift; sourceTree = ""; }; 4FDD775CFD72DD2D3C8A8390 /* NotificationSettingsProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsProxyProtocol.swift; sourceTree = ""; }; 502F986D57158674172C58E3 /* AppLockSetupSettingsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupSettingsScreenModels.swift; sourceTree = ""; }; + 503AADD7C9021C4E0ED92323 /* SpaceListScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceListScreenCoordinator.swift; sourceTree = ""; }; 505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelTests.swift; sourceTree = ""; }; 505ADA084C0B38A0C4AD2574 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 5098DA7799946A61E34A2373 /* FileRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineItem.swift; sourceTree = ""; }; @@ -1869,6 +1878,7 @@ 54A5E6F398C269AD52C9AE21 /* EncryptionResetPasswordScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreenModels.swift; sourceTree = ""; }; 54AD70D6E03D2031AE1B5A52 /* TimelineReactionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReactionsView.swift; sourceTree = ""; }; 54C4E7B46099462F12000C91 /* DeveloperOptionsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenViewModelProtocol.swift; sourceTree = ""; }; + 5557DDA438841AF5DC003D0B /* SpaceListScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceListScreenViewModelTests.swift; sourceTree = ""; }; 55AEEF8142DF1B59DB40FB93 /* TimelineItemSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemSender.swift; sourceTree = ""; }; 55B3BF242C93CD32C4C3E08D /* ChatsFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatsFlowCoordinator.swift; sourceTree = ""; }; 5644919DB2022397D9D5825A /* MockSoftLogoutScreenState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSoftLogoutScreenState.swift; sourceTree = ""; }; @@ -2012,6 +2022,7 @@ 74DD0855F2F76D47E5555082 /* MediaUploadPreviewScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenCoordinator.swift; sourceTree = ""; }; 74E08B8A66948E9690F38B94 /* SecureBackupLogoutConfirmationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenViewModelProtocol.swift; sourceTree = ""; }; 7509AB72755DCC4B4E721B36 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/SAS.strings; sourceTree = ""; }; + 752814FC7CE5A730B04F8142 /* SpaceListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceListScreenViewModel.swift; sourceTree = ""; }; 752A0EB49BF5BCEA37EDF7A3 /* Signposter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signposter.swift; sourceTree = ""; }; 753B4C6C0EDDCBF0708DC384 /* TimelineItemSendInfoLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemSendInfoLabel.swift; sourceTree = ""; }; 75821CD31A4BD02B99C327A4 /* DataProtectionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataProtectionManager.swift; sourceTree = ""; }; @@ -2348,6 +2359,7 @@ B6404166CBF5CC88673FF9E2 /* RoomDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetails.swift; sourceTree = ""; }; B65DDCF8E41759890355ACBC /* AuthenticationStartScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationStartScreenViewModelProtocol.swift; sourceTree = ""; }; B68B31232312AFC844440BFE /* DeclineAndBlockScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeclineAndBlockScreenModels.swift; sourceTree = ""; }; + B6949BC5A16D9BDD932FF93D /* SpaceListScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceListScreenViewModelProtocol.swift; sourceTree = ""; }; B69AEA8755382DB34892FB7B /* ThreadTimelineScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadTimelineScreenModels.swift; sourceTree = ""; }; B6A293D06BAB2B7A17D9314B /* VoiceMessageRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRoomTimelineView.swift; sourceTree = ""; }; B6C585CE1F721A2770C70D47 /* TimelineControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineControllerProtocol.swift; sourceTree = ""; }; @@ -3024,6 +3036,18 @@ path = CallScreen; sourceTree = ""; }; + 12E4648360EEC92520C6BC7E /* SpaceListScreen */ = { + isa = PBXGroup; + children = ( + 503AADD7C9021C4E0ED92323 /* SpaceListScreenCoordinator.swift */, + 0EB311E22D08E85017E15FC0 /* SpaceListScreenModels.swift */, + 752814FC7CE5A730B04F8142 /* SpaceListScreenViewModel.swift */, + B6949BC5A16D9BDD932FF93D /* SpaceListScreenViewModelProtocol.swift */, + CF870CEE51C0AEFABBB525FE /* View */, + ); + path = SpaceListScreen; + sourceTree = ""; + }; 13263FFEA7749D822B51AA90 /* AppLock */ = { isa = PBXGroup; children = ( @@ -4489,6 +4513,7 @@ DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */, 9FE8A35B4AD5256F4B562274 /* SettingsScreenViewModelTests.swift */, AC43313F21511C853D34544E /* SoftLogoutScreenViewModelTests.swift */, + 5557DDA438841AF5DC003D0B /* SpaceListScreenViewModelTests.swift */, 6DF438EAFC732D2D95D34BF6 /* StartChatViewModelTests.swift */, C833673B334A0651AB46F30B /* StaticLocationScreenViewModelTests.swift */, 2CEBCB9676FCD1D0F13188DD /* StringTests.swift */, @@ -5740,6 +5765,14 @@ path = Layout; sourceTree = ""; }; + CF870CEE51C0AEFABBB525FE /* View */ = { + isa = PBXGroup; + children = ( + 005CA6F5F5088A8868DC6CED /* SpaceListScreen.swift */, + ); + path = View; + sourceTree = ""; + }; D0111119CDF3E28E6D7768E8 /* ShareExtension */ = { isa = PBXGroup; children = ( @@ -5997,6 +6030,7 @@ 2565414373E6F68005966B8E /* SecureBackup */, C59BA103987B953BA374509F /* SecurityAndPrivacyScreen */, 70B74A432C241E56A7ACE610 /* Settings */, + 12E4648360EEC92520C6BC7E /* SpaceListScreen */, EC4545C7E37E8294D3FE6800 /* StartChatScreen */, 9688AEF931DE2C71683ACBC7 /* ThreadTimelineScreen */, 15D44FCA9475E660B7F56DB9 /* Timeline */, @@ -7120,6 +7154,7 @@ 755727E0B756430DFFEC4732 /* SessionVerificationViewModelTests.swift in Sources */, 494970EA811FE4D93AC68482 /* SettingsScreenViewModelTests.swift in Sources */, C797C0B4CF45C66CD1921252 /* SoftLogoutScreenViewModelTests.swift in Sources */, + 920DC020F18ABC88175114D3 /* SpaceListScreenViewModelTests.swift in Sources */, 6189B4ABD535CE526FA1107B /* StartChatViewModelTests.swift in Sources */, 9BB91CABB10D8FE90C491BCD /* StaticLocationScreenViewModelTests.swift in Sources */, 1FEC0A4EC6E6DF693C16B32A /* StringTests.swift in Sources */, @@ -7960,6 +7995,11 @@ F86102DC2C68BBBB0521BAAE /* SoftLogoutScreenModels.swift in Sources */, F37629BAA5E8F50AAF2A131D /* SoftLogoutScreenViewModel.swift in Sources */, CF4044A8EED5C41BC0ED6ABE /* SoftLogoutScreenViewModelProtocol.swift in Sources */, + 789C3667354AE2DD9646ECB8 /* SpaceListScreen.swift in Sources */, + 8182E5914C729ABA646B1B5F /* SpaceListScreenCoordinator.swift in Sources */, + F50C34684647D8357A460BC8 /* SpaceListScreenModels.swift in Sources */, + 188B0E4EA6CE5711A0566087 /* SpaceListScreenViewModel.swift in Sources */, + F8D8E142445CB4628F7DC533 /* SpaceListScreenViewModelProtocol.swift in Sources */, DF004A5B2EABBD0574D06A04 /* SplashScreenCoordinator.swift in Sources */, E1C67E5D9E22135A8FEBBD60 /* StackedAvatarsView.swift in Sources */, 3DAF325D8AE461F7CDB282BD /* StartChatScreen.swift in Sources */, diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index abee49a97..aa4d00b31 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -74,6 +74,11 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { isNewLogin: isNewLogin) chatsTabDetails = .init(tag: HomeTab.chats, title: L10n.screenHomeTabChats, icon: \.chat, selectedIcon: \.chatSolid) chatsTabDetails.barVisibility = .hidden + + // This is just temporary, it needs a flow coordinator to properly handle (amongst other things) navigation/split views. + let spaceListScreenCoordinator = SpaceListScreenCoordinator(parameters: .init(userSession: userSession)) + let spacesNavigationCoordinator = NavigationStackCoordinator() + spacesNavigationCoordinator.setRootCoordinator(spaceListScreenCoordinator) spacesTabDetails = .init(tag: HomeTab.spaces, title: L10n.screenHomeTabSpaces, icon: \.space, selectedIcon: \.spaceSolid) onboardingStackCoordinator = NavigationStackCoordinator() @@ -89,7 +94,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { navigationTabCoordinator.setTabs([ .init(coordinator: chatsSplitCoordinator, details: chatsTabDetails), - .init(coordinator: BlankFormCoordinator(), details: spacesTabDetails) + .init(coordinator: spacesNavigationCoordinator, details: spacesTabDetails) ]) setupObservers() diff --git a/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift b/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift index 4e58b869e..5d2c40cb4 100644 --- a/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift +++ b/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift @@ -153,6 +153,7 @@ enum TestablePreviewsDictionary { "SettingsScreen_Previews" : SettingsScreen_Previews.self, "ShimmerOverlay_Previews" : ShimmerOverlay_Previews.self, "SoftLogoutScreen_Previews" : SoftLogoutScreen_Previews.self, + "SpaceListScreen_Previews" : SpaceListScreen_Previews.self, "SplashScreen_Previews" : SplashScreen_Previews.self, "StackedAvatarsView_Previews" : StackedAvatarsView_Previews.self, "StartChatScreen_Previews" : StartChatScreen_Previews.self, diff --git a/ElementX/Sources/Screens/SpaceListScreen/SpaceListScreenCoordinator.swift b/ElementX/Sources/Screens/SpaceListScreen/SpaceListScreenCoordinator.swift new file mode 100644 index 000000000..2ffa6b908 --- /dev/null +++ b/ElementX/Sources/Screens/SpaceListScreen/SpaceListScreenCoordinator.swift @@ -0,0 +1,54 @@ +// +// 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. +// + +// periphery:ignore:all - this is just a spaceList remove this comment once generating the final file + +import Combine +import SwiftUI + +struct SpaceListScreenCoordinatorParameters { + let userSession: UserSessionProtocol +} + +enum SpaceListScreenCoordinatorAction { + case showSettings +} + +final class SpaceListScreenCoordinator: CoordinatorProtocol { + private let parameters: SpaceListScreenCoordinatorParameters + private let viewModel: SpaceListScreenViewModelProtocol + + private var cancellables = Set() + + private let actionsSubject: PassthroughSubject = .init() + var actionsPublisher: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init(parameters: SpaceListScreenCoordinatorParameters) { + self.parameters = parameters + + viewModel = SpaceListScreenViewModel(userSession: parameters.userSession) + } + + func start() { + viewModel.actionsPublisher.sink { [weak self] action in + MXLog.info("Coordinator: received view model action: \(action)") + + guard let self else { return } + switch action { + case .showSettings: + actionsSubject.send(.showSettings) + } + } + .store(in: &cancellables) + } + + func toPresentable() -> AnyView { + AnyView(SpaceListScreen(context: viewModel.context)) + } +} diff --git a/ElementX/Sources/Screens/SpaceListScreen/SpaceListScreenModels.swift b/ElementX/Sources/Screens/SpaceListScreen/SpaceListScreenModels.swift new file mode 100644 index 000000000..b8c766860 --- /dev/null +++ b/ElementX/Sources/Screens/SpaceListScreen/SpaceListScreenModels.swift @@ -0,0 +1,33 @@ +// +// 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 Foundation + +enum SpaceListScreenViewModelAction { + case showSettings +} + +struct SpaceListScreenViewState: BindableState { + let userID: String + var userDisplayName: String? + var userAvatarURL: URL? + + var rooms: [HomeScreenRoom] + var joinedRoomsCount: Int + + var bindings: SpaceListScreenViewStateBindings + + var subtitle: String { + L10n.screenSpaceListDetails(L10n.commonSpaces(rooms.count), L10n.commonRooms(joinedRoomsCount)) + } +} + +struct SpaceListScreenViewStateBindings { } + +enum SpaceListScreenViewAction { + case showSettings +} diff --git a/ElementX/Sources/Screens/SpaceListScreen/SpaceListScreenViewModel.swift b/ElementX/Sources/Screens/SpaceListScreen/SpaceListScreenViewModel.swift new file mode 100644 index 000000000..dc423da05 --- /dev/null +++ b/ElementX/Sources/Screens/SpaceListScreen/SpaceListScreenViewModel.swift @@ -0,0 +1,47 @@ +// +// 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 SwiftUI + +typealias SpaceListScreenViewModelType = StateStoreViewModelV2 + +class SpaceListScreenViewModel: SpaceListScreenViewModelType, SpaceListScreenViewModelProtocol { + private let actionsSubject: PassthroughSubject = .init() + var actionsPublisher: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init(userSession: UserSessionProtocol) { + super.init(initialViewState: SpaceListScreenViewState(userID: userSession.clientProxy.userID, + rooms: [], + joinedRoomsCount: 0, + bindings: .init()), + mediaProvider: userSession.mediaProvider) + + userSession.clientProxy.userAvatarURLPublisher + .receive(on: DispatchQueue.main) + .weakAssign(to: \.state.userAvatarURL, on: self) + .store(in: &cancellables) + + userSession.clientProxy.userDisplayNamePublisher + .receive(on: DispatchQueue.main) + .weakAssign(to: \.state.userDisplayName, on: self) + .store(in: &cancellables) + } + + // MARK: - Public + + override func process(viewAction: SpaceListScreenViewAction) { + MXLog.info("View model: received view action: \(viewAction)") + + switch viewAction { + case .showSettings: + actionsSubject.send(.showSettings) + } + } +} diff --git a/ElementX/Sources/Screens/SpaceListScreen/SpaceListScreenViewModelProtocol.swift b/ElementX/Sources/Screens/SpaceListScreen/SpaceListScreenViewModelProtocol.swift new file mode 100644 index 000000000..25628adcb --- /dev/null +++ b/ElementX/Sources/Screens/SpaceListScreen/SpaceListScreenViewModelProtocol.swift @@ -0,0 +1,14 @@ +// +// 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 + +@MainActor +protocol SpaceListScreenViewModelProtocol { + var actionsPublisher: AnyPublisher { get } + var context: SpaceListScreenViewModelType.Context { get } +} diff --git a/ElementX/Sources/Screens/SpaceListScreen/View/SpaceListScreen.swift b/ElementX/Sources/Screens/SpaceListScreen/View/SpaceListScreen.swift new file mode 100644 index 000000000..da3a5481f --- /dev/null +++ b/ElementX/Sources/Screens/SpaceListScreen/View/SpaceListScreen.swift @@ -0,0 +1,101 @@ +// +// 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 Compound +import SwiftUI + +struct SpaceListScreen: View { + @Bindable var context: SpaceListScreenViewModel.Context + + var body: some View { + ScrollView { + LazyVStack(spacing: 0) { + header + } + } + .navigationTitle(L10n.screenSpaceListTitle) + .navigationBarTitleDisplayMode(.inline) + .toolbar { toolbar } + .background(Color.compound.bgCanvasDefault.ignoresSafeArea()) + .bloom() + } + + var header: some View { + VStack(spacing: 16) { + BigIcon(icon: \.spaceSolid) + + VStack(spacing: 8) { + Text(L10n.screenSpaceListTitle) + .font(.compound.headingLGBold) + .foregroundStyle(.compound.textPrimary) + .multilineTextAlignment(.center) + + Text(context.viewState.subtitle) + .font(.compound.bodyLG) + .foregroundStyle(.compound.textSecondary) + .multilineTextAlignment(.center) + } + + Text(L10n.screenSpaceListDescription) + .font(.compound.bodyMD) + .foregroundStyle(.compound.textPrimary) + .multilineTextAlignment(.center) + } + .frame(maxWidth: .infinity) + .padding(.horizontal, 16) + .padding(.top, 32) + .padding(.bottom, 24) + .overlay(alignment: .bottom) { + Rectangle() + .fill(Color.compound.borderDisabled) + .frame(height: 1 / UIScreen.main.scale) + } + } + + @ToolbarContentBuilder + var toolbar: some ToolbarContent { + ToolbarItem(placement: .navigationBarLeading) { + Button { + context.send(viewAction: .showSettings) + } label: { + LoadableAvatarImage(url: context.viewState.userAvatarURL, + name: context.viewState.userDisplayName, + contentID: context.viewState.userID, + avatarSize: .user(on: .home), + mediaProvider: context.mediaProvider) + .accessibilityIdentifier(A11yIdentifiers.homeScreen.userAvatar) + .compositingGroup() + } + .accessibilityLabel(L10n.commonSettings) + } + + ToolbarItem(placement: .principal) { + // Hides the navigationTitle (which is set for the navigation stack label). + Text("").accessibilityHidden(true) + } + } +} + +// MARK: - Previews + +struct SpaceListScreen_Previews: PreviewProvider, TestablePreview { + static let viewModel = makeViewModel() + + static var previews: some View { + NavigationStack { + SpaceListScreen(context: viewModel.context) + } + } + + static func makeViewModel(counterValue: Int = 0) -> SpaceListScreenViewModel { + let clientProxy = ClientProxyMock(.init()) + let userSession = UserSessionMock(.init(clientProxy: clientProxy)) + let viewModel = SpaceListScreenViewModel(userSession: userSession) + + return viewModel + } +} diff --git a/PreviewTests/Sources/GeneratedPreviewTests.swift b/PreviewTests/Sources/GeneratedPreviewTests.swift index 6915c70c3..14035e40a 100644 --- a/PreviewTests/Sources/GeneratedPreviewTests.swift +++ b/PreviewTests/Sources/GeneratedPreviewTests.swift @@ -875,6 +875,12 @@ extension PreviewTests { } } + func testSpaceListScreen() async throws { + for (index, preview) in SpaceListScreen_Previews._allPreviews.enumerated() { + try await assertSnapshots(matching: preview, step: index) + } + } + func testSplashScreen() async throws { for (index, preview) in SplashScreen_Previews._allPreviews.enumerated() { try await assertSnapshots(matching: preview, step: index) diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/spaceListScreen.iPad-en-GB-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/spaceListScreen.iPad-en-GB-0.png new file mode 100644 index 000000000..27da36f78 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/spaceListScreen.iPad-en-GB-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60e71badaa8586cc67ae28fa888bf072e61bf8c547c708a8ec32461e33c86619 +size 100453 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/spaceListScreen.iPad-pseudo-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/spaceListScreen.iPad-pseudo-0.png new file mode 100644 index 000000000..db0128a35 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/spaceListScreen.iPad-pseudo-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77dfe5e4cc52f77c46891842a27fdd99cdf510c303009d87b4285cd575938309 +size 108725 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/spaceListScreen.iPhone-16-en-GB-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/spaceListScreen.iPhone-16-en-GB-0.png new file mode 100644 index 000000000..82155dc63 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/spaceListScreen.iPhone-16-en-GB-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65416f910dc4c02a093fe7accc67117a32cc12d8a6a1bf26977e4493d8bb0d19 +size 55676 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/spaceListScreen.iPhone-16-pseudo-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/spaceListScreen.iPhone-16-pseudo-0.png new file mode 100644 index 000000000..6d332bfb6 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/spaceListScreen.iPhone-16-pseudo-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6f1b42f4cd90cbedcc744b7c5b9794b7b84b51d0ad4d083bb5837d7a9afc142 +size 70168 diff --git a/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateScreenCoordinator.swift b/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateScreenCoordinator.swift index 14d3c9b63..82c8a8e14 100644 --- a/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateScreenCoordinator.swift +++ b/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateScreenCoordinator.swift @@ -1,5 +1,5 @@ // -// Copyright 2022-2024 New Vector 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. diff --git a/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateScreenModels.swift b/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateScreenModels.swift index 4bb392396..45395a56a 100644 --- a/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateScreenModels.swift +++ b/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateScreenModels.swift @@ -1,5 +1,5 @@ // -// Copyright 2022-2024 New Vector 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. diff --git a/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateScreenViewModel.swift b/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateScreenViewModel.swift index 3e7c8ccb3..24006dd52 100644 --- a/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateScreenViewModel.swift +++ b/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateScreenViewModel.swift @@ -1,5 +1,5 @@ // -// Copyright 2022-2024 New Vector 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. diff --git a/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateScreenViewModelProtocol.swift b/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateScreenViewModelProtocol.swift index 4041406de..7fecb9297 100644 --- a/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateScreenViewModelProtocol.swift +++ b/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateScreenViewModelProtocol.swift @@ -1,5 +1,5 @@ // -// Copyright 2022-2024 New Vector 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. diff --git a/Tools/Scripts/Templates/SimpleScreenExample/ElementX/View/TemplateScreen.swift b/Tools/Scripts/Templates/SimpleScreenExample/ElementX/View/TemplateScreen.swift index f97178a9d..cc2354369 100644 --- a/Tools/Scripts/Templates/SimpleScreenExample/ElementX/View/TemplateScreen.swift +++ b/Tools/Scripts/Templates/SimpleScreenExample/ElementX/View/TemplateScreen.swift @@ -1,5 +1,5 @@ // -// Copyright 2022-2024 New Vector 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. diff --git a/UnitTests/Sources/SpaceListScreenViewModelTests.swift b/UnitTests/Sources/SpaceListScreenViewModelTests.swift new file mode 100644 index 000000000..aec15b14a --- /dev/null +++ b/UnitTests/Sources/SpaceListScreenViewModelTests.swift @@ -0,0 +1,33 @@ +// +// Copyright 2022-2024 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 + +@testable import ElementX + +@MainActor +class SpaceListScreenViewModelTests: XCTestCase { + var viewModel: SpaceListScreenViewModelProtocol! + + var context: SpaceListScreenViewModelType.Context { + viewModel.context + } + + func testInitialState() { + setupViewModel() + XCTAssertTrue(context.viewState.rooms.isEmpty) + XCTAssertEqual(context.viewState.joinedRoomsCount, 0) + } + + // MARK: - Helpers + + private func setupViewModel() { + let clientProxy = ClientProxyMock(.init()) + let userSession = UserSessionMock(.init(clientProxy: clientProxy)) + viewModel = SpaceListScreenViewModel(userSession: userSession) + } +}