diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 01f414a000..7492aaf173 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -218,6 +218,7 @@ 30E5628F74AD3C27A061BF25 /* QRCodeLoginScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BC1B7CB061C9865B2B91B56 /* QRCodeLoginScreenViewModel.swift */; }; 3113065AABBC14CEAE6843FA /* UserSessionFlowCoordinatorStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8774CF614849664B5B3C2A1 /* UserSessionFlowCoordinatorStateMachine.swift */; }; 3116693C5EB476E028990416 /* XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74611A4182DCF5F4D42696EC /* XCTestCase.swift */; }; + 3118D9ABFD4BE5A3492FF88A /* ElementCallConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC437C491EA6996513B1CEAB /* ElementCallConfiguration.swift */; }; 32B7891D937377A59606EDFC /* UserFlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21DD8599815136EFF5B73F38 /* UserFlowTests.swift */; }; 339BC18777912E1989F2F17D /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584A61D9C459FAFEF038A7C0 /* Section.swift */; }; 33CAC1226DFB8B5D8447D286 /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 1BCD21310B997A6837B854D6 /* GZIP */; }; @@ -425,6 +426,7 @@ 6298AB0906DDD3525CD78C6B /* LoremSwiftum in Frameworks */ = {isa = PBXBuildFile; productRef = 1A6B622CCFDEFB92D9CF1CA5 /* LoremSwiftum */; }; 62A7FC3A0191BC7181AA432B /* AudioRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907FA4DE17DEA1A3738EFB83 /* AudioRecorder.swift */; }; 62C5876C4254C58C2086F0DE /* HomeScreenContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3B4B58B79A6FA250B24A1EC /* HomeScreenContent.swift */; }; + 63780F9DA06573E38A471ECA /* GenericCallLinkWidgetDriver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C202C1C7E330F124981A31 /* GenericCallLinkWidgetDriver.swift */; }; 63CDC201A5980F304F6D0A1C /* WaveformInteractionModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFEE91FB8ABB5F5884B6D940 /* WaveformInteractionModifier.swift */; }; 63E46D18B91D08E15FC04125 /* ExpiringTaskRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B25F959A434BB9923A3223F /* ExpiringTaskRunner.swift */; }; 6409CE10CFF4DCB68C4C3872 /* ScaledPaddingModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26C69EC1157D71CC61ADAE4 /* ScaledPaddingModifier.swift */; }; @@ -792,7 +794,6 @@ B4A0C69370E6008A971463E7 /* BugReportScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C89820BB2B88D4EA28131C /* BugReportScreenViewModelProtocol.swift */; }; B4AAB3257A83B73F53FB2689 /* StateStoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F3DFE5B444F131648066F05 /* StateStoreViewModel.swift */; }; B5321A1F5B26A0F3EC54909E /* CollapsibleFlowLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC5F5209279A752D98AAC4B2 /* CollapsibleFlowLayoutTests.swift */; }; - B53D292A5CA61E371C4CD785 /* GenericCallLinkCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514923AA9640C34F39E0500A /* GenericCallLinkCoordinator.swift */; }; B5479997ECC516C121E6625E /* LocationMarkerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFECCE59967018204876D0A5 /* LocationMarkerView.swift */; }; B5618E3C948584E5C1F67033 /* DTHTMLElement+AttributedStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E508AB0EDEE017FF4F6F8D1 /* DTHTMLElement+AttributedStringBuilder.swift */; }; B5899F18AD6C56CE08FE532B /* RoomSummaryProviderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC83F47D2173B7538AA72E0E /* RoomSummaryProviderMock.swift */; }; @@ -1357,6 +1358,7 @@ 284FEEB0789B8894E52A7F34 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 287FC98AF2664EAD79C0D902 /* UIDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIDevice.swift; sourceTree = ""; }; 28C19F54A0C4FC9AB7ABD583 /* TextRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItemContent.swift; sourceTree = ""; }; + 28C202C1C7E330F124981A31 /* GenericCallLinkWidgetDriver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericCallLinkWidgetDriver.swift; sourceTree = ""; }; 28EA8BE9EEDBD17555141C7E /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = el; path = el.lproj/Localizable.stringsdict; sourceTree = ""; }; 2910422CB628D3B2BBE47449 /* SeparatorRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineView.swift; sourceTree = ""; }; 295E28C3B9EAADF519BF2F44 /* AuthenticationFlowCoordinatorUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationFlowCoordinatorUITests.swift; sourceTree = ""; }; @@ -1364,6 +1366,7 @@ 2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilder.swift; sourceTree = ""; }; 2AB2C848BB9A7A9B618B7B89 /* TextBasedRoomTimelineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineTests.swift; sourceTree = ""; }; 2AE807361805463F5AEDD1CA /* VoiceMessagePreviewComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessagePreviewComposer.swift; sourceTree = ""; }; + 2AE83A3DD63BCFBB956FE5CB /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nl; path = nl.lproj/Localizable.stringsdict; sourceTree = ""; }; 2AF715D4FD4710EBB637D661 /* SettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenViewModelProtocol.swift; sourceTree = ""; }; 2BA894BC09972DC45E497D37 /* TimelineInteractionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineInteractionHandler.swift; sourceTree = ""; }; 2BB385E148DE55C85C0A02D6 /* SoftLogoutScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutScreenModels.swift; sourceTree = ""; }; @@ -1376,6 +1379,7 @@ 2D0946F77B696176E062D037 /* RoomMembersListScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenModels.swift; sourceTree = ""; }; 2D505843AB66822EB91F0DF0 /* TimelineItemProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemProxy.swift; sourceTree = ""; }; 2D7A2C4A3A74F0D2FFE9356A /* MediaPlayerProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerProviderTests.swift; sourceTree = ""; }; + 2DA3DBE1A42EAFF93889FA04 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/SAS.strings; sourceTree = ""; }; 2DA4F09CB613C54FDC73AE6A /* ThreadDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadDecorator.swift; sourceTree = ""; }; 2DB0E533508094156D8024C3 /* TimelineStyler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyler.swift; sourceTree = ""; }; 2E11E7C396ED06A154CF6DF3 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/SAS.strings; sourceTree = ""; }; @@ -1458,6 +1462,7 @@ 40B21E611DADDEF00307E7AC /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; 4100DDE6BF3C566AB66B80CC /* MentionSuggestionItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionSuggestionItemView.swift; sourceTree = ""; }; 4176C3E20C772DE8D182863C /* LegalInformationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreen.swift; sourceTree = ""; }; + 419957D7B1C983D7B3B93678 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/InfoPlist.strings"; 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 = ""; }; @@ -1488,6 +1493,7 @@ 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = ""; }; 471BB7276C97AF60B3A5463B /* RoomDirectorySearchProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchProxy.swift; sourceTree = ""; }; 475D47D0BFE961B02BAC5D49 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = id; path = id.lproj/Localizable.stringsdict; sourceTree = ""; }; + 475EB595D7527E9A8A14043E /* uz */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uz; path = uz.lproj/Localizable.strings; sourceTree = ""; }; 47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenModels.swift; sourceTree = ""; }; 47EBB5D698CE9A25BB553A2D /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; }; 47F29139BC2A804CE5E0757E /* MediaUploadPreviewScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModel.swift; sourceTree = ""; }; @@ -1528,7 +1534,6 @@ 50E31AB0E77BB70E2BC77463 /* MatrixUserShareLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixUserShareLink.swift; sourceTree = ""; }; 510E89B989477E5EE8E503C0 /* PinnedEventsTimelineScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedEventsTimelineScreenViewModelProtocol.swift; sourceTree = ""; }; 514363244AE7D68080D44C6F /* NotificationSettingsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreenViewModelTests.swift; sourceTree = ""; }; - 514923AA9640C34F39E0500A /* GenericCallLinkCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericCallLinkCoordinator.swift; sourceTree = ""; }; 51C2BCE0BC1FC69C1B36E688 /* BugReportScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenModels.swift; sourceTree = ""; }; 51C454AE59914B551A6D02C0 /* UserProfileProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileProxy.swift; sourceTree = ""; }; 52135BD9E0E7A091688F627A /* MessageForwardingScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenModels.swift; sourceTree = ""; }; @@ -2041,6 +2046,7 @@ CBF9AEA706926DD0DA2B954C /* JoinedRoomSize+MemberCount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JoinedRoomSize+MemberCount.swift"; sourceTree = ""; }; CC03209FDE8CE0810617BFFF /* RoomMembersListScreenMemberCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenMemberCell.swift; sourceTree = ""; }; CC1DDB2293A51EA4C2739351 /* RoomListFiltersEmptyStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListFiltersEmptyStateView.swift; sourceTree = ""; }; + CC437C491EA6996513B1CEAB /* ElementCallConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallConfiguration.swift; sourceTree = ""; }; CC680E0E79D818706CB28CF8 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; CC743C7A85E3171BCBF0A653 /* AvatarHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarHeaderView.swift; sourceTree = ""; }; CCACD75595C40EACD6AD4A74 /* AuthenticationTextFieldStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationTextFieldStyle.swift; sourceTree = ""; }; @@ -2049,6 +2055,7 @@ CD6613DE16AD26B3A74DA1F5 /* LocationRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRoomTimelineItemContent.swift; sourceTree = ""; }; CD700E035C85738EE4B97129 /* PerformanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerformanceTests.swift; sourceTree = ""; }; 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 = ""; }; D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoverySection.swift; sourceTree = ""; }; @@ -2110,6 +2117,7 @@ DEC1D382565A4E9CAC2F14EA /* MediaFileHandleProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaFileHandleProxy.swift; sourceTree = ""; }; DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationViewModelTests.swift; sourceTree = ""; }; DF3D25B3EDB283B5807EADCF /* ReadMarkerRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineItem.swift; sourceTree = ""; }; + DFFB0E7C6D8E190AFA0176DC /* uz */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uz; path = uz.lproj/Localizable.stringsdict; sourceTree = ""; }; E062C1750EFC8627DE4CAB8E /* MapTilerAuthorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerAuthorization.swift; sourceTree = ""; }; E06AAD6D9D3F5833E7A5A2F9 /* RoomListFilterModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListFilterModels.swift; sourceTree = ""; }; E0F7CCC4A9D1927223F559D5 /* AuthenticationStartScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationStartScreenViewModelProtocol.swift; sourceTree = ""; }; @@ -2138,6 +2146,7 @@ E55B5EA766E89FF1F87C3ACB /* RoomNotificationSettingsProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsProxyProtocol.swift; sourceTree = ""; }; E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerProtocol.swift; sourceTree = ""; }; E5F2B6443D1ED8602F328539 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ru.lproj/Localizable.stringsdict; sourceTree = ""; }; + E60757AFE04391B43EA568B8 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; E6372DD10DED30E7AD7BCE21 /* RoomListFiltersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListFiltersView.swift; sourceTree = ""; }; E65DA46BD5CA83747AE144F3 /* secrets.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = secrets.xcconfig; sourceTree = ""; }; E66763BD54A3A1D9C6E6F2F1 /* PinnedItemsIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedItemsIndicatorView.swift; sourceTree = ""; }; @@ -3919,7 +3928,6 @@ 5221DFDF809142A2D6AC82B9 /* RoomScreen.swift */, 4552D3466B1453F287223ADA /* SwipeRightAction.swift */, 464C6BFAA853DC755B9C1F60 /* PinnedItemsBanner */, - B7D3886505ECC85A06DA8258 /* Timeline */, ); path = View; sourceTree = ""; @@ -4240,11 +4248,13 @@ 92E99C57D7F92ED16F73282C /* ElementCall */ = { isa = PBXGroup; children = ( + CC437C491EA6996513B1CEAB /* ElementCallConfiguration.swift */, 33AE897D86784CCA5E4E9227 /* ElementCallService.swift */, 406C90AF8C3E98DF5D4E5430 /* ElementCallServiceConstants.swift */, 6FC8B21E86B137BE4E91F82A /* ElementCallServiceProtocol.swift */, 309AD8BAE6437C31BA7157BF /* ElementCallWidgetDriver.swift */, A6C11AD9813045E44F950410 /* ElementCallWidgetDriverProtocol.swift */, + 28C202C1C7E330F124981A31 /* GenericCallLinkWidgetDriver.swift */, ); path = ElementCall; sourceTree = ""; @@ -4482,7 +4492,6 @@ A448A3A8F764174C60CD0CA1 /* Other */ = { isa = PBXGroup; children = ( - 514923AA9640C34F39E0500A /* GenericCallLinkCoordinator.swift */, BF34A2FD6797535C95AC918D /* PlaceholderScreenCoordinator.swift */, 854BCEAF2A832176FAACD2CB /* SplashScreenCoordinator.swift */, ); @@ -4699,13 +4708,6 @@ path = UserIndicator; sourceTree = ""; }; - B7D3886505ECC85A06DA8258 /* Timeline */ = { - isa = PBXGroup; - children = ( - ); - path = Timeline; - sourceTree = ""; - }; B86CF59E083C82C2A842E4AD /* RoomMemberDetailsScreen */ = { isa = PBXGroup; children = ( @@ -5644,6 +5646,7 @@ id, it, ka, + nl, pl, pt, "pt-BR", @@ -5652,6 +5655,7 @@ sk, sv, uk, + uz, "zh-Hans", "zh-Hant-TW", ); @@ -6252,6 +6256,7 @@ 2955F4C160CFD7794D819C64 /* EffectsScene.swift in Sources */, AE1160076F663BF14E0E893A /* EffectsView.swift in Sources */, FE4593FC2A02AAF92E089565 /* ElementAnimations.swift in Sources */, + 3118D9ABFD4BE5A3492FF88A /* ElementCallConfiguration.swift in Sources */, 5732395A4F71F51F9C754C5A /* ElementCallService.swift in Sources */, 3895969759E68FAB90C63EF7 /* ElementCallServiceConstants.swift in Sources */, 8E7A902CA16E24928F83646C /* ElementCallServiceMock.swift in Sources */, @@ -6305,7 +6310,7 @@ 7807B1DEE32617896886A8E5 /* FormattingToolbar.swift in Sources */, 46BA7F4B4D3A7164DED44B88 /* FullscreenDialog.swift in Sources */, F18CA61A58C77C84F551B8E7 /* GeneratedMocks.swift in Sources */, - B53D292A5CA61E371C4CD785 /* GenericCallLinkCoordinator.swift in Sources */, + 63780F9DA06573E38A471ECA /* GenericCallLinkWidgetDriver.swift in Sources */, 4295E5F850897710A51AE114 /* GeoURI.swift in Sources */, F0DACC95F24128A54CD537E4 /* GlobalSearchScreen.swift in Sources */, 9F11E743EA01482E78A438B0 /* GlobalSearchScreenCell.swift in Sources */, @@ -6966,6 +6971,7 @@ D196116D2DD3F2757D45FCB7 /* hu */, 330AF4D121C3396F7A14B21D /* id */, 61B33F23681660E940BA57F4 /* it */, + 2DA3DBE1A42EAFF93889FA04 /* nl */, BEE365C5A4E90ACBE398EFFE /* pt */, FABAC5C4373B0EC24D399663 /* pt-BR */, 105429F29096729EDD3152CF /* ru */, @@ -6993,6 +6999,7 @@ 475D47D0BFE961B02BAC5D49 /* id */, 6FC5015B9634698BDB8701AF /* it */, D1896F6288D80E1F3EFB3DF8 /* ka */, + 2AE83A3DD63BCFBB956FE5CB /* nl */, 4C8D988E82A8DFA13BE46F7C /* pl */, 8166F121C79C7B62BF01D508 /* pt */, 21BA866267F84BF4350B0CB7 /* pt-BR */, @@ -7001,6 +7008,7 @@ 667DD3A9D932D7D9EB380CAA /* sk */, 0EE9EAF0309A2A1D67D8FAF5 /* sv */, 5F12E996BFBEB43815189ABF /* uk */, + DFFB0E7C6D8E190AFA0176DC /* uz */, AB26D5444A4A7E095222DE8B /* zh-Hans */, 49E6066092ED45E36BB306F7 /* zh-Hant-TW */, ); @@ -7023,6 +7031,7 @@ EF98A02DED04075F7CF0C721 /* id */, 7B04BD3874D736127A8156B8 /* it */, 4629710C0337ADD9C8909542 /* ka */, + CDE3F3911FF7CC639BDE5844 /* nl */, 8140010A796DB2C7977B6643 /* pl */, 0CB569EAA5017B5B23970655 /* pt */, 8A9AE4967817E9608E22EB44 /* pt-BR */, @@ -7031,6 +7040,7 @@ AD378D580A41E42560C60E9C /* sk */, ACA11F7F50A4A3887A18CA5A /* sv */, ADCB8A232D3A8FB3E16A7303 /* uk */, + 475EB595D7527E9A8A14043E /* uz */, 284FEEB0789B8894E52A7F34 /* zh-Hans */, 91CF6F7D08228D16BA69B63B /* zh-Hant-TW */, ); @@ -7052,8 +7062,10 @@ 1D652E78832289CD9EB64488 /* hu */, 7199693797B66245EF97BCF5 /* id */, 44C314C00533E2C297796B60 /* it */, + E60757AFE04391B43EA568B8 /* nl */, 997BF045585AF6DB2EBC5755 /* pl */, A8DF55467ED4CE76B7AE9A33 /* pt */, + 419957D7B1C983D7B3B93678 /* pt-BR */, 86C8CE2630F54D5FE1591786 /* ro */, 9B7D8D3638864B7482E148CC /* ru */, 7D39AF1F659923D77778511E /* sk */, diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index 030b572195..a4c0cee691 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -212,7 +212,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg if let userSessionFlowCoordinator { userSessionFlowCoordinator.handleAppRoute(route, animated: true) } else { - navigationRootCoordinator.setSheetCoordinator(GenericCallLinkCoordinator(parameters: .init(url: url))) + presentCallScreen(genericCallLink: url) } case .userProfile(let userID): if isExternalURL { @@ -648,6 +648,31 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg elementCallService.setClientProxy(userSession.clientProxy) } + + private func presentCallScreen(genericCallLink url: URL) { + let configuration = ElementCallConfiguration(genericCallLink: url) + + let callScreenCoordinator = CallScreenCoordinator(parameters: .init(elementCallService: elementCallService, + configuration: configuration, + elementCallPictureInPictureEnabled: false, + appHooks: appHooks)) + + callScreenCoordinator.actions + .sink { [weak self] action in + guard let self else { return } + switch action { + case .pictureInPictureStarted, .pictureInPictureStopped: + // Don't allow PiP when signed out - the user could login at which point we'd + // need to hand over the call from here to the user session flow coordinator. + MXLog.error("Picture in Picture not supported before login.") + case .dismiss: + navigationRootCoordinator.setOverlayCoordinator(nil) + } + } + .store(in: &cancellables) + + navigationRootCoordinator.setOverlayCoordinator(callScreenCoordinator, animated: false) + } private func configureNotificationManager() { notificationManager.setUserSession(userSession) diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index 5c8da5b603..53a0637ac4 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -268,11 +268,9 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { case .userProfile(let userID): stateMachine.processEvent(.showUserProfileScreen(userID: userID), userInfo: .init(animated: animated)) case .call(let roomID): - Task { - await presentCallScreen(roomID: roomID) - } + Task { await presentCallScreen(roomID: roomID) } case .genericCallLink(let url): - navigationSplitCoordinator.setSheetCoordinator(GenericCallLinkCoordinator(parameters: .init(url: url)), animated: animated) + presentCallScreen(genericCallLink: url) case .settings, .chatBackupSettings: settingsFlowCoordinator.handleAppRoute(appRoute, animated: animated) case .oidcCallback: @@ -558,23 +556,39 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { // MARK: Calls - private var callScreenPictureInPictureController: AVPictureInPictureController? + private func presentCallScreen(genericCallLink url: URL) { + presentCallScreen(configuration: .init(genericCallLink: url)) + } + + private func presentCallScreen(roomID: String) async { + guard let roomProxy = await userSession.clientProxy.roomForIdentifier(roomID) else { + return + } + + presentCallScreen(roomProxy: roomProxy) + } + private func presentCallScreen(roomProxy: RoomProxyProtocol) { - guard elementCallService.ongoingCallRoomID != roomProxy.id else { + let colorScheme: ColorScheme = appMediator.windowManager.mainWindow.traitCollection.userInterfaceStyle == .light ? .light : .dark + presentCallScreen(configuration: .init(roomProxy: roomProxy, + clientProxy: userSession.clientProxy, + clientID: InfoPlistReader.main.bundleIdentifier, + elementCallBaseURL: appSettings.elementCallBaseURL, + elementCallBaseURLOverride: appSettings.elementCallBaseURLOverride, + colorScheme: colorScheme)) + } + + private var callScreenPictureInPictureController: AVPictureInPictureController? + private func presentCallScreen(configuration: ElementCallConfiguration) { + guard elementCallService.ongoingCallRoomID != configuration.callID else { MXLog.info("Returning to existing call.") callScreenPictureInPictureController?.stopPictureInPicture() return } - let colorScheme: ColorScheme = appMediator.windowManager.mainWindow.traitCollection.userInterfaceStyle == .light ? .light : .dark let callScreenCoordinator = CallScreenCoordinator(parameters: .init(elementCallService: elementCallService, - clientProxy: userSession.clientProxy, - roomProxy: roomProxy, - clientID: InfoPlistReader.main.bundleIdentifier, - elementCallBaseURL: appSettings.elementCallBaseURL, - elementCallBaseURLOverride: appSettings.elementCallBaseURLOverride, + configuration: configuration, elementCallPictureInPictureEnabled: appSettings.elementCallPictureInPictureEnabled, - colorScheme: colorScheme, appHooks: appHooks)) callScreenCoordinator.actions @@ -600,14 +614,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { analytics.track(screen: .RoomCall) } - private func presentCallScreen(roomID: String) async { - guard let roomProxy = await userSession.clientProxy.roomForIdentifier(roomID) else { - return - } - - presentCallScreen(roomProxy: roomProxy) - } - private func dismissCallScreenIfNeeded() { guard navigationSplitCoordinator.sheetCoordinator is CallScreenCoordinator else { return diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 4ad32098ab..b5c0633904 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -5142,17 +5142,17 @@ class ElementCallWidgetDriverMock: ElementCallWidgetDriverProtocol { return startBaseURLClientIDColorSchemeReturnValue } } - //MARK: - sendMessage + //MARK: - handleMessage - var sendMessageUnderlyingCallsCount = 0 - var sendMessageCallsCount: Int { + var handleMessageUnderlyingCallsCount = 0 + var handleMessageCallsCount: Int { get { if Thread.isMainThread { - return sendMessageUnderlyingCallsCount + return handleMessageUnderlyingCallsCount } else { var returnValue: Int? = nil DispatchQueue.main.sync { - returnValue = sendMessageUnderlyingCallsCount + returnValue = handleMessageUnderlyingCallsCount } return returnValue! @@ -5160,29 +5160,29 @@ class ElementCallWidgetDriverMock: ElementCallWidgetDriverProtocol { } set { if Thread.isMainThread { - sendMessageUnderlyingCallsCount = newValue + handleMessageUnderlyingCallsCount = newValue } else { DispatchQueue.main.sync { - sendMessageUnderlyingCallsCount = newValue + handleMessageUnderlyingCallsCount = newValue } } } } - var sendMessageCalled: Bool { - return sendMessageCallsCount > 0 + var handleMessageCalled: Bool { + return handleMessageCallsCount > 0 } - var sendMessageReceivedMessage: String? - var sendMessageReceivedInvocations: [String] = [] + var handleMessageReceivedMessage: String? + var handleMessageReceivedInvocations: [String] = [] - var sendMessageUnderlyingReturnValue: Result! - var sendMessageReturnValue: Result! { + var handleMessageUnderlyingReturnValue: Result! + var handleMessageReturnValue: Result! { get { if Thread.isMainThread { - return sendMessageUnderlyingReturnValue + return handleMessageUnderlyingReturnValue } else { var returnValue: Result? = nil DispatchQueue.main.sync { - returnValue = sendMessageUnderlyingReturnValue + returnValue = handleMessageUnderlyingReturnValue } return returnValue! @@ -5190,26 +5190,26 @@ class ElementCallWidgetDriverMock: ElementCallWidgetDriverProtocol { } set { if Thread.isMainThread { - sendMessageUnderlyingReturnValue = newValue + handleMessageUnderlyingReturnValue = newValue } else { DispatchQueue.main.sync { - sendMessageUnderlyingReturnValue = newValue + handleMessageUnderlyingReturnValue = newValue } } } } - var sendMessageClosure: ((String) async -> Result)? + var handleMessageClosure: ((String) async -> Result)? - func sendMessage(_ message: String) async -> Result { - sendMessageCallsCount += 1 - sendMessageReceivedMessage = message + func handleMessage(_ message: String) async -> Result { + handleMessageCallsCount += 1 + handleMessageReceivedMessage = message DispatchQueue.main.async { - self.sendMessageReceivedInvocations.append(message) + self.handleMessageReceivedInvocations.append(message) } - if let sendMessageClosure = sendMessageClosure { - return await sendMessageClosure(message) + if let handleMessageClosure = handleMessageClosure { + return await handleMessageClosure(message) } else { - return sendMessageReturnValue + return handleMessageReturnValue } } } diff --git a/ElementX/Sources/Screens/CallScreen/CallScreenCoordinator.swift b/ElementX/Sources/Screens/CallScreen/CallScreenCoordinator.swift index 13b13b0e34..401fab4c03 100644 --- a/ElementX/Sources/Screens/CallScreen/CallScreenCoordinator.swift +++ b/ElementX/Sources/Screens/CallScreen/CallScreenCoordinator.swift @@ -20,13 +20,8 @@ import SwiftUI struct CallScreenCoordinatorParameters { let elementCallService: ElementCallServiceProtocol - let clientProxy: ClientProxyProtocol - let roomProxy: RoomProxyProtocol - let clientID: String - let elementCallBaseURL: URL - let elementCallBaseURLOverride: URL? + let configuration: ElementCallConfiguration let elementCallPictureInPictureEnabled: Bool - let colorScheme: ColorScheme let appHooks: AppHooks } @@ -50,13 +45,8 @@ final class CallScreenCoordinator: CoordinatorProtocol { init(parameters: CallScreenCoordinatorParameters) { viewModel = CallScreenViewModel(elementCallService: parameters.elementCallService, - clientProxy: parameters.clientProxy, - roomProxy: parameters.roomProxy, - clientID: parameters.clientID, - elementCallBaseURL: parameters.elementCallBaseURL, - elementCallBaseURLOverride: parameters.elementCallBaseURLOverride, + configuration: parameters.configuration, elementCallPictureInPictureEnabled: parameters.elementCallPictureInPictureEnabled, - colorScheme: parameters.colorScheme, appHooks: parameters.appHooks) } diff --git a/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift b/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift index 71748a3727..a6b4d70d95 100644 --- a/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift +++ b/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift @@ -23,7 +23,7 @@ typealias CallScreenViewModelType = StateStoreViewModel AnyView { - AnyView(WebView(url: parameters.url) - // This URL is stable, forces view reloads if this representable is ever reused for another url - .id(parameters.url) - .ignoresSafeArea(edges: .bottom) - .presentationDragIndicator(.visible)) - } -} - -private struct WebView: UIViewRepresentable { - let url: URL - - func makeUIView(context: Context) -> WKWebView { - context.coordinator.webView - } - - func makeCoordinator() -> Coordinator { - Coordinator(url: url) - } - - func updateUIView(_ webView: WKWebView, context: Context) { - webView.load(URLRequest(url: context.coordinator.url)) - } - - @MainActor - class Coordinator: NSObject, WKUIDelegate, WKNavigationDelegate { - let url: URL - private(set) var webView: WKWebView! - - init(url: URL) { - if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true) { - var fragmentQueryItems = urlComponents.fragmentQueryItems ?? [] - - fragmentQueryItems.removeAll { $0.name == GenericCallLinkQueryParameters.appPrompt } - fragmentQueryItems.removeAll { $0.name == GenericCallLinkQueryParameters.confineToRoom } - - fragmentQueryItems.append(.init(name: GenericCallLinkQueryParameters.appPrompt, value: "false")) - fragmentQueryItems.append(.init(name: GenericCallLinkQueryParameters.confineToRoom, value: "true")) - - urlComponents.fragmentQueryItems = fragmentQueryItems - - if let adjustedURL = urlComponents.url { - self.url = adjustedURL - } else { - MXLog.error("Failed adjusting URL with components: \(urlComponents)") - self.url = url - } - } else { - MXLog.error("Failed constructing URL components for url: \(url)") - self.url = url - } - - super.init() - - let configuration = WKWebViewConfiguration() - - configuration.allowsInlineMediaPlayback = true - configuration.allowsPictureInPictureMediaPlayback = true - - webView = WKWebView(frame: .zero, configuration: configuration) - webView.uiDelegate = self - } - - // MARK: - WKUIDelegate - - func webView(_ webView: WKWebView, decideMediaCapturePermissionsFor origin: WKSecurityOrigin, initiatedBy frame: WKFrameInfo, type: WKMediaCaptureType) async -> WKPermissionDecision { - // Don't allow permissions for domains different than what the call was started on - guard origin.host == url.host else { - return .deny - } - - return .grant - } - - // MARK: - WKNavigationDelegate - - func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy { - // Allow any content from the main URL. - if navigationAction.request.url?.host == url.host { - return .allow - } - - // Additionally allow any embedded content such as captchas. - if let targetFrame = navigationAction.targetFrame, !targetFrame.isMainFrame { - return .allow - } - - // Otherwise the request is invalid. - return .cancel - } - } -} diff --git a/ElementX/Sources/Services/ElementCall/ElementCallConfiguration.swift b/ElementX/Sources/Services/ElementCall/ElementCallConfiguration.swift new file mode 100644 index 0000000000..b2b3f992ac --- /dev/null +++ b/ElementX/Sources/Services/ElementCall/ElementCallConfiguration.swift @@ -0,0 +1,88 @@ +// +// 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 SwiftUI + +private enum GenericCallLinkQueryParameters { + static let appPrompt = "appPrompt" + static let confineToRoom = "confineToRoom" +} + +/// Information about how a call should be configured. +struct ElementCallConfiguration { + enum Kind { + case genericCallLink(URL) + case roomCall(roomProxy: RoomProxyProtocol, + clientProxy: ClientProxyProtocol, + clientID: String, + elementCallBaseURL: URL, + elementCallBaseURLOverride: URL?, + colorScheme: ColorScheme) + } + + /// The type of call being configured i.e. whether it's an external URL or an internal room call. + let kind: Kind + + /// Creates a configuration for an external call URL. + init(genericCallLink url: URL) { + if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true) { + var fragmentQueryItems = urlComponents.fragmentQueryItems ?? [] + + fragmentQueryItems.removeAll { $0.name == GenericCallLinkQueryParameters.appPrompt } + fragmentQueryItems.removeAll { $0.name == GenericCallLinkQueryParameters.confineToRoom } + + fragmentQueryItems.append(.init(name: GenericCallLinkQueryParameters.appPrompt, value: "false")) + fragmentQueryItems.append(.init(name: GenericCallLinkQueryParameters.confineToRoom, value: "true")) + + urlComponents.fragmentQueryItems = fragmentQueryItems + + if let adjustedURL = urlComponents.url { + kind = .genericCallLink(adjustedURL) + } else { + MXLog.error("Failed adjusting URL with components: \(urlComponents)") + kind = .genericCallLink(url) + } + } else { + MXLog.error("Failed constructing URL components for url: \(url)") + kind = .genericCallLink(url) + } + } + + /// Creates a configuration for an internal room call. + init(roomProxy: RoomProxyProtocol, + clientProxy: ClientProxyProtocol, + clientID: String, + elementCallBaseURL: URL, + elementCallBaseURLOverride: URL?, + colorScheme: ColorScheme) { + kind = .roomCall(roomProxy: roomProxy, + clientProxy: clientProxy, + clientID: clientID, + elementCallBaseURL: elementCallBaseURL, + elementCallBaseURLOverride: elementCallBaseURLOverride, + colorScheme: colorScheme) + } + + /// A string representing the call being configured. + var callID: String { + switch kind { + case .genericCallLink(let url): + url.absoluteString + case .roomCall(let roomProxy, _, _, _, _, _): + roomProxy.id + } + } +} diff --git a/ElementX/Sources/Services/ElementCall/ElementCallWidgetDriver.swift b/ElementX/Sources/Services/ElementCall/ElementCallWidgetDriver.swift index 6969bb1716..f90b7a86d0 100644 --- a/ElementX/Sources/Services/ElementCall/ElementCallWidgetDriver.swift +++ b/ElementX/Sources/Services/ElementCall/ElementCallWidgetDriver.swift @@ -147,7 +147,7 @@ class ElementCallWidgetDriver: WidgetCapabilitiesProvider, ElementCallWidgetDriv return .success(url) } - func sendMessage(_ message: String) async -> Result { + func handleMessage(_ message: String) async -> Result { guard let widgetDriver else { return .failure(.driverNotSetup) } diff --git a/ElementX/Sources/Services/ElementCall/ElementCallWidgetDriverProtocol.swift b/ElementX/Sources/Services/ElementCall/ElementCallWidgetDriverProtocol.swift index 1c512a921d..2e669c6ffb 100644 --- a/ElementX/Sources/Services/ElementCall/ElementCallWidgetDriverProtocol.swift +++ b/ElementX/Sources/Services/ElementCall/ElementCallWidgetDriverProtocol.swift @@ -40,5 +40,6 @@ protocol ElementCallWidgetDriverProtocol { func start(baseURL: URL, clientID: String, colorScheme: ColorScheme) async -> Result - func sendMessage(_ message: String) async -> Result + /// Passes a message from the Widget to the SDK to handle, returning a Bool that represents whether or not the widget driver is still running. + func handleMessage(_ message: String) async -> Result } diff --git a/ElementX/Sources/Services/ElementCall/GenericCallLinkWidgetDriver.swift b/ElementX/Sources/Services/ElementCall/GenericCallLinkWidgetDriver.swift new file mode 100644 index 0000000000..2c100ffea0 --- /dev/null +++ b/ElementX/Sources/Services/ElementCall/GenericCallLinkWidgetDriver.swift @@ -0,0 +1,44 @@ +// +// 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 SwiftUI + +class GenericCallLinkWidgetDriver: ElementCallWidgetDriverProtocol { + private let url: URL + + let widgetID = UUID().uuidString + let messagePublisher = PassthroughSubject() + + private let actionsSubject: PassthroughSubject = .init() + var actions: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init(url: URL) { + self.url = url + } + + func start(baseURL: URL, clientID: String, colorScheme: ColorScheme) async -> Result { + MXLog.error("Nothing to start, use the configuration's URL directly instead.") + return .success(url) + } + + func handleMessage(_ message: String) async -> Result { + // The web view doesn't send us messages through the Widget API, so nothing to implement (yet?). + .failure(.driverNotSetup) + } +}