diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 555bb361b..55271731d 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 56; objects = { /* Begin PBXAggregateTarget section */ @@ -28,6 +28,7 @@ 020C530986D7B97631877FEF /* TimelineItemMacContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A4AD793D50748F8997E5B15 /* TimelineItemMacContextMenu.swift */; }; 020F7E70167FB2833266F2F0 /* AnalyticsSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39D7F513A36C9C1951DB44C /* AnalyticsSettingsScreen.swift */; }; 024E70451A7CD9E4E034D8A9 /* VoiceMessageRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D529B976F8B2AA654D923422 /* VoiceMessageRoomTimelineItem.swift */; }; + 02A92F8F4538CECDFB4F2607 /* RoomDirectorySearchScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1562EAF6231151A675BED7A9 /* RoomDirectorySearchScreenCoordinator.swift */; }; 02D8DF8EB7537EB4E9019DDB /* EventBasedTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218AB05B4E3889731959C5F1 /* EventBasedTimelineItemProtocol.swift */; }; 02F4FAE40AF63A1941FD3BBA /* NotificationCenterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10B7F8EE25775DE2A305CBB5 /* NotificationCenterProtocol.swift */; }; 037006FB6DF1374F94E4058D /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDCAC6CAAD65A2C24EA9C4B /* Dictionary.swift */; }; @@ -82,6 +83,7 @@ 0EEC614342F823E5BF966C2C /* AppLockTimerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A5B4CD611DE7E94F5BA87B2 /* AppLockTimerTests.swift */; }; 0F6C8033FA60CFD36F7CA205 /* AppLockSetupPINScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A019A12C866D64CF072024B9 /* AppLockSetupPINScreenViewModel.swift */; }; 0F9E38A75337D0146652ACAB /* BackgroundTaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DFCAA239095A116976E32C4 /* BackgroundTaskTests.swift */; }; + 10D60D287025B71F4743A425 /* RoomDirectorySearchProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471BB7276C97AF60B3A5463B /* RoomDirectorySearchProxy.swift */; }; 1146E9EDCF8344F7D6E0D553 /* MockCoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0376C429FAB1687C3D905F3E /* MockCoder.swift */; }; 119AE9A3FC6E0606C1146528 /* NotificationSettingsEditScreenRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97F8963B14EB0AF3940DDBF /* NotificationSettingsEditScreenRoomCell.swift */; }; 11A6B8E3CBDBF0A4107FF4CE /* OnboardingFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3285BD95B564CA2A948E511 /* OnboardingFlowCoordinator.swift */; }; @@ -131,6 +133,7 @@ 1D5DC685CED904386C89B7DA /* NSRegularExpresion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BAC0F6C9644336E9567EE6 /* NSRegularExpresion.swift */; }; 1D623953F970D11F6F38499C /* AppLockService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 851B95BB98649B8E773D6790 /* AppLockService.swift */; }; 1D69E31913DF66426985909B /* EmojiPickerScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11151E78D6BB2B04A8FBD389 /* EmojiPickerScreenViewModelProtocol.swift */; }; + 1DC227816777A2F3A19657E5 /* RoomDirectorySearchScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCF71646898A2F720C5BFDF5 /* RoomDirectorySearchScreenViewModel.swift */; }; 1E59B77A0B2CE83DCC1B203C /* LoginViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A05707BF550D770168A406DB /* LoginViewModelTests.swift */; }; 1EC6D1B58B24369734CD62BA /* PollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317F41A4B5C4F457AF710666 /* PollView.swift */; }; 1F04C63D4FA95948E3F52147 /* FileRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51E3D86A84341C3A0CB8A40 /* FileRoomTimelineView.swift */; }; @@ -169,6 +172,7 @@ 2797C9D9BA642370F1C85D78 /* Untranslated.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = F75DF9500D69A3AAF8339E69 /* Untranslated.stringsdict */; }; 27E9263DA75E266690A37EB1 /* PermalinkBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB31A32C93D94930B253FBF /* PermalinkBuilderTests.swift */; }; 27F015B0D5436633B5B3C8C3 /* SecureBackupRecoveryKeyScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7061BE2C0BF427C38AEDEF5E /* SecureBackupRecoveryKeyScreenViewModel.swift */; }; + 2814E7075BF3A5C0CCBC9F90 /* RoomDirectorySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AF32E4136FD6F159D86C2C /* RoomDirectorySearchView.swift */; }; 281BED345D59A9A6A99E9D98 /* UNNotificationContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */; }; 282A5F3375DDC774AE09B0C3 /* TracingConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1734A445A58ED855B977A0A8 /* TracingConfigurationTests.swift */; }; 2835FD52F3F618D07F799B3D /* Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7310D8DFE01AF45F0689C3AA /* Publisher.swift */; }; @@ -183,6 +187,7 @@ 2ABF11717C64054CEF2819A3 /* RoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F85164F9475FF2867F71AAA /* RoomTimelineController.swift */; }; 2AD59AD5B09498EF8B3B04EC /* InvitesScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E1BBA73B611EDEEA6E20E05 /* InvitesScreenModels.swift */; }; 2B1E080B32167AE9EFC763A2 /* TimelineTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27B8315A340B46F98B9C5AF0 /* TimelineTableViewController.swift */; }; + 2B97BCE72D86645F1485C976 /* RoomDirectorySearchMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 894EE8F5B399A165BA2A6634 /* RoomDirectorySearchMock.swift */; }; 2BA59D0AEFB4B82A2EC2A326 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 50009897F60FAE7D63EF5E5B /* Kingfisher */; }; 2BAA5B222856068158D0B3C6 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = B1E8B697DF78FE7F61FC6CA4 /* MatrixRustSDK */; }; 2BBA132149DEBED6624084A8 /* MessageForwardingScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FAE373A7F20780BA84B59C /* MessageForwardingScreenCoordinator.swift */; }; @@ -303,6 +308,7 @@ 491D62ACD19E6F134B1766AF /* RoomNotificationSettingsUserDefinedScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3203C6566DC17B7AECC1B7FD /* RoomNotificationSettingsUserDefinedScreen.swift */; }; 492274DA6691EE985C2FCCAA /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 67E7A6F388D3BF85767609D9 /* Sentry */; }; 4940B439681767BE9D78CFDB /* AppLockSetupBiometricsScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F5EE5DE3B55D59299DB5BC /* AppLockSetupBiometricsScreenViewModelTests.swift */; }; + 49500BBA1CD65A5AE252D970 /* RoomDirectorySearchScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41BB37D96C3EA18F3CE8675D /* RoomDirectorySearchScreenModels.swift */; }; 49814A48470F347426513B07 /* TimelineReadReceiptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1877038D1AD3D5A029F8AE2C /* TimelineReadReceiptsView.swift */; }; 49F2E7DD8CAACE09CEECE3E6 /* SeparatorRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6390A6DC140CA3D6865A66FF /* SeparatorRoomTimelineView.swift */; }; 4A618590DEB72C4F186BFED4 /* UserSessionFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99FDEEB71173C4C6FA2734C /* UserSessionFlowCoordinator.swift */; }; @@ -369,6 +375,7 @@ 5AE6404C4FD4848ACCFF9EDC /* SecureBackupLogoutConfirmationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1573D28C8A9FB6399D0EEFB /* SecureBackupLogoutConfirmationScreenCoordinator.swift */; }; 5B2D1210B40570D87B11BD3B /* ThreadDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA3F8E905DF50BF22ECC18F /* ThreadDecorator.swift */; }; 5B6E5AD224509E6C0B520D6E /* RoomMemberDetailsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DDF49CEBC0DFC59C308335F /* RoomMemberDetailsScreenViewModelProtocol.swift */; }; + 5B7D24A318AFF75AD611A026 /* RoomDirectorySearchScreenScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE6BFF453838CF6C3982C5A3 /* RoomDirectorySearchScreenScreenViewModelTests.swift */; }; 5BC6C4ADFE7F2A795ECDE130 /* SecureBackupKeyBackupScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D4EEBE8C098BBADD10939 /* SecureBackupKeyBackupScreenCoordinator.swift */; }; 5C02841B2A86327B2C377682 /* NotificationConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C830A64609CBD152F06E0457 /* NotificationConstants.swift */; }; 5C164551F7D26E24F09083D3 /* StaticLocationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C616D90B1E2F033CAA325439 /* StaticLocationScreenViewModelProtocol.swift */; }; @@ -596,12 +603,14 @@ 8ED8AF57A06F5EE9978ED23F /* AuthenticationStartScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FB89DC7F9A4A91020037001 /* AuthenticationStartScreenViewModelTests.swift */; }; 8EF63DDDC1B54F122070B04D /* ReadMarkerRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */; }; 8F2FAA98457750D9D664136F /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = 4278261E147DB2DE5CFB7FC5 /* PostHog */; }; + 904F06C9C1AEF884C2077542 /* RoomDirectorySearchScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E4EF80DFB8FE7C4469B15D /* RoomDirectorySearchScreen.swift */; }; 90733645AE76FB33DAD28C2B /* URLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE40D4A5DD857AC16EED945A /* URLSession.swift */; }; 9095B9E40DB5CF8BA26CE0D8 /* ReactionsSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 153726EDCE1ACBB3D466A916 /* ReactionsSummaryView.swift */; }; 90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */; }; 90EB25D13AE6EEF034BDE9D2 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D52BAA5BADB06E5E8C295D /* Assets.swift */; }; 915B4CDAF220D9AEB4047D45 /* PollInteractionHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 259E5B05BDE6E20C26CF11B4 /* PollInteractionHandlerProtocol.swift */; }; 91ABC91758A6E4A5FAA2E9C4 /* ReadReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */; }; + 91C6AC0E9D2B9C0C76CC6AD4 /* RoomDirectorySearchScreenScreenModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3984C93B8E9B10C92DADF9EE /* RoomDirectorySearchScreenScreenModelProtocol.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 */; }; @@ -894,6 +903,7 @@ D53B80EF02C1062E68659EDD /* ReportContentViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086C19086DD16E9B38E25954 /* ReportContentViewModelTests.swift */; }; D55AF9B5B55FEED04771A461 /* RoomFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A008E57D52B07B78DFAD1BB /* RoomFlowCoordinator.swift */; }; D5681C80D8281560AACE0035 /* Label.swift in Sources */ = {isa = PBXBuildFile; fileRef = 045253F9967A535EE5B16691 /* Label.swift */; }; + D5B1531A72387D432939D4E0 /* RoomDirectorySearchProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0516C69708D5CBDE1A8E77EC /* RoomDirectorySearchProxyProtocol.swift */; }; D5C805F49B2C75DC3793E780 /* EmojiItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A243E04B58DC6E41FDCD82 /* EmojiItem.swift */; }; D5E771132BB36240DE38102F /* RoomMessageEventStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E815FF3CC5E5A355E3A25E /* RoomMessageEventStringBuilder.swift */; }; D5EA4C6C80579279770D5804 /* ImageRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A45283CF1DB96E583BECA6 /* ImageRoomTimelineView.swift */; }; @@ -915,6 +925,7 @@ DC77E9DB2CFBE84A2BDF20C5 /* RoomRolesAndPermissionsFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0833F51229E166BCA141D004 /* RoomRolesAndPermissionsFlowCoordinator.swift */; }; DCFE7CB3B9A104330BBB96AD /* AnalyticsPromptScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B67DF223EEB8DCAF178A1D4 /* AnalyticsPromptScreenCoordinator.swift */; }; DDB47D29C6865669288BF87C /* UIFont+AttributedStringBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = E8CA187FE656EE5A3F6C7DE5 /* UIFont+AttributedStringBuilder.m */; }; + DDE7B4771452300C103B1EB8 /* RoomDirectoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F6210134203BE1F2DD5C679 /* RoomDirectoryCell.swift */; }; DDFBDEE1DC32BDD5488F898C /* ClientProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F96CCBEAAA7F2185BFA354 /* ClientProxyMock.swift */; }; DE4F8C4E0F1DB4832F09DE97 /* HomeScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D6764D6976D235926FE5FC /* HomeScreenViewModel.swift */; }; DF004A5B2EABBD0574D06A04 /* SplashScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 854BCEAF2A832176FAACD2CB /* SplashScreenCoordinator.swift */; }; @@ -1130,14 +1141,15 @@ 033DB41C51865A2E83174E87 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = ""; }; 035177BCD8E8308B098AC3C2 /* WindowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowManager.swift; sourceTree = ""; }; 0376C429FAB1687C3D905F3E /* MockCoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCoder.swift; sourceTree = ""; }; - 0392E3FDE372C9B56FEEED8B /* test_voice_message.m4a */ = {isa = PBXFileReference; path = test_voice_message.m4a; sourceTree = ""; }; + 0392E3FDE372C9B56FEEED8B /* test_voice_message.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_voice_message.m4a; 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 = ""; }; 044E501B8331B339874D1B96 /* CompoundIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompoundIcon.swift; sourceTree = ""; }; 045253F9967A535EE5B16691 /* Label.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Label.swift; sourceTree = ""; }; - 048A21188AB19349D026BECD /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + 048A21188AB19349D026BECD /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 04BB8DDE245ED86C489BA983 /* AccessibilityIdentifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityIdentifiers.swift; sourceTree = ""; }; 04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManager.swift; sourceTree = ""; }; + 0516C69708D5CBDE1A8E77EC /* RoomDirectorySearchProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchProxyProtocol.swift; sourceTree = ""; }; 052B2F924572AFD70B5F500E /* StartChatScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreenViewModel.swift; sourceTree = ""; }; 054F469E433864CC6FE6EE8E /* ServerSelectionUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionUITests.swift; sourceTree = ""; }; 05512FB13987D221B7205DE0 /* HomeScreenRecoveryKeyConfirmationBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRecoveryKeyConfirmationBanner.swift; sourceTree = ""; }; @@ -1195,13 +1207,14 @@ 127A57D053CE8C87B5EFB089 /* Consumable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Consumable.swift; sourceTree = ""; }; 127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentValuePublisher.swift; sourceTree = ""; }; 12F1E7F9C2BE8BB751037826 /* WaitlistScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenCoordinator.swift; sourceTree = ""; }; - 1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; path = IntegrationTests.xctestplan; sourceTree = ""; }; + 1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = IntegrationTests.xctestplan; sourceTree = ""; }; 130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNTextInputNotificationResponse+Creator.swift"; sourceTree = ""; }; 13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; 1423AB065857FA546444DB15 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = ""; }; 142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryDetails.swift; sourceTree = ""; }; 1454CF3AABD242F55C8A2615 /* InviteUsersScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenModels.swift; sourceTree = ""; }; 153726EDCE1ACBB3D466A916 /* ReactionsSummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsSummaryView.swift; sourceTree = ""; }; + 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 = ""; }; @@ -1283,7 +1296,7 @@ 25F7FE40EF7490A7E09D7BE6 /* NotificationItemProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationItemProxy.swift; sourceTree = ""; }; 25F8664F1FB95AF3C4202478 /* PollFormScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollFormScreenCoordinator.swift; sourceTree = ""; }; 260004737C573A56FA01E86E /* Encodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encodable.swift; sourceTree = ""; }; - 267BB1D5B08A9511F894CB57 /* PreviewTests.xctestplan */ = {isa = PBXFileReference; path = PreviewTests.xctestplan; sourceTree = ""; }; + 267BB1D5B08A9511F894CB57 /* PreviewTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = PreviewTests.xctestplan; sourceTree = ""; }; 26B0A96B8FE4849227945067 /* VoiceMessageRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecorder.swift; sourceTree = ""; }; 26EAAB54C6CE91D64B69A9F8 /* AppLockServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockServiceProtocol.swift; sourceTree = ""; }; 2721D7B051F0159AA919DA05 /* RoomChangePermissionsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangePermissionsScreenViewModelProtocol.swift; sourceTree = ""; }; @@ -1340,7 +1353,7 @@ 3558A15CFB934F9229301527 /* RestorationToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestorationToken.swift; sourceTree = ""; }; 35AFCF4C05DEED04E3DB1A16 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; 35FA991289149D31F4286747 /* UserPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPreference.swift; sourceTree = ""; }; - 36DA824791172B9821EACBED /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + 36DA824791172B9821EACBED /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 36FD673E24FBFCFDF398716A /* RoomMemberProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberProxyMock.swift; sourceTree = ""; }; 376D941BF8BB294389C0DE24 /* MapTilerURLBuildersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerURLBuildersTests.swift; sourceTree = ""; }; 37A243E04B58DC6E41FDCD82 /* EmojiItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiItem.swift; sourceTree = ""; }; @@ -1352,6 +1365,7 @@ 38354164AF59C5006CD05878 /* GlobalSearchScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalSearchScreenViewModel.swift; sourceTree = ""; }; 38E521D6C2BF8DF0DFB35146 /* DeveloperOptionsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreen.swift; sourceTree = ""; }; 3948D16F021DFDB2CD26EAA8 /* MockBackgroundTaskService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBackgroundTaskService.swift; sourceTree = ""; }; + 3984C93B8E9B10C92DADF9EE /* RoomDirectorySearchScreenScreenModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchScreenScreenModelProtocol.swift; sourceTree = ""; }; 398817652FA8ABAE0A31AC6D /* ReadableFrameModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadableFrameModifier.swift; sourceTree = ""; }; 39B6C8690AEA1E49FF1BAF95 /* MediaUploadPreviewScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenUITests.swift; sourceTree = ""; }; 39C0D861FC397AC34BCF089E /* KeychainControllerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerMock.swift; sourceTree = ""; }; @@ -1384,6 +1398,7 @@ 4132F882A984ED971338EE9D /* ReportContentScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreenUITests.swift; sourceTree = ""; }; 4151163F666ED94FD959475A /* NotificationName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationName.swift; sourceTree = ""; }; 4176C3E20C772DE8D182863C /* LegalInformationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreen.swift; sourceTree = ""; }; + 41BB37D96C3EA18F3CE8675D /* RoomDirectorySearchScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchScreenModels.swift; sourceTree = ""; }; 41D041A857614A9AE13C7795 /* RoomChangePermissionsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangePermissionsScreenViewModelTests.swift; sourceTree = ""; }; 421E716C521F96D24ECE69B3 /* NoticeRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineItem.swift; sourceTree = ""; }; 421FA93BCC2840E66E4F306F /* NotificationSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreenViewModelProtocol.swift; sourceTree = ""; }; @@ -1410,6 +1425,7 @@ 46D560DDA3B20C82766ACFAD /* NotificationSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreenViewModel.swift; sourceTree = ""; }; 46F52419AEEDA2C006CB7181 /* NotificationSettingsEditScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenUITests.swift; sourceTree = ""; }; 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = ""; }; + 471BB7276C97AF60B3A5463B /* RoomDirectorySearchProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchProxy.swift; sourceTree = ""; }; 471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineView.swift; sourceTree = ""; }; 475D47D0BFE961B02BAC5D49 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = id; path = id.lproj/Localizable.stringsdict; sourceTree = ""; }; 47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenModels.swift; sourceTree = ""; }; @@ -1632,6 +1648,7 @@ 848F69921527D31CAACB93AF /* SecureBackupLogoutConfirmationScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenViewModelTests.swift; sourceTree = ""; }; 84A00BB9CD12CF6AC98D5485 /* SecureBackupScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupScreen.swift; sourceTree = ""; }; 84A87D0471D438A233C2CF4A /* RoomMemberDetailsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsScreenViewModel.swift; sourceTree = ""; }; + 84AF32E4136FD6F159D86C2C /* RoomDirectorySearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchView.swift; sourceTree = ""; }; 84B7A28A6606D58D1E38C55A /* ExpiringTaskRunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpiringTaskRunnerTests.swift; sourceTree = ""; }; 85149F56BA333619900E2410 /* UserDetailsEditScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDetailsEditScreenViewModelProtocol.swift; sourceTree = ""; }; 851B95BB98649B8E773D6790 /* AppLockService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockService.swift; sourceTree = ""; }; @@ -1651,6 +1668,7 @@ 89233612A8632AD7E2803620 /* AudioPlayerStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerStateTests.swift; sourceTree = ""; }; 892E29C98C4E8182C9037F84 /* TimelineStyler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyler.swift; sourceTree = ""; }; 893777A4997BBDB68079D4F5 /* ArrayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayTests.swift; sourceTree = ""; }; + 894EE8F5B399A165BA2A6634 /* RoomDirectorySearchMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchMock.swift; sourceTree = ""; }; 8977176AB534AA41630395BC /* LegalInformationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenViewModelProtocol.swift; sourceTree = ""; }; 897DF5E9A70CE05A632FC8AF /* UTType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTType.swift; sourceTree = ""; }; 89AAEA70CFF3284920811941 /* RoomChangePermissionsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangePermissionsScreen.swift; sourceTree = ""; }; @@ -1665,12 +1683,13 @@ 8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStateEventStringBuilder.swift; sourceTree = ""; }; 8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; 8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = ""; }; - 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = ""; }; + 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = ""; }; 8E1BBA73B611EDEEA6E20E05 /* InvitesScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenModels.swift; sourceTree = ""; }; 8EC57A32ABC80D774CC663DB /* SettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenUITests.swift; sourceTree = ""; }; 8F21ED7205048668BEB44A38 /* AppActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppActivityView.swift; sourceTree = ""; }; 8F421E51DF00377DE1A01354 /* CompletionSuggestionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionSuggestionView.swift; sourceTree = ""; }; 8F61A0DD8243B395499C99A2 /* InvitesScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenUITests.swift; sourceTree = ""; }; + 8F6210134203BE1F2DD5C679 /* RoomDirectoryCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectoryCell.swift; sourceTree = ""; }; 8F841F219ACDFC1D3F42FEFB /* RoomChangeRolesScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreenViewModelTests.swift; sourceTree = ""; }; 8FB89DC7F9A4A91020037001 /* AuthenticationStartScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationStartScreenViewModelTests.swift; sourceTree = ""; }; 8FC803282F9268D49F4ABF14 /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = ""; }; @@ -1819,7 +1838,7 @@ B50F03079F6B5EF9CA005F14 /* TimelineProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineProxyProtocol.swift; sourceTree = ""; }; B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = ""; }; B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = ""; }; - B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = ""; }; + B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = ConfettiScene.scn; sourceTree = ""; }; B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineView.swift; sourceTree = ""; }; B63B69F9A2BC74DD40DC75C8 /* AdvancedSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsScreenViewModel.swift; sourceTree = ""; }; B6404166CBF5CC88673FF9E2 /* RoomDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetails.swift; sourceTree = ""; }; @@ -1925,6 +1944,7 @@ CC743C7A85E3171BCBF0A653 /* AvatarHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarHeaderView.swift; sourceTree = ""; }; CCACD75595C40EACD6AD4A74 /* AuthenticationTextFieldStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationTextFieldStyle.swift; sourceTree = ""; }; CCB6F36CCE44A29A06FCAF1C /* VoiceMessageRecordingComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecordingComposer.swift; sourceTree = ""; }; + CCF71646898A2F720C5BFDF5 /* RoomDirectorySearchScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchScreenViewModel.swift; sourceTree = ""; }; CD469F7513574341181F7EAA /* ServerSelectionScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionScreen.swift; sourceTree = ""; }; CD6613DE16AD26B3A74DA1F5 /* LocationRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRoomTimelineItemContent.swift; sourceTree = ""; }; CD700E035C85738EE4B97129 /* PerformanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerformanceTests.swift; sourceTree = ""; }; @@ -1933,7 +1953,7 @@ CE47A97726F0675DEE387BF9 /* TypingIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingIndicatorView.swift; sourceTree = ""; }; CEE0E6043EFCF6FD2A341861 /* TimelineReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReplyView.swift; 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 = ""; }; + CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = ""; }; CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxy.swift; sourceTree = ""; }; D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoverySection.swift; sourceTree = ""; }; D086854995173E897F993C26 /* AdvancedSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsScreenViewModelProtocol.swift; sourceTree = ""; }; @@ -2051,11 +2071,12 @@ ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = ""; }; ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = ""; }; ED33988DA4FD4FC666800106 /* SessionVerificationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenViewModel.swift; sourceTree = ""; }; - ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = ""; }; + ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = message.caf; sourceTree = ""; }; ED60E4D2CD678E1EBF16F77A /* BlockedUsersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreen.swift; sourceTree = ""; }; ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = ""; }; EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = ""; }; EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = ""; }; + EE6BFF453838CF6C3982C5A3 /* RoomDirectorySearchScreenScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchScreenScreenViewModelTests.swift; sourceTree = ""; }; EEAA2832D93EC7D2608703FB /* NSEUserSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEUserSession.swift; sourceTree = ""; }; EF13BFD415CA84B1272E94F8 /* PINTextFieldTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PINTextFieldTests.swift; sourceTree = ""; }; EF1593DD87F974F8509BB619 /* ElementAnimations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementAnimations.swift; sourceTree = ""; }; @@ -2071,7 +2092,8 @@ F174A5627CDB3CAF280D1880 /* EmojiPickerScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenModels.swift; sourceTree = ""; }; F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProvider.swift; sourceTree = ""; }; F1B8500C152BC59445647DA8 /* UnsupportedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedRoomTimelineItem.swift; sourceTree = ""; }; - F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; path = portrait_test_video.mp4; sourceTree = ""; }; + F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = portrait_test_video.mp4; sourceTree = ""; }; + F2E4EF80DFB8FE7C4469B15D /* RoomDirectorySearchScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchScreen.swift; sourceTree = ""; }; F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixEntityRegexTests.swift; sourceTree = ""; }; F348B5F2C12F9D4F4B4D3884 /* VideoRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineItem.swift; sourceTree = ""; }; F36C0A6D59717193F49EA986 /* UserSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionTests.swift; sourceTree = ""; }; @@ -2301,6 +2323,7 @@ 114DC16B28140F885FD833E2 /* NotificationSettings */, 599DFFE0805B08454E40D64A /* Polls */, 40E6246F03D1FE377BC5D963 /* Room */, + 4FFDC8D1A752384B4C6EB0EB /* RoomDirectorySearch */, BDCEF7C3BF6D09F5611CFC8B /* SecureBackup */, 82D5AD3EAE3A5C1068A44A88 /* Session */, 5329E48968EB951235E83DAE /* SessionVerification */, @@ -2646,6 +2669,7 @@ E2F96CCBEAAA7F2185BFA354 /* ClientProxyMock.swift */, 382B50F7E379B3DBBD174364 /* NotificationSettingsProxyMock.swift */, D38391154120264910D19528 /* PollMock.swift */, + 894EE8F5B399A165BA2A6634 /* RoomDirectorySearchMock.swift */, 36FD673E24FBFCFDF398716A /* RoomMemberProxyMock.swift */, F5D1BAA90F3A073D91B4F16B /* RoomNotificationSettingsProxyMock.swift */, 1ABDE6F66532CBEB0E016F94 /* RoomProxyMock.swift */, @@ -3071,6 +3095,15 @@ path = View; sourceTree = ""; }; + 4BF0F0C4AA1F62828A89099E /* View */ = { + isa = PBXGroup; + children = ( + 8F6210134203BE1F2DD5C679 /* RoomDirectoryCell.swift */, + F2E4EF80DFB8FE7C4469B15D /* RoomDirectorySearchScreen.swift */, + ); + path = View; + sourceTree = ""; + }; 4BF8D11D9ED15CFC373D0119 /* Analytics */ = { isa = PBXGroup; children = ( @@ -3117,11 +3150,21 @@ 05512FB13987D221B7205DE0 /* HomeScreenRecoveryKeyConfirmationBanner.swift */, ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */, C7661EFFCAA307A97D71132A /* HomeScreenRoomList.swift */, + 84AF32E4136FD6F159D86C2C /* RoomDirectorySearchView.swift */, 037A5661B26EC6BE068188D7 /* Filters */, ); path = View; sourceTree = ""; }; + 4FFDC8D1A752384B4C6EB0EB /* RoomDirectorySearch */ = { + isa = PBXGroup; + children = ( + 471BB7276C97AF60B3A5463B /* RoomDirectorySearchProxy.swift */, + 0516C69708D5CBDE1A8E77EC /* RoomDirectorySearchProxyProtocol.swift */, + ); + path = RoomDirectorySearch; + sourceTree = ""; + }; 52AA75722911233E40A3B366 /* Scripts */ = { isa = PBXGroup; children = ( @@ -3499,6 +3542,7 @@ 8F841F219ACDFC1D3F42FEFB /* RoomChangeRolesScreenViewModelTests.swift */, 00E5B2CBEF8F96424F095508 /* RoomDetailsEditScreenViewModelTests.swift */, 2EFE1922F39398ABFB36DF3F /* RoomDetailsViewModelTests.swift */, + EE6BFF453838CF6C3982C5A3 /* RoomDirectorySearchScreenScreenViewModelTests.swift */, 4FCB2126C091EEF2454B4D56 /* RoomFlowCoordinatorTests.swift */, 8AE0C9653870803E4F91F474 /* RoomListFiltersStateTests.swift */, EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */, @@ -4565,6 +4609,18 @@ path = SessionVerificationScreen; sourceTree = ""; }; + C45CF12DD74BF5B6C970C5E1 /* RoomDirectorySearchScreen */ = { + isa = PBXGroup; + children = ( + 1562EAF6231151A675BED7A9 /* RoomDirectorySearchScreenCoordinator.swift */, + 41BB37D96C3EA18F3CE8675D /* RoomDirectorySearchScreenModels.swift */, + 3984C93B8E9B10C92DADF9EE /* RoomDirectorySearchScreenScreenModelProtocol.swift */, + CCF71646898A2F720C5BFDF5 /* RoomDirectorySearchScreenViewModel.swift */, + 4BF0F0C4AA1F62828A89099E /* View */, + ); + path = RoomDirectorySearchScreen; + sourceTree = ""; + }; CA15BB3F6C62B35AE2C281A9 /* Provider */ = { isa = PBXGroup; children = ( @@ -4800,6 +4856,7 @@ D8388454B5909D862CAC78F7 /* RoomChangeRolesScreen */, E71742A824A7192C8D378875 /* RoomDetailsEditScreen */, E703BBD16266053B8A193C7B /* RoomDetailsScreen */, + C45CF12DD74BF5B6C970C5E1 /* RoomDirectorySearchScreen */, B86CF59E083C82C2A842E4AD /* RoomMemberDetailsScreen */, D4DB8163C10389C069458252 /* RoomMemberListScreen */, 0210F4932B59277E2EEEF7BC /* RoomNotificationSettingsScreen */, @@ -5649,6 +5706,7 @@ D2825E013A8ECFB66D9A1DE6 /* RoomChangeRolesScreenViewModelTests.swift in Sources */, 9DD84E014ADFB2DD813022D5 /* RoomDetailsEditScreenViewModelTests.swift in Sources */, EA974337FA7D040E7C74FE6E /* RoomDetailsViewModelTests.swift in Sources */, + 5B7D24A318AFF75AD611A026 /* RoomDirectorySearchScreenScreenViewModelTests.swift in Sources */, 095D3906CF2F940C2D2D17CC /* RoomFlowCoordinatorTests.swift in Sources */, 4C8C0C9FC10BA73AB7780534 /* RoomListFiltersStateTests.swift in Sources */, 6B31508C6334C617360C2EAB /* RoomMemberDetailsViewModelTests.swift in Sources */, @@ -6155,6 +6213,16 @@ DB079D1929B5A5F52D207C83 /* RoomDetailsScreenModels.swift in Sources */, A5D551E5691749066E0E0C44 /* RoomDetailsScreenViewModel.swift in Sources */, E9560744F7B0292E20ECE5F2 /* RoomDetailsScreenViewModelProtocol.swift in Sources */, + DDE7B4771452300C103B1EB8 /* RoomDirectoryCell.swift in Sources */, + 2B97BCE72D86645F1485C976 /* RoomDirectorySearchMock.swift in Sources */, + 10D60D287025B71F4743A425 /* RoomDirectorySearchProxy.swift in Sources */, + D5B1531A72387D432939D4E0 /* RoomDirectorySearchProxyProtocol.swift in Sources */, + 904F06C9C1AEF884C2077542 /* RoomDirectorySearchScreen.swift in Sources */, + 02A92F8F4538CECDFB4F2607 /* RoomDirectorySearchScreenCoordinator.swift in Sources */, + 49500BBA1CD65A5AE252D970 /* RoomDirectorySearchScreenModels.swift in Sources */, + 91C6AC0E9D2B9C0C76CC6AD4 /* RoomDirectorySearchScreenScreenModelProtocol.swift in Sources */, + 1DC227816777A2F3A19657E5 /* RoomDirectorySearchScreenViewModel.swift in Sources */, + 2814E7075BF3A5C0CCBC9F90 /* RoomDirectorySearchView.swift in Sources */, 42F1C8731166633E35A6D7E6 /* RoomEventStringBuilder.swift in Sources */, D55AF9B5B55FEED04771A461 /* RoomFlowCoordinator.swift in Sources */, 04A16B45228F7678A027C079 /* RoomHeaderView.swift in Sources */, @@ -6661,9 +6729,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = "$(MARKETING_VERSION)"; - OTHER_SWIFT_FLAGS = ( - "-DIS_NSE", - ); + OTHER_SWIFT_FLAGS = "-DIS_NSE"; PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.nse"; PRODUCT_DISPLAY_NAME = "$(APP_DISPLAY_NAME)"; PRODUCT_NAME = NSE; @@ -6712,9 +6778,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = "$(MARKETING_VERSION)"; - OTHER_SWIFT_FLAGS = ( - "-DIS_MAIN_APP", - ); + OTHER_SWIFT_FLAGS = "-DIS_MAIN_APP"; PILLS_UT_TYPE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).pills"; PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(APP_NAME)"; @@ -6740,9 +6804,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = "$(MARKETING_VERSION)"; - OTHER_SWIFT_FLAGS = ( - "-DIS_MAIN_APP", - ); + OTHER_SWIFT_FLAGS = "-DIS_MAIN_APP"; PILLS_UT_TYPE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).pills"; PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(APP_NAME)"; @@ -6985,9 +7047,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = "$(MARKETING_VERSION)"; - OTHER_SWIFT_FLAGS = ( - "-DIS_NSE", - ); + OTHER_SWIFT_FLAGS = "-DIS_NSE"; PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.nse"; PRODUCT_DISPLAY_NAME = "$(APP_DISPLAY_NAME)"; PRODUCT_NAME = NSE; @@ -7196,7 +7256,7 @@ repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift"; requirement = { kind = exactVersion; - version = 1.1.50; + version = 1.1.51; }; }; 821C67C9A7F8CC3FD41B28B4 /* XCRemoteSwiftPackageReference "emojibase-bindings" */ = { @@ -7212,7 +7272,7 @@ repositoryURL = "https://github.com/matrix-org/matrix-wysiwyg-composer-swift"; requirement = { kind = exactVersion; - version = 2.33.0; + version = 2.34.0; }; }; 96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */ = { diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 50300f4f3..901732979 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -130,8 +130,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/matrix-org/matrix-rust-components-swift", "state" : { - "revision" : "e6b350d257aeb7395e003c3d0c26ae65c2e2e349", - "version" : "1.1.50" + "revision" : "6f8e4535e11e848a513bd12027cfb5e3faa89f30", + "version" : "1.1.51" } }, { @@ -139,8 +139,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/matrix-org/matrix-wysiwyg-composer-swift", "state" : { - "revision" : "35119f128237dd76a60f34d1508b1341ec652e53", - "version" : "2.33.0" + "revision" : "f28fe8b5ade7496ebb09d5e36708a40de838c84d", + "version" : "2.34.0" } }, { diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.strings b/ElementX/Resources/Localizations/en.lproj/Localizable.strings index b19aa1c28..559ed42ea 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.strings @@ -536,6 +536,8 @@ "screen_room_details_notification_title" = "Notifications"; "screen_room_details_share_room_title" = "Share room"; "screen_room_details_updating_room" = "Updating room…"; +"screen_room_directory_search_loading_error" = "Failed loading"; +"screen_room_directory_search_title" = "Room directory"; "screen_room_encrypted_history_banner" = "Message history is currently unavailable."; "screen_room_encrypted_history_banner_unverified" = "Message history is unavailable in this room. Verify this device to see your message history."; "screen_room_error_failed_retrieving_user_details" = "Could not retrieve user details"; @@ -629,6 +631,7 @@ "screen_roomlist_main_space_title" = "Chats"; "screen_roomlist_mark_as_read" = "Mark as read"; "screen_roomlist_mark_as_unread" = "Mark as unread"; +"screen_roomlist_room_directory_button_title" = "Browse all rooms"; "screen_server_confirmation_change_server" = "Change account provider"; "screen_server_confirmation_message_login_element_dot_io" = "A private server for Element employees."; "screen_server_confirmation_message_login_matrix_dot_org" = "Matrix is an open network for secure, decentralised communication."; diff --git a/ElementX/Sources/Application/AppSettings.swift b/ElementX/Sources/Application/AppSettings.swift index cb63f2fba..a192bc1f5 100644 --- a/ElementX/Sources/Application/AppSettings.swift +++ b/ElementX/Sources/Application/AppSettings.swift @@ -53,6 +53,7 @@ final class AppSettings { case markAsUnreadEnabled case markAsFavouriteEnabled case roomModerationEnabled + case publicSearchEnabled } private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier @@ -283,6 +284,9 @@ final class AppSettings { @UserPreference(key: UserDefaultsKeys.roomModerationEnabled, defaultValue: false, storageType: .userDefaults(store)) var roomModerationEnabled + @UserPreference(key: UserDefaultsKeys.publicSearchEnabled, defaultValue: false, storageType: .volatile) + var publicSearchEnabled + #endif // MARK: - Shared diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index f121e9796..0ba05ce7d 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -310,6 +310,11 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { case (.logoutConfirmationScreen, .dismissedLogoutConfirmationScreen, .roomList): break + case (.roomList, .showRoomDirectorySearchScreen, .roomDirectorySearchScreen): + presentRoomDirectorySearch() + case (.roomDirectorySearchScreen, .dismissedRoomDirectorySearchScreen, .roomList): + dismissRoomDirectorySearch() + default: fatalError("Unknown transition: \(context)") } @@ -370,6 +375,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { stateMachine.processEvent(.showInvitesScreen) case .presentGlobalSearch: presentGlobalSearch() + case .presentRoomDirectorySearch: + stateMachine.processEvent(.showRoomDirectorySearchScreen) } } .store(in: &cancellables) @@ -566,4 +573,26 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { globalSearchScreenCoordinator = nil } + + // MARK: Room Directory Search + + private func presentRoomDirectorySearch() { + let coordinator = RoomDirectorySearchScreenCoordinator(parameters: .init(roomDirectorySearchProxy: userSession.clientProxy.roomDirectorySearchProxy(), + imageProvider: userSession.mediaProvider, + userIndicatorController: ServiceLocator.shared.userIndicatorController)) + + coordinator.actionsPublisher.sink { [weak self] action in + switch action { + case .dismiss: + self?.stateMachine.processEvent(.dismissedRoomDirectorySearchScreen(joinedRoomID: nil)) + } + } + .store(in: &cancellables) + + navigationSplitCoordinator.setFullScreenCoverCoordinator(coordinator) + } + + private func dismissRoomDirectorySearch() { + navigationSplitCoordinator.setFullScreenCoverCoordinator(nil) + } } diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinatorStateMachine.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinatorStateMachine.swift index e0154cbed..9d1c6c9ed 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinatorStateMachine.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinatorStateMachine.swift @@ -40,6 +40,9 @@ class UserSessionFlowCoordinatorStateMachine { // Showing the logout flows case logoutConfirmationScreen(selectedRoomID: String?) + + // Showing Room Directory Search screen + case roomDirectorySearchScreen(selectedRoomID: String?) } struct EventUserInfo { @@ -81,6 +84,9 @@ class UserSessionFlowCoordinatorStateMachine { case showLogoutConfirmationScreen /// Logout has been cancelled case dismissedLogoutConfirmationScreen + + case showRoomDirectorySearchScreen + case dismissedRoomDirectorySearchScreen(joinedRoomID: String?) } private let stateMachine: StateMachine @@ -136,6 +142,14 @@ class UserSessionFlowCoordinatorStateMachine { case (.logoutConfirmationScreen(let selectedRoomID), .dismissedLogoutConfirmationScreen): return .roomList(selectedRoomID: selectedRoomID) + case (.roomList(let selectedRoomID), .showRoomDirectorySearchScreen): + return .roomDirectorySearchScreen(selectedRoomID: selectedRoomID) + case (.roomDirectorySearchScreen(let selectedRoomID), .dismissedRoomDirectorySearchScreen(let joinedRoomID)): + if let joinedRoomID { + return .roomList(selectedRoomID: joinedRoomID) + } + return .roomList(selectedRoomID: selectedRoomID) + default: return nil } diff --git a/ElementX/Sources/Generated/Strings.swift b/ElementX/Sources/Generated/Strings.swift index 0ba214ac1..549c90a4f 100644 --- a/ElementX/Sources/Generated/Strings.swift +++ b/ElementX/Sources/Generated/Strings.swift @@ -1311,6 +1311,10 @@ internal enum L10n { internal static var screenRoomDetailsTopicTitle: String { return L10n.tr("Localizable", "screen_room_details_topic_title") } /// Updating room… internal static var screenRoomDetailsUpdatingRoom: String { return L10n.tr("Localizable", "screen_room_details_updating_room") } + /// Failed loading + internal static var screenRoomDirectorySearchLoadingError: String { return L10n.tr("Localizable", "screen_room_directory_search_loading_error") } + /// Room directory + internal static var screenRoomDirectorySearchTitle: String { return L10n.tr("Localizable", "screen_room_directory_search_title") } /// Message history is currently unavailable. internal static var screenRoomEncryptedHistoryBanner: String { return L10n.tr("Localizable", "screen_room_encrypted_history_banner") } /// Message history is unavailable in this room. Verify this device to see your message history. @@ -1543,6 +1547,8 @@ internal enum L10n { internal static var screenRoomlistMarkAsRead: String { return L10n.tr("Localizable", "screen_roomlist_mark_as_read") } /// Mark as unread internal static var screenRoomlistMarkAsUnread: String { return L10n.tr("Localizable", "screen_roomlist_mark_as_unread") } + /// Browse all rooms + internal static var screenRoomlistRoomDirectoryButtonTitle: String { return L10n.tr("Localizable", "screen_roomlist_room_directory_button_title") } /// Change account provider internal static var screenServerConfirmationChangeServer: String { return L10n.tr("Localizable", "screen_server_confirmation_change_server") } /// A private server for Element employees. diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 6a858fa29..197b726fa 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -1131,6 +1131,23 @@ class ClientProxyMock: ClientProxyProtocol { return profileForReturnValue } } + //MARK: - roomDirectorySearchProxy + + var roomDirectorySearchProxyCallsCount = 0 + var roomDirectorySearchProxyCalled: Bool { + return roomDirectorySearchProxyCallsCount > 0 + } + var roomDirectorySearchProxyReturnValue: RoomDirectorySearchProxyProtocol! + var roomDirectorySearchProxyClosure: (() -> RoomDirectorySearchProxyProtocol)? + + func roomDirectorySearchProxy() -> RoomDirectorySearchProxyProtocol { + roomDirectorySearchProxyCallsCount += 1 + if let roomDirectorySearchProxyClosure = roomDirectorySearchProxyClosure { + return roomDirectorySearchProxyClosure() + } else { + return roomDirectorySearchProxyReturnValue + } + } //MARK: - ignoreUser var ignoreUserCallsCount = 0 @@ -2245,6 +2262,52 @@ class PollInteractionHandlerMock: PollInteractionHandlerProtocol { } } } +class RoomDirectorySearchProxyMock: RoomDirectorySearchProxyProtocol { + var resultsPublisher: CurrentValuePublisher<[RoomDirectorySearchResult], Never> { + get { return underlyingResultsPublisher } + set(value) { underlyingResultsPublisher = value } + } + var underlyingResultsPublisher: CurrentValuePublisher<[RoomDirectorySearchResult], Never>! + + //MARK: - search + + var searchQueryCallsCount = 0 + var searchQueryCalled: Bool { + return searchQueryCallsCount > 0 + } + var searchQueryReceivedQuery: String? + var searchQueryReceivedInvocations: [String?] = [] + var searchQueryReturnValue: Result! + var searchQueryClosure: ((String?) async -> Result)? + + func search(query: String?) async -> Result { + searchQueryCallsCount += 1 + searchQueryReceivedQuery = query + searchQueryReceivedInvocations.append(query) + if let searchQueryClosure = searchQueryClosure { + return await searchQueryClosure(query) + } else { + return searchQueryReturnValue + } + } + //MARK: - nextPage + + var nextPageCallsCount = 0 + var nextPageCalled: Bool { + return nextPageCallsCount > 0 + } + var nextPageReturnValue: Result! + var nextPageClosure: (() async -> Result)? + + func nextPage() async -> Result { + nextPageCallsCount += 1 + if let nextPageClosure = nextPageClosure { + return await nextPageClosure() + } else { + return nextPageReturnValue + } + } +} class RoomMemberProxyMock: RoomMemberProxyProtocol { var userID: String { get { return underlyingUserID } diff --git a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift index b786829fa..93afbd428 100644 --- a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift @@ -505,6 +505,23 @@ class SDKClientMock: SDKClientProtocol { restoreSessionSessionReceivedInvocations.append(session) try restoreSessionSessionClosure?(session) } + //MARK: - roomDirectorySearch + + public var roomDirectorySearchCallsCount = 0 + public var roomDirectorySearchCalled: Bool { + return roomDirectorySearchCallsCount > 0 + } + public var roomDirectorySearchReturnValue: RoomDirectorySearch! + public var roomDirectorySearchClosure: (() -> RoomDirectorySearch)? + + public func roomDirectorySearch() -> RoomDirectorySearch { + roomDirectorySearchCallsCount += 1 + if let roomDirectorySearchClosure = roomDirectorySearchClosure { + return roomDirectorySearchClosure() + } else { + return roomDirectorySearchReturnValue + } + } //MARK: - rooms public var roomsCallsCount = 0 diff --git a/ElementX/Sources/Mocks/RoomDirectorySearchMock.swift b/ElementX/Sources/Mocks/RoomDirectorySearchMock.swift new file mode 100644 index 000000000..0ee20b140 --- /dev/null +++ b/ElementX/Sources/Mocks/RoomDirectorySearchMock.swift @@ -0,0 +1,31 @@ +// +// Copyright 2024 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import Foundation + +struct RoomDirectorySearchProxyMockConfiguration { + let results: [RoomDirectorySearchResult] +} + +extension RoomDirectorySearchProxyMock { + convenience init(configuration: RoomDirectorySearchProxyMockConfiguration) { + self.init() + resultsPublisher = CurrentValueSubject(configuration.results).asCurrentValuePublisher() + searchQueryReturnValue = .success(()) + nextPageReturnValue = .success(()) + } +} diff --git a/ElementX/Sources/Other/AvatarSize.swift b/ElementX/Sources/Other/AvatarSize.swift index 551ede302..4ca16b1f1 100644 --- a/ElementX/Sources/Other/AvatarSize.swift +++ b/ElementX/Sources/Other/AvatarSize.swift @@ -92,6 +92,7 @@ enum RoomAvatarSizeOnScreen { case globalSearch case details case notificationSettings + case roomDirectorySearch var value: CGFloat { switch self { @@ -99,6 +100,8 @@ enum RoomAvatarSizeOnScreen { return 30 case .timeline: return 32 + case .roomDirectorySearch: + return 32 case .messageForwarding: return 36 case .globalSearch: diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift index f5bbb4672..546a12ca1 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift @@ -35,6 +35,7 @@ enum HomeScreenCoordinatorAction { case presentStartChatScreen case presentInvitesScreen case presentGlobalSearch + case presentRoomDirectorySearch case logout } @@ -83,6 +84,8 @@ final class HomeScreenCoordinator: CoordinatorProtocol { actionsSubject.send(.presentInvitesScreen) case .presentGlobalSearch: actionsSubject.send(.presentGlobalSearch) + case .presentRoomDirectorySearch: + actionsSubject.send(.presentRoomDirectorySearch) } } .store(in: &cancellables) diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift index 4ec962c03..8b7a973a5 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift @@ -28,6 +28,7 @@ enum HomeScreenViewModelAction { case presentStartChatScreen case presentInvitesScreen case presentGlobalSearch + case presentRoomDirectorySearch case logout } @@ -46,6 +47,7 @@ enum HomeScreenViewAction { case markRoomAsUnread(roomIdentifier: String) case markRoomAsRead(roomIdentifier: String) case markRoomAsFavourite(roomIdentifier: String, isFavourite: Bool) + case selectRoomDirectorySearch } enum HomeScreenRoomListMode: CustomStringConvertible { @@ -88,6 +90,8 @@ struct HomeScreenViewState: BindableState { var hasPendingInvitations = false var hasUnreadPendingInvitations = false + var isRoomDirectorySearchEnabled = false + var selectedRoomID: String? var visibleRooms: [HomeScreenRoom] { diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index 198cd2d46..27d5097e6 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -94,6 +94,10 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol .sink { [weak self] _ in self?.updateRooms() } .store(in: &cancellables) + appSettings.$publicSearchEnabled + .weakAssign(to: \.state.isRoomDirectorySearchEnabled, on: self) + .store(in: &cancellables) + let isSearchFieldFocused = context.$viewState.map(\.bindings.isSearchFieldFocused) let searchQuery = context.$viewState.map(\.bindings.searchQuery) let activeFilters = context.$viewState.map(\.bindings.filtersState.activeFilters) @@ -177,6 +181,8 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol Task { await markRoomAsFavourite(roomIdentifier, isFavourite: isFavourite) } + case .selectRoomDirectorySearch: + actionsSubject.send(.presentRoomDirectorySearch) } } diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomList.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomList.swift index 732cbadad..91b38583e 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomList.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomList.swift @@ -25,6 +25,10 @@ struct HomeScreenRoomList: View { // avoids glitches when focusing the search bar if !context.viewState.shouldHideRoomList { content + } else if context.viewState.isRoomDirectorySearchEnabled { + RoomDirectorySearchView { + context.send(viewAction: .selectRoomDirectorySearch) + } } } diff --git a/ElementX/Sources/Screens/HomeScreen/View/RoomDirectorySearchView.swift b/ElementX/Sources/Screens/HomeScreen/View/RoomDirectorySearchView.swift new file mode 100644 index 000000000..5862f723e --- /dev/null +++ b/ElementX/Sources/Screens/HomeScreen/View/RoomDirectorySearchView.swift @@ -0,0 +1,37 @@ +// +// Copyright 2024 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Compound +import SwiftUI + +struct RoomDirectorySearchView: View { + let onTap: () -> Void + + var body: some View { + Button(action: onTap) { + Label(L10n.screenRoomlistRoomDirectoryButtonTitle, icon: \.listBulleted) + } + .buttonStyle(.compound(.secondary)) + .padding(.horizontal, 16) + .padding(.vertical, 8) + } +} + +struct RoomDirectorySearchView_Previews: PreviewProvider, TestablePreview { + static var previews: some View { + RoomDirectorySearchView { } + } +} diff --git a/ElementX/Sources/Screens/RoomDirectorySearchScreen/RoomDirectorySearchScreenCoordinator.swift b/ElementX/Sources/Screens/RoomDirectorySearchScreen/RoomDirectorySearchScreenCoordinator.swift new file mode 100644 index 000000000..e6f36fec8 --- /dev/null +++ b/ElementX/Sources/Screens/RoomDirectorySearchScreen/RoomDirectorySearchScreenCoordinator.swift @@ -0,0 +1,58 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import SwiftUI + +struct RoomDirectorySearchScreenCoordinatorParameters { + let roomDirectorySearchProxy: RoomDirectorySearchProxyProtocol + let imageProvider: ImageProviderProtocol + let userIndicatorController: UserIndicatorControllerProtocol +} + +enum RoomDirectorySearchScreenCoordinatorAction { + case dismiss +} + +final class RoomDirectorySearchScreenCoordinator: CoordinatorProtocol { + private let viewModel: RoomDirectorySearchScreenViewModelProtocol + + private var cancellables = Set() + + private let actionsSubject: PassthroughSubject = .init() + var actionsPublisher: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init(parameters: RoomDirectorySearchScreenCoordinatorParameters) { + viewModel = RoomDirectorySearchScreenViewModel(roomDirectorySearch: parameters.roomDirectorySearchProxy, userIndicatorController: parameters.userIndicatorController, imageProvider: parameters.imageProvider) + } + + func start() { + viewModel.actionsPublisher.sink { [weak self] action in + guard let self else { return } + switch action { + case .dismiss: + self.actionsSubject.send(.dismiss) + } + } + .store(in: &cancellables) + } + + func toPresentable() -> AnyView { + AnyView(RoomDirectorySearchScreen(context: viewModel.context)) + } +} diff --git a/ElementX/Sources/Screens/RoomDirectorySearchScreen/RoomDirectorySearchScreenModels.swift b/ElementX/Sources/Screens/RoomDirectorySearchScreen/RoomDirectorySearchScreenModels.swift new file mode 100644 index 000000000..864b332ba --- /dev/null +++ b/ElementX/Sources/Screens/RoomDirectorySearchScreen/RoomDirectorySearchScreenModels.swift @@ -0,0 +1,38 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +enum RoomDirectorySearchScreenViewModelAction { + case dismiss +} + +struct RoomDirectorySearchScreenViewState: BindableState { + var searchResults: [RoomDirectorySearchResult] = [] + var isLoading = false + + var bindings = RoomDirectorySearchScreenViewStateBindings() +} + +struct RoomDirectorySearchScreenViewStateBindings { + var isSearching = false + var searchString = "" +} + +enum RoomDirectorySearchScreenViewAction { + case dismiss + case join(roomID: String) +} diff --git a/ElementX/Sources/Screens/RoomDirectorySearchScreen/RoomDirectorySearchScreenScreenModelProtocol.swift b/ElementX/Sources/Screens/RoomDirectorySearchScreen/RoomDirectorySearchScreenScreenModelProtocol.swift new file mode 100644 index 000000000..6c209568f --- /dev/null +++ b/ElementX/Sources/Screens/RoomDirectorySearchScreen/RoomDirectorySearchScreenScreenModelProtocol.swift @@ -0,0 +1,23 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine + +@MainActor +protocol RoomDirectorySearchScreenViewModelProtocol { + var actionsPublisher: AnyPublisher { get } + var context: RoomDirectorySearchScreenViewModelType.Context { get } +} diff --git a/ElementX/Sources/Screens/RoomDirectorySearchScreen/RoomDirectorySearchScreenViewModel.swift b/ElementX/Sources/Screens/RoomDirectorySearchScreen/RoomDirectorySearchScreenViewModel.swift new file mode 100644 index 000000000..2889cc249 --- /dev/null +++ b/ElementX/Sources/Screens/RoomDirectorySearchScreen/RoomDirectorySearchScreenViewModel.swift @@ -0,0 +1,91 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import SwiftUI + +typealias RoomDirectorySearchScreenViewModelType = StateStoreViewModel + +class RoomDirectorySearchScreenViewModel: RoomDirectorySearchScreenViewModelType, RoomDirectorySearchScreenViewModelProtocol { + private let roomDirectorySearch: RoomDirectorySearchProxyProtocol + private let userIndicatorController: UserIndicatorControllerProtocol + + private let actionsSubject: PassthroughSubject = .init() + var actionsPublisher: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init(roomDirectorySearch: RoomDirectorySearchProxyProtocol, + userIndicatorController: UserIndicatorControllerProtocol, + imageProvider: ImageProviderProtocol) { + self.roomDirectorySearch = roomDirectorySearch + self.userIndicatorController = userIndicatorController + super.init(initialViewState: RoomDirectorySearchScreenViewState(), imageProvider: imageProvider) + + roomDirectorySearch.resultsPublisher + .receive(on: DispatchQueue.main) + .weakAssign(to: \.state.searchResults, on: self) + .store(in: &cancellables) + + context.$viewState.map(\.bindings.searchString) + // only listen to the search string when is not loading + .combineLatest(context.$viewState.map(\.isLoading) + .filter { $0 == false }) + .map(\.0) + .debounceTextQueriesAndRemoveDuplicates() + .sink { [weak self] query in + self?.search(query: query) + } + .store(in: &cancellables) + } + + // MARK: - Public + + override func process(viewAction: RoomDirectorySearchScreenViewAction) { + MXLog.info("View model: received view action: \(viewAction)") + + switch viewAction { + case .dismiss: + actionsSubject.send(.dismiss) + case .join(roomID: let roomID): + joinRoom(roomID: roomID) + } + } + + private func joinRoom(roomID: String) { } + + private static let errorID = "roomDirectorySearchViewModelLoadingError" + + private func search(query: String?) { + guard !state.isLoading else { + return + } + + state.isLoading = true + Task { + switch await roomDirectorySearch.search(query: query) { + case .success: + break + case .failure: + userIndicatorController.submitIndicator(UserIndicator(id: Self.errorID, + type: .toast, + title: L10n.screenRoomDirectorySearchLoadingError, + iconName: "xmark")) + } + state.isLoading = false + } + } +} diff --git a/ElementX/Sources/Screens/RoomDirectorySearchScreen/View/RoomDirectoryCell.swift b/ElementX/Sources/Screens/RoomDirectorySearchScreen/View/RoomDirectoryCell.swift new file mode 100644 index 000000000..0b2e5066c --- /dev/null +++ b/ElementX/Sources/Screens/RoomDirectorySearchScreen/View/RoomDirectoryCell.swift @@ -0,0 +1,69 @@ +// +// Copyright 2024 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Compound +import SwiftUI + +struct RoomDirectorySearchCell: View { + let result: RoomDirectorySearchResult + let imageProvider: ImageProviderProtocol? + + private var description: String? { + if let topic = result.topic { + return topic + } + + if result.name != nil, let alias = result.alias { + return alias + } else if result.name == nil, result.alias == nil { + return nil + } + + return result.id + } + + var body: some View { + ListRow(label: .avatar(title: result.name ?? result.alias ?? result.id, + description: description, + icon: avatar), kind: .label) + } + + private var avatar: some View { + LoadableAvatarImage(url: result.avatarURL, + name: result.name, + contentID: result.id, + avatarSize: .room(on: .roomDirectorySearch), + imageProvider: imageProvider) + .accessibilityHidden(true) + } +} + +// MARK: - Previews + +struct RoomDirectorySearchCell_Previews: PreviewProvider, TestablePreview { + static var previews: some View { + List { + RoomDirectorySearchCell(result: .init(id: "!test_id_1:matrix.org", alias: "#test:example.com", name: "Test title", topic: "test description", avatarURL: nil, canBeJoined: false), imageProvider: MockMediaProvider()) + RoomDirectorySearchCell(result: .init(id: "!test_id_2:matrix.org", alias: "#test:example.com", name: nil, topic: "test description", avatarURL: nil, canBeJoined: false), imageProvider: MockMediaProvider()) + RoomDirectorySearchCell(result: .init(id: "!test_id_3:example.com", alias: "#test_no_topic:example.com", name: "Test title no topic", topic: nil, avatarURL: nil, canBeJoined: false), imageProvider: MockMediaProvider()) + RoomDirectorySearchCell(result: .init(id: "!test_id_4:example.com", alias: "#test_no_topic:example.com", name: nil, topic: nil, avatarURL: nil, canBeJoined: false), imageProvider: MockMediaProvider()) + RoomDirectorySearchCell(result: .init(id: "!test_id_5:example.com", alias: nil, name: "Test title no alias", topic: nil, avatarURL: nil, canBeJoined: false), imageProvider: MockMediaProvider()) + RoomDirectorySearchCell(result: .init(id: "!test_id_6:example.com", alias: nil, name: "Test title no alias", topic: "Topic", avatarURL: nil, canBeJoined: false), imageProvider: MockMediaProvider()) + RoomDirectorySearchCell(result: .init(id: "!test_id_7:example.com", alias: nil, name: nil, topic: "Topic", avatarURL: nil, canBeJoined: false), imageProvider: MockMediaProvider()) + RoomDirectorySearchCell(result: .init(id: "!test_id_8:example.com", alias: nil, name: nil, topic: nil, avatarURL: nil, canBeJoined: false), imageProvider: MockMediaProvider()) + } + } +} diff --git a/ElementX/Sources/Screens/RoomDirectorySearchScreen/View/RoomDirectorySearchScreen.swift b/ElementX/Sources/Screens/RoomDirectorySearchScreen/View/RoomDirectorySearchScreen.swift new file mode 100644 index 000000000..aa28919b4 --- /dev/null +++ b/ElementX/Sources/Screens/RoomDirectorySearchScreen/View/RoomDirectorySearchScreen.swift @@ -0,0 +1,73 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Compound +import SwiftUI + +struct RoomDirectorySearchScreen: View { + @ObservedObject var context: RoomDirectorySearchScreenViewModel.Context + + var body: some View { + NavigationStack { + List { + Section { + ForEach(context.viewState.searchResults) { + RoomDirectorySearchCell(result: $0, imageProvider: context.imageProvider) + } + } + } + .listStyle(.plain) + .environment(\.defaultMinListRowHeight, 48) + .scrollContentBackground(.hidden) + .background(Color.compound.bgCanvasDefault.ignoresSafeArea()) + .isSearching($context.isSearching) + .searchable(text: $context.searchString, placement: .navigationBarDrawer(displayMode: .always)) + .navigationTitle(L10n.screenRoomDirectorySearchTitle) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button(L10n.actionCancel) { + context.send(viewAction: .dismiss) + } + } + } + } + } +} + +// MARK: - Previews + +struct RoomDirectorySearchScreenScreen_Previews: PreviewProvider, TestablePreview { + static let viewModel = RoomDirectorySearchScreenViewModel(roomDirectorySearch: RoomDirectorySearchProxyMock(configuration: .init(results: [.init(id: "test_1", + alias: "#test_1:example.com", + name: "Test 1", + topic: "Test description 1", + avatarURL: nil, + canBeJoined: true), + .init(id: "test_2", + alias: "#test_2:example.com", + name: "Test 2", + topic: "Test description 2", + avatarURL: URL.documentsDirectory, + canBeJoined: false)])), + userIndicatorController: UserIndicatorControllerMock(), + imageProvider: MockMediaProvider()) + + static var previews: some View { + RoomDirectorySearchScreen(context: viewModel.context) + .snapshot(delay: 1.0) + } +} diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift index 413be2871..fee83e126 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift @@ -50,6 +50,7 @@ protocol DeveloperOptionsProtocol: AnyObject { var hideUnreadMessagesBadge: Bool { get set } var roomModerationEnabled: Bool { get set } var elementCallBaseURL: URL { get set } + var publicSearchEnabled: Bool { get set } } extension AppSettings: DeveloperOptionsProtocol { } diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift index cd321754a..3a65533de 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift @@ -47,6 +47,12 @@ struct DeveloperOptionsScreen: View { } } + Section("Room Directory Search") { + Toggle(isOn: $context.publicSearchEnabled) { + Text("Public rooms search") + } + } + Section("Element Call") { TextField(context.elementCallBaseURL.absoluteString, text: $elementCallBaseURLString) .submitLabel(.done) diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index 2bf42670c..328d0c52d 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -541,6 +541,10 @@ class ClientProxy: ClientProxyProtocol { } } + func roomDirectorySearchProxy() -> RoomDirectorySearchProxyProtocol { + RoomDirectorySearchProxy(roomDirectorySearch: client.roomDirectorySearch()) + } + // MARK: - Ignored users func ignoreUser(_ userID: String) async -> Result { diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index 00c2d6bf4..5d83de07f 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -150,6 +150,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol { func profile(for userID: String) async -> Result + func roomDirectorySearchProxy() -> RoomDirectorySearchProxyProtocol + // MARK: - Ignored users func ignoreUser(_ userID: String) async -> Result diff --git a/ElementX/Sources/Services/RoomDirectorySearch/RoomDirectorySearchProxy.swift b/ElementX/Sources/Services/RoomDirectorySearch/RoomDirectorySearchProxy.swift new file mode 100644 index 000000000..8193afb4d --- /dev/null +++ b/ElementX/Sources/Services/RoomDirectorySearch/RoomDirectorySearchProxy.swift @@ -0,0 +1,172 @@ +// +// Copyright 2024 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import Foundation + +import MatrixRustSDK + +final class RoomDirectorySearchProxy: RoomDirectorySearchProxyProtocol { + private let roomDirectorySearch: RoomDirectorySearchProtocol + private let serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomdirectorysearch", qos: .default) + + private let resultsSubject = CurrentValueSubject<[RoomDirectorySearchResult], Never>([]) + + var resultsPublisher: CurrentValuePublisher<[RoomDirectorySearchResult], Never> { + resultsSubject.asCurrentValuePublisher() + } + + private var results: [RoomDirectorySearchResult] { + get { resultsSubject.value } + set { resultsSubject.send(newValue) } + } + + private let diffsPublisher = PassthroughSubject<[RoomDirectorySearchEntryUpdate], Never>() + + private var searchEntriesSubscription: TaskHandle? + + private var cancellables = Set() + + init(roomDirectorySearch: RoomDirectorySearchProtocol) { + self.roomDirectorySearch = roomDirectorySearch + diffsPublisher + .receive(on: serialDispatchQueue) + .sink { [weak self] in self?.updateResultsWithDiffs($0) } + .store(in: &cancellables) + + Task { + searchEntriesSubscription = await roomDirectorySearch.results(listener: RoomDirectorySearchEntriesListenerProxy { [weak self] updates in + self?.diffsPublisher.send(updates) + }) + } + } + + func search(query: String?) async -> Result { + do { + try await roomDirectorySearch.search(filter: query, batchSize: 50) + return .success(()) + } catch { + return .failure(.searchFailed) + } + } + + func nextPage() async -> Result { + do { + try await roomDirectorySearch.nextPage() + return .success(()) + } catch { + return .failure(.nextPageQueryFailed) + } + } + + private func updateResultsWithDiffs(_ updates: [RoomDirectorySearchEntryUpdate]) { + results = updates.reduce(results) { currentItems, diff in + processDiff(diff, on: currentItems) + } + } + + private func processDiff(_ diff: RoomDirectorySearchEntryUpdate, on currentItems: [RoomDirectorySearchResult]) -> [RoomDirectorySearchResult] { + guard let collectionDiff = buildDiff(from: diff, on: currentItems) else { + return currentItems + } + + guard let updatedItems = currentItems.applying(collectionDiff) else { + return currentItems + } + + return updatedItems + } + + private func buildDiff(from diff: RoomDirectorySearchEntryUpdate, on currentItems: [RoomDirectorySearchResult]) -> CollectionDifference? { + var changes = [CollectionDifference.Change]() + + switch diff { + case .append(let values): + for (index, value) in values.enumerated() { + let result = buildResultForRoomDescription(value) + changes.append(.insert(offset: results.count + index, element: result, associatedWith: nil)) + } + case .clear: + for (index, value) in results.enumerated() { + changes.append(.remove(offset: index, element: value, associatedWith: nil)) + } + case .insert(let index, let value): + let result = buildResultForRoomDescription(value) + changes.append(.insert(offset: Int(index), element: result, associatedWith: nil)) + case .popBack: + guard let value = results.last else { + fatalError() + } + + changes.append(.remove(offset: results.count - 1, element: value, associatedWith: nil)) + case .popFront: + let result = results[0] + changes.append(.remove(offset: 0, element: result, associatedWith: nil)) + case .pushBack(let value): + let result = buildResultForRoomDescription(value) + changes.append(.insert(offset: results.count, element: result, associatedWith: nil)) + case .pushFront(let value): + let result = buildResultForRoomDescription(value) + changes.append(.insert(offset: 0, element: result, associatedWith: nil)) + case .remove(let index): + let result = results[Int(index)] + changes.append(.remove(offset: Int(index), element: result, associatedWith: nil)) + case .reset(let values): + for (index, result) in results.enumerated() { + changes.append(.remove(offset: index, element: result, associatedWith: nil)) + } + + for (index, value) in values.enumerated() { + changes.append(.insert(offset: index, element: buildResultForRoomDescription(value), associatedWith: nil)) + } + case .set(let index, let value): + let result = buildResultForRoomDescription(value) + changes.append(.remove(offset: Int(index), element: result, associatedWith: nil)) + changes.append(.insert(offset: Int(index), element: result, associatedWith: nil)) + case .truncate(let length): + for (index, value) in results.enumerated() { + if index < length { + continue + } + + changes.append(.remove(offset: index, element: value, associatedWith: nil)) + } + } + + return CollectionDifference(changes) + } + + private func buildResultForRoomDescription(_ value: RoomDescription) -> RoomDirectorySearchResult { + RoomDirectorySearchResult(id: value.roomId, + alias: value.alias, + name: value.name, + topic: value.topic, + avatarURL: value.avatarUrl.flatMap(URL.init(string:)), + canBeJoined: value.joinRule == .public) + } +} + +private final class RoomDirectorySearchEntriesListenerProxy: RoomDirectorySearchEntriesListener { + private let onUpdateClosure: ([RoomDirectorySearchEntryUpdate]) -> Void + + func onUpdate(roomEntriesUpdate: [RoomDirectorySearchEntryUpdate]) { + onUpdateClosure(roomEntriesUpdate) + } + + init(_ onUpdateClosure: @escaping (([RoomDirectorySearchEntryUpdate]) -> Void)) { + self.onUpdateClosure = onUpdateClosure + } +} diff --git a/ElementX/Sources/Services/RoomDirectorySearch/RoomDirectorySearchProxyProtocol.swift b/ElementX/Sources/Services/RoomDirectorySearch/RoomDirectorySearchProxyProtocol.swift new file mode 100644 index 000000000..c1c0588d5 --- /dev/null +++ b/ElementX/Sources/Services/RoomDirectorySearch/RoomDirectorySearchProxyProtocol.swift @@ -0,0 +1,39 @@ +// +// Copyright 2024 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +// sourcery: AutoMockable +protocol RoomDirectorySearchProxyProtocol { + var resultsPublisher: CurrentValuePublisher<[RoomDirectorySearchResult], Never> { get } + + func search(query: String?) async -> Result + func nextPage() async -> Result +} + +enum RoomDirectorySearchError: Error { + case searchFailed + case nextPageQueryFailed +} + +struct RoomDirectorySearchResult: Identifiable { + let id: String + let alias: String? + let name: String? + let topic: String? + let avatarURL: URL? + let canBeJoined: Bool +} diff --git a/ElementX/Sources/Services/Timeline/TimelineProxy.swift b/ElementX/Sources/Services/Timeline/TimelineProxy.swift index 1c26a39dc..e42e20c30 100644 --- a/ElementX/Sources/Services/Timeline/TimelineProxy.swift +++ b/ElementX/Sources/Services/Timeline/TimelineProxy.swift @@ -220,7 +220,7 @@ final class TimelineProxy: TimelineProxyProtocol { sendMessageBackgroundTask?.stop() } - let handle = timeline.sendAudio(url: url.path(percentEncoded: false), audioInfo: audioInfo, progressWatcher: UploadProgressListener { progress in + let handle = timeline.sendAudio(url: url.path(percentEncoded: false), audioInfo: audioInfo, caption: nil, formattedCaption: nil, progressWatcher: UploadProgressListener { progress in progressSubject?.send(progress) }) @@ -281,7 +281,7 @@ final class TimelineProxy: TimelineProxyProtocol { sendMessageBackgroundTask?.stop() } - let handle = timeline.sendImage(url: url.path(percentEncoded: false), thumbnailUrl: thumbnailURL.path(percentEncoded: false), imageInfo: imageInfo, progressWatcher: UploadProgressListener { progress in + let handle = timeline.sendImage(url: url.path(percentEncoded: false), thumbnailUrl: thumbnailURL.path(percentEncoded: false), imageInfo: imageInfo, caption: nil, formattedCaption: nil, progressWatcher: UploadProgressListener { progress in progressSubject?.send(progress) }) @@ -339,7 +339,7 @@ final class TimelineProxy: TimelineProxyProtocol { sendMessageBackgroundTask?.stop() } - let handle = timeline.sendVideo(url: url.path(percentEncoded: false), thumbnailUrl: thumbnailURL.path(percentEncoded: false), videoInfo: videoInfo, progressWatcher: UploadProgressListener { progress in + let handle = timeline.sendVideo(url: url.path(percentEncoded: false), thumbnailUrl: thumbnailURL.path(percentEncoded: false), videoInfo: videoInfo, caption: nil, formattedCaption: nil, progressWatcher: UploadProgressListener { progress in progressSubject?.send(progress) }) @@ -370,7 +370,7 @@ final class TimelineProxy: TimelineProxyProtocol { sendMessageBackgroundTask?.stop() } - let handle = timeline.sendVoiceMessage(url: url.path(percentEncoded: false), audioInfo: audioInfo, waveform: waveform, progressWatcher: UploadProgressListener { progress in + let handle = timeline.sendVoiceMessage(url: url.path(percentEncoded: false), audioInfo: audioInfo, waveform: waveform, caption: nil, formattedCaption: nil, progressWatcher: UploadProgressListener { progress in progressSubject?.send(progress) }) diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchCell-iPad-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchCell-iPad-en-GB.1.png new file mode 100644 index 000000000..859ab5c4b --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchCell-iPad-en-GB.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e313574042e1aa4c4252f5a4f4a5a2b3b567963217b1f0b14b94fa3ea5555b3e +size 210853 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchCell-iPad-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchCell-iPad-pseudo.1.png new file mode 100644 index 000000000..859ab5c4b --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchCell-iPad-pseudo.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e313574042e1aa4c4252f5a4f4a5a2b3b567963217b1f0b14b94fa3ea5555b3e +size 210853 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchCell-iPhone-15-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchCell-iPhone-15-en-GB.1.png new file mode 100644 index 000000000..393d410f2 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchCell-iPhone-15-en-GB.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9dfc605661458f731da9bbcdf338535e123c4843e2643e63d3c57c01c5189c95 +size 149275 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchCell-iPhone-15-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchCell-iPhone-15-pseudo.1.png new file mode 100644 index 000000000..393d410f2 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchCell-iPhone-15-pseudo.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9dfc605661458f731da9bbcdf338535e123c4843e2643e63d3c57c01c5189c95 +size 149275 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchScreenScreen-iPad-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchScreenScreen-iPad-en-GB.1.png new file mode 100644 index 000000000..d2966ffbf --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchScreenScreen-iPad-en-GB.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4b0603f35bb8a828beff63268191992fab0d83660e2a0c5175e98b62e7b0730 +size 108648 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchScreenScreen-iPad-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchScreenScreen-iPad-pseudo.1.png new file mode 100644 index 000000000..73ddbd869 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchScreenScreen-iPad-pseudo.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2b8ce266191dfbc6e95a944ff0edc826d71cdf923f3eb0efb5e34215925fbcff +size 121551 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchScreenScreen-iPhone-15-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchScreenScreen-iPhone-15-en-GB.1.png new file mode 100644 index 000000000..364f8b397 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchScreenScreen-iPhone-15-en-GB.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:615968dc9a95fbdb89e1f9cb3fe8b44916c3af6e323cd92c2f4d5db4ddb1284d +size 64240 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchScreenScreen-iPhone-15-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchScreenScreen-iPhone-15-pseudo.1.png new file mode 100644 index 000000000..213db2ed7 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchScreenScreen-iPhone-15-pseudo.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97564fa372abeae5d8968dfc37dc9daefc8ae2d2fd5e3d7749d10cf0da5e4815 +size 72907 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchView-iPad-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchView-iPad-en-GB.1.png new file mode 100644 index 000000000..624e27895 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchView-iPad-en-GB.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4268ac08be6b463369ec4e0c934231e997b033e45baed23df03cb35869007272 +size 82475 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchView-iPad-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchView-iPad-pseudo.1.png new file mode 100644 index 000000000..0208ddfd9 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchView-iPad-pseudo.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e267a3678f60b674ec7078a2f5c2ccec2c54c5d18fed5954f0abe00f4fa6aee4 +size 88868 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchView-iPhone-15-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchView-iPhone-15-en-GB.1.png new file mode 100644 index 000000000..3da2b0c12 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchView-iPhone-15-en-GB.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0d8ffab7621673199dc80f52821ba7777e889454b530d95da212a929aefbca3 +size 40309 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchView-iPhone-15-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchView-iPhone-15-pseudo.1.png new file mode 100644 index 000000000..d88f85745 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_roomDirectorySearchView-iPhone-15-pseudo.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbdc78f06c3ea80474b399722c2bbfd31cfd4ff79cb7e0b0729e87a41587b3be +size 47531 diff --git a/UnitTests/Sources/RoomDirectorySearchScreenScreenViewModelTests.swift b/UnitTests/Sources/RoomDirectorySearchScreenScreenViewModelTests.swift new file mode 100644 index 000000000..43bac13e2 --- /dev/null +++ b/UnitTests/Sources/RoomDirectorySearchScreenScreenViewModelTests.swift @@ -0,0 +1,22 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest + +@testable import ElementX + +@MainActor +class RoomDirectorySearchScreenScreenViewModelTests: XCTestCase { } diff --git a/changelog.d/pr-2585.wip b/changelog.d/pr-2585.wip new file mode 100644 index 000000000..d5be61958 --- /dev/null +++ b/changelog.d/pr-2585.wip @@ -0,0 +1 @@ +Room directory search to search public rooms can now be accessed and used for searching, with no pagination yet. Only available in nightly. \ No newline at end of file diff --git a/project.yml b/project.yml index 743cd8a97..3d86b3cb3 100644 --- a/project.yml +++ b/project.yml @@ -48,7 +48,7 @@ packages: # Element/Matrix dependencies MatrixRustSDK: url: https://github.com/matrix-org/matrix-rust-components-swift - exactVersion: 1.1.50 + exactVersion: 1.1.51 # path: ../matrix-rust-sdk Compound: url: https://github.com/element-hq/compound-ios @@ -67,7 +67,7 @@ packages: # path: ../swift-ogg WysiwygComposer: url: https://github.com/matrix-org/matrix-wysiwyg-composer-swift - exactVersion: 2.33.0 + exactVersion: 2.34.0 # path: ../matrix-wysiwyg/platforms/ios/lib/WysiwygComposer # External dependencies