diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 56e4832517..dfda797d7f 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 */ @@ -767,6 +767,8 @@ A6D4C5EEA85A6A0ABA1559D6 /* RoomDetailsEditScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16D09C79746BDCD9173EB3A7 /* RoomDetailsEditScreenModels.swift */; }; A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */; }; A6F345328CCC5C9B0DAE2257 /* LogViewerScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BB05221D7D941CC82DC8480 /* LogViewerScreenViewModel.swift */; }; + A71F957D2CBFF33100FDBDF2 /* KnockedRoomProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71F957C2CBFF32A00FDBDF2 /* KnockedRoomProxy.swift */; }; + A71F957F2CBFFD2500FDBDF2 /* KnockedRoomProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71F957E2CBFFD1F00FDBDF2 /* KnockedRoomProxyMock.swift */; }; A722F426FD81FC67706BB1E0 /* CustomLayoutLabelStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42236480CF0431535EBE8387 /* CustomLayoutLabelStyle.swift */; }; A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */; }; A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; }; @@ -1213,13 +1215,13 @@ 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 = ""; }; 046C0D3F53B0B5EF0A1F5BEA /* RoomSummaryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryTests.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 = ""; }; @@ -1285,7 +1287,7 @@ 127A57D053CE8C87B5EFB089 /* Consumable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Consumable.swift; sourceTree = ""; }; 127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentValuePublisher.swift; sourceTree = ""; }; 128501375217576AF0FE3E92 /* RoomAttachmentPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomAttachmentPicker.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 = ""; }; 136F80A613B55BDD071DCEA5 /* JoinRoomScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRoomScreenModels.swift; sourceTree = ""; }; 13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -1374,7 +1376,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 = ""; }; @@ -1445,7 +1447,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 = ""; }; @@ -1549,7 +1551,7 @@ 4B41FABA2B0AEF4389986495 /* LoginMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginMode.swift; sourceTree = ""; }; 4BD371B60E07A5324B9507EF /* AnalyticsSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenCoordinator.swift; sourceTree = ""; }; 4C8D988E82A8DFA13BE46F7C /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = pl; path = pl.lproj/Localizable.stringsdict; sourceTree = ""; }; - 4CD6AC7546E8D7E5C73CEA48 /* ElementX.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = ElementX.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 4CD6AC7546E8D7E5C73CEA48 /* ElementX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ElementX.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4CDDDDD9FE1A699D23A5E096 /* LoginScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreen.swift; sourceTree = ""; }; 4D3A7375AB22721C436EB056 /* ComposerToolbarModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerToolbarModels.swift; sourceTree = ""; }; 4E2245243369B99216C7D84E /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; }; @@ -1815,7 +1817,7 @@ 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 = ""; }; 8DA1E8F287680C8ED25EDBAC /* NetworkMonitorMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitorMock.swift; sourceTree = ""; }; - 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = ""; }; + 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = ""; }; 8E1584F8BCF407BB94F48F04 /* EncryptionResetPasswordScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreen.swift; sourceTree = ""; }; 8EAF4A49F3ACD8BB8B0D2371 /* ClientSDKMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientSDKMock.swift; sourceTree = ""; }; 8F21ED7205048668BEB44A38 /* AppActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppActivityView.swift; sourceTree = ""; }; @@ -1917,6 +1919,8 @@ A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = ""; }; A6C11AD9813045E44F950410 /* ElementCallWidgetDriverProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallWidgetDriverProtocol.swift; sourceTree = ""; }; A6EA0D8B0BBD8805F7D5A133 /* TextBasedRoomTimelineViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineViewProtocol.swift; sourceTree = ""; }; + A71F957C2CBFF32A00FDBDF2 /* KnockedRoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KnockedRoomProxy.swift; sourceTree = ""; }; + A71F957E2CBFFD1F00FDBDF2 /* KnockedRoomProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KnockedRoomProxyMock.swift; sourceTree = ""; }; A73A07BAEDD74C48795A996A /* AsyncSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSequence.swift; sourceTree = ""; }; A7978C9EFBDD7DE39BD86726 /* RestorationTokenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestorationTokenTests.swift; sourceTree = ""; }; A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = ""; }; @@ -1991,7 +1995,7 @@ B50F03079F6B5EF9CA005F14 /* TimelineProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineProxyProtocol.swift; sourceTree = ""; }; B53AC78E49A297AC1D72A7CF /* AppMediator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppMediator.swift; sourceTree = ""; }; B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = ""; }; - B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = ""; }; + B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = ConfettiScene.scn; 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 = ""; }; B655A536341D2695158C6664 /* AuthenticationClientBuilderFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationClientBuilderFactory.swift; sourceTree = ""; }; @@ -2106,7 +2110,7 @@ CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = ""; }; CDE3F3911FF7CC639BDE5844 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; CEE20623EB4A9B88FB29F2BA /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/SAS.strings; sourceTree = ""; }; - CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = ""; }; + CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; 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 = ""; }; D09A267106B9585D3D0CFC0D /* ClientError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientError.swift; sourceTree = ""; }; @@ -2237,7 +2241,7 @@ ED0CBEAB5F796BEFBAF7BB6A /* VideoRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineView.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 = ""; }; ED49073BB1C1FC649DAC2CCD /* LocationRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRoomTimelineView.swift; sourceTree = ""; }; ED60E4D2CD678E1EBF16F77A /* BlockedUsersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreen.swift; sourceTree = ""; }; EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = ""; }; @@ -2259,7 +2263,7 @@ 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 = ""; }; @@ -2899,6 +2903,7 @@ 31CE4DA53232AA534057F912 /* Mocks */ = { isa = PBXGroup; children = ( + A71F957E2CBFFD1F00FDBDF2 /* KnockedRoomProxyMock.swift */, 69CB8242D69B7E4D0B32E18D /* AggregatedReactionMock.swift */, 3BAC027034248429A438886B /* AppMediatorMock.swift */, 0554FEA301486A8CFA475D5A /* AuthenticationClientBuilderFactoryMock.swift */, @@ -3195,6 +3200,7 @@ 40E6246F03D1FE377BC5D963 /* Room */ = { isa = PBXGroup; children = ( + A71F957C2CBFF32A00FDBDF2 /* KnockedRoomProxy.swift */, 0E95B3BDB80531C85CD50AE6 /* InvitedRoomProxy.swift */, 07C6B0B087FE6601C3F77816 /* JoinedRoomProxy.swift */, B6404166CBF5CC88673FF9E2 /* RoomDetails.swift */, @@ -6450,6 +6456,7 @@ 46A6DB0F78FB399BD59E2D41 /* EncryptionKeyProviderProtocol.swift in Sources */, 0C6DF318E9C8F6461E6ABDE7 /* EncryptionResetPasswordScreen.swift in Sources */, 36926D795D6D19177C7812F8 /* EncryptionResetPasswordScreenCoordinator.swift in Sources */, + A71F957D2CBFF33100FDBDF2 /* KnockedRoomProxy.swift in Sources */, B1B255CE0E4306DD6E09D936 /* EncryptionResetPasswordScreenModels.swift in Sources */, D6152E21036B88C44ECB22E7 /* EncryptionResetPasswordScreenViewModel.swift in Sources */, A0601810597769B81C2358AF /* EncryptionResetPasswordScreenViewModelProtocol.swift in Sources */, @@ -6607,6 +6614,7 @@ EA01A06EEDFEF4AE7652E5F3 /* NSRegularExpresion.swift in Sources */, FA2BBAE9FC5E2E9F960C0980 /* NavigationCoordinators.swift in Sources */, 71C1347F23868324A4F43940 /* NavigationModule.swift in Sources */, + A71F957F2CBFFD2500FDBDF2 /* KnockedRoomProxyMock.swift in Sources */, B5E455C9689EA600EDB3E9E0 /* NavigationRootCoordinator.swift in Sources */, 93BAF04D9CCBC0A8841414D0 /* NetworkMonitor.swift in Sources */, 96B3606E30F824095B1DD022 /* NetworkMonitorMock.swift in Sources */, @@ -7277,9 +7285,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; @@ -7328,9 +7334,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)"; @@ -7356,9 +7360,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)"; @@ -7603,9 +7605,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; diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.strings b/ElementX/Resources/Localizations/en.lproj/Localizable.strings index fea01d5a7a..3724e95cab 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.strings @@ -226,6 +226,7 @@ "common_username" = "Username"; "common_verification_cancelled" = "Verification cancelled"; "common_verification_complete" = "Verification complete"; +"common_verified" = "Verified"; "common_verify_device" = "Verify device"; "common_video" = "Video"; "common_voice_message" = "Voice message"; @@ -341,6 +342,10 @@ "screen_create_room_access_section_header" = "Room Access"; "screen_create_room_access_section_knocking_option_description" = "Anyone can ask to join the room but an administrator or a moderator will have to accept the request"; "screen_create_room_access_section_knocking_option_title" = "Ask to join"; +"screen_join_room_cancel_knock_action" = "Cancel request"; +"screen_join_room_knock_message_description" = "Message (optional)"; +"screen_join_room_knock_sent_description" = "You will receive an invite to join the room if your request is accepted."; +"screen_join_room_knock_sent_title" = "Request to join sent"; "screen_pinned_timeline_empty_state_description" = "Press on a message and choose “%1$@” to include here."; "screen_pinned_timeline_empty_state_headline" = "Pin important messages so that they can be easily discovered"; "screen_reset_encryption_password_error" = "An unknown error happened. Please check your account password is correct and try again."; @@ -504,7 +509,7 @@ "screen_invites_empty_list" = "No Invites"; "screen_invites_invited_you" = "%1$@ (%2$@) invited you"; "screen_join_room_join_action" = "Join room"; -"screen_join_room_knock_action" = "Knock to join"; +"screen_join_room_knock_action" = "Send request to join"; "screen_join_room_space_not_supported_description" = "%1$@ does not support spaces yet. You can access spaces on web."; "screen_join_room_space_not_supported_title" = "Spaces are not supported yet"; "screen_join_room_subtitle_knock" = "Click the button below and a room administrator will be notified. You’ll be able to join the conversation once approved."; diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index 1c10cdbc56..5eabe89580 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -662,7 +662,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { via: via, clientProxy: userSession.clientProxy, mediaProvider: userSession.mediaProvider, - userIndicatorController: userIndicatorController)) + userIndicatorController: userIndicatorController, + appSettings: appSettings)) joinRoomScreenCoordinator = coordinator diff --git a/ElementX/Sources/Generated/Strings.swift b/ElementX/Sources/Generated/Strings.swift index a14b6da1e7..11dfb30e47 100644 --- a/ElementX/Sources/Generated/Strings.swift +++ b/ElementX/Sources/Generated/Strings.swift @@ -502,6 +502,8 @@ internal enum L10n { internal static var commonVerificationCancelled: String { return L10n.tr("Localizable", "common_verification_cancelled") } /// Verification complete internal static var commonVerificationComplete: String { return L10n.tr("Localizable", "common_verification_complete") } + /// Verified + internal static var commonVerified: String { return L10n.tr("Localizable", "common_verified") } /// Verify device internal static var commonVerifyDevice: String { return L10n.tr("Localizable", "common_verify_device") } /// Video @@ -1187,10 +1189,18 @@ internal enum L10n { internal static func screenInvitesInvitedYou(_ p1: Any, _ p2: Any) -> String { return L10n.tr("Localizable", "screen_invites_invited_you", String(describing: p1), String(describing: p2)) } + /// Cancel request + internal static var screenJoinRoomCancelKnockAction: String { return L10n.tr("Localizable", "screen_join_room_cancel_knock_action") } /// Join room internal static var screenJoinRoomJoinAction: String { return L10n.tr("Localizable", "screen_join_room_join_action") } - /// Knock to join + /// Send request to join internal static var screenJoinRoomKnockAction: String { return L10n.tr("Localizable", "screen_join_room_knock_action") } + /// Message (optional) + internal static var screenJoinRoomKnockMessageDescription: String { return L10n.tr("Localizable", "screen_join_room_knock_message_description") } + /// You will receive an invite to join the room if your request is accepted. + internal static var screenJoinRoomKnockSentDescription: String { return L10n.tr("Localizable", "screen_join_room_knock_sent_description") } + /// Request to join sent + internal static var screenJoinRoomKnockSentTitle: String { return L10n.tr("Localizable", "screen_join_room_knock_sent_title") } /// %1$@ does not support spaces yet. You can access spaces on web. internal static func screenJoinRoomSpaceNotSupportedDescription(_ p1: Any) -> String { return L10n.tr("Localizable", "screen_join_room_space_not_supported_description", String(describing: p1)) diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 1b472cd45a..039932ee7f 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -2835,6 +2835,146 @@ class ClientProxyMock: ClientProxyProtocol { return joinRoomAliasReturnValue } } + //MARK: - knockRoom + + var knockRoomMessageUnderlyingCallsCount = 0 + var knockRoomMessageCallsCount: Int { + get { + if Thread.isMainThread { + return knockRoomMessageUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = knockRoomMessageUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + knockRoomMessageUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + knockRoomMessageUnderlyingCallsCount = newValue + } + } + } + } + var knockRoomMessageCalled: Bool { + return knockRoomMessageCallsCount > 0 + } + var knockRoomMessageReceivedArguments: (roomID: String, message: String?)? + var knockRoomMessageReceivedInvocations: [(roomID: String, message: String?)] = [] + + var knockRoomMessageUnderlyingReturnValue: Result! + var knockRoomMessageReturnValue: Result! { + get { + if Thread.isMainThread { + return knockRoomMessageUnderlyingReturnValue + } else { + var returnValue: Result? = nil + DispatchQueue.main.sync { + returnValue = knockRoomMessageUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + knockRoomMessageUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + knockRoomMessageUnderlyingReturnValue = newValue + } + } + } + } + var knockRoomMessageClosure: ((String, String?) async -> Result)? + + func knockRoom(_ roomID: String, message: String?) async -> Result { + knockRoomMessageCallsCount += 1 + knockRoomMessageReceivedArguments = (roomID: roomID, message: message) + DispatchQueue.main.async { + self.knockRoomMessageReceivedInvocations.append((roomID: roomID, message: message)) + } + if let knockRoomMessageClosure = knockRoomMessageClosure { + return await knockRoomMessageClosure(roomID, message) + } else { + return knockRoomMessageReturnValue + } + } + //MARK: - knockRoomAlias + + var knockRoomAliasMessageUnderlyingCallsCount = 0 + var knockRoomAliasMessageCallsCount: Int { + get { + if Thread.isMainThread { + return knockRoomAliasMessageUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = knockRoomAliasMessageUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + knockRoomAliasMessageUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + knockRoomAliasMessageUnderlyingCallsCount = newValue + } + } + } + } + var knockRoomAliasMessageCalled: Bool { + return knockRoomAliasMessageCallsCount > 0 + } + var knockRoomAliasMessageReceivedArguments: (roomAlias: String, message: String?)? + var knockRoomAliasMessageReceivedInvocations: [(roomAlias: String, message: String?)] = [] + + var knockRoomAliasMessageUnderlyingReturnValue: Result! + var knockRoomAliasMessageReturnValue: Result! { + get { + if Thread.isMainThread { + return knockRoomAliasMessageUnderlyingReturnValue + } else { + var returnValue: Result? = nil + DispatchQueue.main.sync { + returnValue = knockRoomAliasMessageUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + knockRoomAliasMessageUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + knockRoomAliasMessageUnderlyingReturnValue = newValue + } + } + } + } + var knockRoomAliasMessageClosure: ((String, String?) async -> Result)? + + func knockRoomAlias(_ roomAlias: String, message: String?) async -> Result { + knockRoomAliasMessageCallsCount += 1 + knockRoomAliasMessageReceivedArguments = (roomAlias: roomAlias, message: message) + DispatchQueue.main.async { + self.knockRoomAliasMessageReceivedInvocations.append((roomAlias: roomAlias, message: message)) + } + if let knockRoomAliasMessageClosure = knockRoomAliasMessageClosure { + return await knockRoomAliasMessageClosure(roomAlias, message) + } else { + return knockRoomAliasMessageReturnValue + } + } //MARK: - uploadMedia var uploadMediaUnderlyingCallsCount = 0 @@ -9541,6 +9681,117 @@ class KeychainControllerMock: KeychainControllerProtocol { removePINCodeBiometricStateClosure?() } } +class KnockedRoomProxyMock: KnockedRoomProxyProtocol { + var id: String { + get { return underlyingId } + set(value) { underlyingId = value } + } + var underlyingId: String! + var canonicalAlias: String? + var ownUserID: String { + get { return underlyingOwnUserID } + set(value) { underlyingOwnUserID = value } + } + var underlyingOwnUserID: String! + var name: String? + var topic: String? + var avatar: RoomAvatar { + get { return underlyingAvatar } + set(value) { underlyingAvatar = value } + } + var underlyingAvatar: RoomAvatar! + var avatarURL: URL? + var isPublic: Bool { + get { return underlyingIsPublic } + set(value) { underlyingIsPublic = value } + } + var underlyingIsPublic: Bool! + var isDirect: Bool { + get { return underlyingIsDirect } + set(value) { underlyingIsDirect = value } + } + var underlyingIsDirect: Bool! + var isSpace: Bool { + get { return underlyingIsSpace } + set(value) { underlyingIsSpace = value } + } + var underlyingIsSpace: Bool! + var joinedMembersCount: Int { + get { return underlyingJoinedMembersCount } + set(value) { underlyingJoinedMembersCount = value } + } + var underlyingJoinedMembersCount: Int! + var activeMembersCount: Int { + get { return underlyingActiveMembersCount } + set(value) { underlyingActiveMembersCount = value } + } + var underlyingActiveMembersCount: Int! + + //MARK: - cancelKnock + + var cancelKnockUnderlyingCallsCount = 0 + var cancelKnockCallsCount: Int { + get { + if Thread.isMainThread { + return cancelKnockUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = cancelKnockUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + cancelKnockUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + cancelKnockUnderlyingCallsCount = newValue + } + } + } + } + var cancelKnockCalled: Bool { + return cancelKnockCallsCount > 0 + } + + var cancelKnockUnderlyingReturnValue: Result! + var cancelKnockReturnValue: Result! { + get { + if Thread.isMainThread { + return cancelKnockUnderlyingReturnValue + } else { + var returnValue: Result? = nil + DispatchQueue.main.sync { + returnValue = cancelKnockUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + cancelKnockUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + cancelKnockUnderlyingReturnValue = newValue + } + } + } + } + var cancelKnockClosure: (() async -> Result)? + + func cancelKnock() async -> Result { + cancelKnockCallsCount += 1 + if let cancelKnockClosure = cancelKnockClosure { + return await cancelKnockClosure() + } else { + return cancelKnockReturnValue + } + } +} class MediaLoaderMock: MediaLoaderProtocol { //MARK: - loadMediaContentForSource diff --git a/ElementX/Sources/Mocks/KnockedRoomProxyMock.swift b/ElementX/Sources/Mocks/KnockedRoomProxyMock.swift new file mode 100644 index 0000000000..610e975a57 --- /dev/null +++ b/ElementX/Sources/Mocks/KnockedRoomProxyMock.swift @@ -0,0 +1,29 @@ +// +// Copyright 2024 New Vector Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only +// Please see LICENSE in the repository root for full details. +// + +import Combine +import Foundation + +@MainActor +struct KnockedRoomProxyMockConfiguration { + var id = UUID().uuidString + var name: String? + var avatarURL: URL? + var members: [RoomMemberProxyMock] = .allMembers +} + +extension KnockedRoomProxyMock { + @MainActor + convenience init(_ configuration: KnockedRoomProxyMockConfiguration) { + self.init() + id = configuration.id + name = configuration.name + avatarURL = configuration.avatarURL + avatar = .room(id: configuration.id, name: configuration.name, avatarURL: configuration.avatarURL) // Note: This doesn't replicate the real proxy logic. + activeMembersCount = configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count + } +} diff --git a/ElementX/Sources/Other/Extensions/String.swift b/ElementX/Sources/Other/Extensions/String.swift index 58caff4888..2e27561a2e 100644 --- a/ElementX/Sources/Other/Extensions/String.swift +++ b/ElementX/Sources/Other/Extensions/String.swift @@ -90,3 +90,10 @@ extension String { return result } } + +extension String { + /// detects if the string is empty or contains only whitespaces and newlines + var isBlank: Bool { + trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + } +} diff --git a/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenCoordinator.swift b/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenCoordinator.swift index e669b42049..fbe2e23c0d 100644 --- a/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenCoordinator.swift +++ b/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenCoordinator.swift @@ -14,6 +14,7 @@ struct JoinRoomScreenCoordinatorParameters { let clientProxy: ClientProxyProtocol let mediaProvider: MediaProviderProtocol let userIndicatorController: UserIndicatorControllerProtocol + let appSettings: AppSettings } enum JoinRoomScreenCoordinatorAction { @@ -34,6 +35,7 @@ final class JoinRoomScreenCoordinator: CoordinatorProtocol { init(parameters: JoinRoomScreenCoordinatorParameters) { viewModel = JoinRoomScreenViewModel(roomID: parameters.roomID, via: parameters.via, + appSettings: parameters.appSettings, clientProxy: parameters.clientProxy, mediaProvider: parameters.mediaProvider, userIndicatorController: parameters.userIndicatorController) diff --git a/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenModels.swift b/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenModels.swift index f2c5d4ac60..a0bf1d91c6 100644 --- a/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenModels.swift +++ b/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenModels.swift @@ -18,6 +18,7 @@ enum JoinRoomScreenInteractionMode { case invited case join case knock + case knocked } struct JoinRoomScreenRoomDetails { @@ -48,6 +49,7 @@ struct JoinRoomScreenViewState: BindableState { case .loading: nil case .unknown: L10n.screenJoinRoomSubtitleNoPreview case .invited, .join, .knock: roomDetails?.canonicalAlias + case .knocked: nil } } @@ -58,6 +60,7 @@ struct JoinRoomScreenViewState: BindableState { struct JoinRoomScreenViewStateBindings { var alertInfo: AlertInfo? + var knockMessage = "" } enum JoinRoomScreenAlertType { @@ -65,6 +68,7 @@ enum JoinRoomScreenAlertType { } enum JoinRoomScreenViewAction { + case cancelKnock case knock case join case acceptInvite diff --git a/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift b/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift index da59f09bad..5088efa29f 100644 --- a/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift @@ -13,7 +13,7 @@ typealias JoinRoomScreenViewModelType = StateStoreViewModel JoinRoomScreenViewModel { @@ -145,11 +214,18 @@ struct JoinRoomScreen_Previews: PreviewProvider, TestablePreview { (false, false, true, false) case .knock: (false, false, false, true) + case .knocked: + (false, false, false, false) } if mode == .unknown { clientProxy.roomPreviewForIdentifierViaReturnValue = .failure(.sdkError(ClientProxyMockError.generic)) } else { + if mode == .knocked { + clientProxy.roomForIdentifierClosure = { _ in + .knocked(KnockedRoomProxyMock(.init())) + } + } clientProxy.roomPreviewForIdentifierViaReturnValue = .success(.init(roomID: "1", name: "The Three-Body Problem - 三体", canonicalAlias: "#3🌞problem:matrix.org", @@ -164,9 +240,11 @@ struct JoinRoomScreen_Previews: PreviewProvider, TestablePreview { canKnock: membership.canKnock)) } + ServiceLocator.shared.settings.knockingEnabled = true + return JoinRoomScreenViewModel(roomID: "1", via: [], - allowKnocking: true, + appSettings: ServiceLocator.shared.settings, clientProxy: clientProxy, mediaProvider: MediaProviderMock(configuration: .init()), userIndicatorController: ServiceLocator.shared.userIndicatorController) diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index 72a647283a..06972a1ec7 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -415,6 +415,30 @@ class ClientProxy: ClientProxyProtocol { } } + func knockRoom(_ roomID: String, message: String?) async -> Result { + do { + // TODO: It should also include a message but the API for is not available yet + let _ = try await client.knock(roomIdOrAlias: roomID) + await waitForRoomToSync(roomID: roomID, timeout: .seconds(30)) + return .success(()) + } catch { + MXLog.error("Failed knocking roomID: \(roomID) with error: \(error)") + return .failure(.sdkError(error)) + } + } + + func knockRoomAlias(_ roomAlias: String, message: String?) async -> Result { + do { + // TODO: It should also include a message but the API for is not available yet + let room = try await client.knock(roomIdOrAlias: roomAlias) + await waitForRoomToSync(roomID: room.id(), timeout: .seconds(30)) + return .success(()) + } catch { + MXLog.error("Failed knocking roomAlias: \(roomAlias) with error: \(error)") + return .failure(.sdkError(error)) + } + } + func uploadMedia(_ media: MediaInfo) async -> Result { guard let mimeType = media.mimeType else { MXLog.error("Failed uploading media, invalid mime type: \(media)") @@ -846,9 +870,17 @@ class ClientProxy: ClientProxyProtocol { let roomListItem = try roomListService.room(roomId: roomID) switch roomListItem.membership() { - case .invited, .knocked: + case .invited: return try .invited(InvitedRoomProxy(roomListItem: roomListItem, room: roomListItem.invitedRoom())) + case .knocked: + if appSettings.knockingEnabled { + return try .knocked(KnockedRoomProxy(roomListItem: roomListItem, + room: roomListItem.invitedRoom())) + } else { + return try .invited(InvitedRoomProxy(roomListItem: roomListItem, + room: roomListItem.invitedRoom())) + } case .joined: if roomListItem.isTimelineInitialized() == false { try await roomListItem.initTimeline(eventTypeFilter: eventFilters, internalIdPrefix: nil) diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index e8160f2412..aa7663a533 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -145,6 +145,10 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol { func joinRoomAlias(_ roomAlias: String) async -> Result + func knockRoom(_ roomID: String, message: String?) async -> Result + + func knockRoomAlias(_ roomAlias: String, message: String?) async -> Result + func uploadMedia(_ media: MediaInfo) async -> Result func roomForIdentifier(_ identifier: String) async -> RoomProxyType? diff --git a/ElementX/Sources/Services/Room/KnockedRoomProxy.swift b/ElementX/Sources/Services/Room/KnockedRoomProxy.swift new file mode 100644 index 0000000000..942baa0412 --- /dev/null +++ b/ElementX/Sources/Services/Room/KnockedRoomProxy.swift @@ -0,0 +1,82 @@ +// +// Copyright 2024 New Vector Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only +// Please see LICENSE in the repository root for full details. +// + +import Foundation +import MatrixRustSDK +import UIKit + +class KnockedRoomProxy: KnockedRoomProxyProtocol { + private let roomListItem: RoomListItemProtocol + private let room: RoomProtocol + + // A room identifier is constant and lazy stops it from being fetched + // multiple times over FFI + lazy var id: String = room.id() + + var canonicalAlias: String? { + room.canonicalAlias() + } + + var ownUserID: String { + room.ownUserId() + } + + var name: String? { + roomListItem.displayName() + } + + var topic: String? { + room.topic() + } + + var avatarURL: URL? { + roomListItem.avatarUrl().flatMap(URL.init(string:)) + } + + var avatar: RoomAvatar { + if isDirect, avatarURL == nil { + let heroes = room.heroes() + + if heroes.count == 1 { + return .heroes(heroes.map(UserProfileProxy.init)) + } + } + + return .room(id: id, name: name, avatarURL: avatarURL) + } + + var isDirect: Bool { + room.isDirect() + } + + var isPublic: Bool { + room.isPublic() + } + + var isSpace: Bool { + room.isSpace() + } + + var joinedMembersCount: Int { + Int(room.joinedMembersCount()) + } + + var activeMembersCount: Int { + Int(room.activeMembersCount()) + } + + init(roomListItem: RoomListItemProtocol, + room: RoomProtocol) { + self.roomListItem = roomListItem + self.room = room + } + + func cancelKnock() async -> Result { + // TODO: Implement this once the API is available + .failure(.invalidURL) + } +} diff --git a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift index 552e0484d6..46cc11f080 100644 --- a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift @@ -21,6 +21,7 @@ enum RoomProxyError: Error { enum RoomProxyType { case joined(JoinedRoomProxyProtocol) case invited(InvitedRoomProxyProtocol) + case knocked(KnockedRoomProxyProtocol) case left } @@ -56,6 +57,11 @@ protocol InvitedRoomProxyProtocol: RoomProxyProtocol { func acceptInvitation() async -> Result } +// sourcery: AutoMockable +protocol KnockedRoomProxyProtocol: RoomProxyProtocol { + func cancelKnock() async -> Result +} + enum JoinedRoomProxyAction: Equatable { case roomInfoUpdate } diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPad-en-GB.Knock.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPad-en-GB.Knock.png index 1789652cdb..3b61214cdc 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPad-en-GB.Knock.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPad-en-GB.Knock.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b9bb89feeb7a80c3bdd110bf69eb2efb08b009ebcb30825a24c2343a537280df -size 1938161 +oid sha256:adb3e3a08681e2febea9cd377862eededc6b576bfe06c753bf075eb886f55f9f +size 1788568 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPad-en-GB.Knocked.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPad-en-GB.Knocked.png new file mode 100644 index 0000000000..16efb5ef59 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPad-en-GB.Knocked.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79f1c8ea20c6791a7d25d8fc18f45d1d0fef989b8fa0316c5b312bfc9dd4c271 +size 1972382 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPad-pseudo.Knock.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPad-pseudo.Knock.png index 60860bc7e2..d34ffa65c4 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPad-pseudo.Knock.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPad-pseudo.Knock.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd7969bf12e64467689fe589abd6f193425b114778f0cfac04ec6abf78000b98 -size 1938771 +oid sha256:338b8be69ace718a2796101e2ccfbe93dca412e299279a6078b785fec10e7e4b +size 1797161 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPad-pseudo.Knocked.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPad-pseudo.Knocked.png new file mode 100644 index 0000000000..65181a2704 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPad-pseudo.Knocked.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d159ef72270668acb4ffe1443954086a895e630d5809ff51a65b9302ada8ca89 +size 1988527 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPhone-16-en-GB.Knock.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPhone-16-en-GB.Knock.png index 49e1c4a792..f0f6bd47a8 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPhone-16-en-GB.Knock.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPhone-16-en-GB.Knock.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4fb08acf5df0a46b166d2e0b4b4ebcdf399c5ce0acf6b6b728124b817aae8d0c -size 791872 +oid sha256:0be47f8b5c89c0d03e4713e32686c457452851ccc246bf4588f5cb5f82fdbabc +size 701611 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPhone-16-en-GB.Knocked.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPhone-16-en-GB.Knocked.png new file mode 100644 index 0000000000..4e8d7e6c1c --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPhone-16-en-GB.Knocked.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e0a6de1d32f7d2845bf66ae655ac2b7404083a6d3478f96e1eea29f0b102e51 +size 811030 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPhone-16-pseudo.Knock.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPhone-16-pseudo.Knock.png index c2b2930b16..999c04fe9b 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPhone-16-pseudo.Knock.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPhone-16-pseudo.Knock.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9685beb39e3ca4a7509b5438ec4733fce1019d8aae6fb12d2a1a87214a971b00 -size 793600 +oid sha256:471ae5bf707a3f9e91b7f8cea098287bb21d618297d027a72c105575b5049ff8 +size 709284 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPhone-16-pseudo.Knocked.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPhone-16-pseudo.Knocked.png new file mode 100644 index 0000000000..daca3b20bf --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_joinRoomScreen-iPhone-16-pseudo.Knocked.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e8581d10df35158cd98cc36e23f1ca1ad38659732699bd7073f92aa4331e847 +size 828204 diff --git a/UnitTests/Sources/JoinRoomScreenViewModelTests.swift b/UnitTests/Sources/JoinRoomScreenViewModelTests.swift index c0a0984cee..42b1b6a526 100644 --- a/UnitTests/Sources/JoinRoomScreenViewModelTests.swift +++ b/UnitTests/Sources/JoinRoomScreenViewModelTests.swift @@ -16,6 +16,11 @@ class JoinRoomScreenViewModelTests: XCTestCase { var context: JoinRoomScreenViewModelType.Context { viewModel.context } + + override func tearDown() { + viewModel = nil + AppSettings.resetAllSettings() + } func testInteraction() async throws { setupViewModel() @@ -43,7 +48,15 @@ class JoinRoomScreenViewModelTests: XCTestCase { XCTAssertEqual(viewModel.context.alertInfo?.id, .declineInvite) } - private func setupViewModel(throwing: Bool = false) { + func testKnockedState() async throws { + setupViewModel(knocked: true) + + try await deferFulfillment(viewModel.context.$viewState) { state in + state.mode == .knocked + }.fulfill() + } + + private func setupViewModel(throwing: Bool = false, knocked: Bool = false) { let clientProxy = ClientProxyMock(.init()) clientProxy.joinRoomViaReturnValue = throwing ? .failure(.sdkError(ClientProxyMockError.generic)) : .success(()) @@ -60,8 +73,17 @@ class JoinRoomScreenViewModelTests: XCTestCase { isPublic: false, canKnock: false)) + if knocked { + clientProxy.roomForIdentifierClosure = { _ in + .knocked(KnockedRoomProxyMock(.init())) + } + } + + ServiceLocator.shared.settings.knockingEnabled = true + viewModel = JoinRoomScreenViewModel(roomID: "1", via: [], + appSettings: ServiceLocator.shared.settings, clientProxy: clientProxy, mediaProvider: MediaProviderMock(configuration: .init()), userIndicatorController: ServiceLocator.shared.userIndicatorController)