Remove GenericCallLinkCoordinator, merging it into CallScreen. (#3181)

* Remove incorrect message send call and fix typo.

* Add overlay coordinator presentation to the NavigationRootCoordinator.

* Remove GenericCallLinkCoordinator, merging it into CallScreen.

This will allow for picture in picture on call links when available.
This commit is contained in:
Doug 2024-08-19 17:21:25 +01:00 committed by GitHub
parent 6616d1799f
commit e0ba9925e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 418 additions and 294 deletions

View File

@ -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 = "<group>"; };
287FC98AF2664EAD79C0D902 /* UIDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIDevice.swift; sourceTree = "<group>"; };
28C19F54A0C4FC9AB7ABD583 /* TextRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItemContent.swift; sourceTree = "<group>"; };
28C202C1C7E330F124981A31 /* GenericCallLinkWidgetDriver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericCallLinkWidgetDriver.swift; sourceTree = "<group>"; };
28EA8BE9EEDBD17555141C7E /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = el; path = el.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
2910422CB628D3B2BBE47449 /* SeparatorRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineView.swift; sourceTree = "<group>"; };
295E28C3B9EAADF519BF2F44 /* AuthenticationFlowCoordinatorUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationFlowCoordinatorUITests.swift; sourceTree = "<group>"; };
@ -1364,6 +1366,7 @@
2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilder.swift; sourceTree = "<group>"; };
2AB2C848BB9A7A9B618B7B89 /* TextBasedRoomTimelineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineTests.swift; sourceTree = "<group>"; };
2AE807361805463F5AEDD1CA /* VoiceMessagePreviewComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessagePreviewComposer.swift; sourceTree = "<group>"; };
2AE83A3DD63BCFBB956FE5CB /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nl; path = nl.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
2AF715D4FD4710EBB637D661 /* SettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
2BA894BC09972DC45E497D37 /* TimelineInteractionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineInteractionHandler.swift; sourceTree = "<group>"; };
2BB385E148DE55C85C0A02D6 /* SoftLogoutScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutScreenModels.swift; sourceTree = "<group>"; };
@ -1376,6 +1379,7 @@
2D0946F77B696176E062D037 /* RoomMembersListScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenModels.swift; sourceTree = "<group>"; };
2D505843AB66822EB91F0DF0 /* TimelineItemProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemProxy.swift; sourceTree = "<group>"; };
2D7A2C4A3A74F0D2FFE9356A /* MediaPlayerProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerProviderTests.swift; sourceTree = "<group>"; };
2DA3DBE1A42EAFF93889FA04 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/SAS.strings; sourceTree = "<group>"; };
2DA4F09CB613C54FDC73AE6A /* ThreadDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadDecorator.swift; sourceTree = "<group>"; };
2DB0E533508094156D8024C3 /* TimelineStyler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyler.swift; sourceTree = "<group>"; };
2E11E7C396ED06A154CF6DF3 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/SAS.strings; sourceTree = "<group>"; };
@ -1458,6 +1462,7 @@
40B21E611DADDEF00307E7AC /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
4100DDE6BF3C566AB66B80CC /* MentionSuggestionItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionSuggestionItemView.swift; sourceTree = "<group>"; };
4176C3E20C772DE8D182863C /* LegalInformationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreen.swift; sourceTree = "<group>"; };
419957D7B1C983D7B3B93678 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
41BB37D96C3EA18F3CE8675D /* RoomDirectorySearchScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchScreenModels.swift; sourceTree = "<group>"; };
41D041A857614A9AE13C7795 /* RoomChangePermissionsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangePermissionsScreenViewModelTests.swift; sourceTree = "<group>"; };
421E716C521F96D24ECE69B3 /* NoticeRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineItem.swift; sourceTree = "<group>"; };
@ -1488,6 +1493,7 @@
47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = "<group>"; };
471BB7276C97AF60B3A5463B /* RoomDirectorySearchProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchProxy.swift; sourceTree = "<group>"; };
475D47D0BFE961B02BAC5D49 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = id; path = id.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
475EB595D7527E9A8A14043E /* uz */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uz; path = uz.lproj/Localizable.strings; sourceTree = "<group>"; };
47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenModels.swift; sourceTree = "<group>"; };
47EBB5D698CE9A25BB553A2D /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = "<group>"; };
47F29139BC2A804CE5E0757E /* MediaUploadPreviewScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModel.swift; sourceTree = "<group>"; };
@ -1528,7 +1534,6 @@
50E31AB0E77BB70E2BC77463 /* MatrixUserShareLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixUserShareLink.swift; sourceTree = "<group>"; };
510E89B989477E5EE8E503C0 /* PinnedEventsTimelineScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedEventsTimelineScreenViewModelProtocol.swift; sourceTree = "<group>"; };
514363244AE7D68080D44C6F /* NotificationSettingsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreenViewModelTests.swift; sourceTree = "<group>"; };
514923AA9640C34F39E0500A /* GenericCallLinkCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericCallLinkCoordinator.swift; sourceTree = "<group>"; };
51C2BCE0BC1FC69C1B36E688 /* BugReportScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenModels.swift; sourceTree = "<group>"; };
51C454AE59914B551A6D02C0 /* UserProfileProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileProxy.swift; sourceTree = "<group>"; };
52135BD9E0E7A091688F627A /* MessageForwardingScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenModels.swift; sourceTree = "<group>"; };
@ -2041,6 +2046,7 @@
CBF9AEA706926DD0DA2B954C /* JoinedRoomSize+MemberCount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JoinedRoomSize+MemberCount.swift"; sourceTree = "<group>"; };
CC03209FDE8CE0810617BFFF /* RoomMembersListScreenMemberCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenMemberCell.swift; sourceTree = "<group>"; };
CC1DDB2293A51EA4C2739351 /* RoomListFiltersEmptyStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListFiltersEmptyStateView.swift; sourceTree = "<group>"; };
CC437C491EA6996513B1CEAB /* ElementCallConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallConfiguration.swift; sourceTree = "<group>"; };
CC680E0E79D818706CB28CF8 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
CC743C7A85E3171BCBF0A653 /* AvatarHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarHeaderView.swift; sourceTree = "<group>"; };
CCACD75595C40EACD6AD4A74 /* AuthenticationTextFieldStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationTextFieldStyle.swift; sourceTree = "<group>"; };
@ -2049,6 +2055,7 @@
CD6613DE16AD26B3A74DA1F5 /* LocationRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRoomTimelineItemContent.swift; sourceTree = "<group>"; };
CD700E035C85738EE4B97129 /* PerformanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerformanceTests.swift; sourceTree = "<group>"; };
CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = "<group>"; };
CDE3F3911FF7CC639BDE5844 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = "<group>"; };
CEE20623EB4A9B88FB29F2BA /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/SAS.strings; sourceTree = "<group>"; };
CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = "<group>"; };
D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoverySection.swift; sourceTree = "<group>"; };
@ -2110,6 +2117,7 @@
DEC1D382565A4E9CAC2F14EA /* MediaFileHandleProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaFileHandleProxy.swift; sourceTree = "<group>"; };
DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationViewModelTests.swift; sourceTree = "<group>"; };
DF3D25B3EDB283B5807EADCF /* ReadMarkerRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineItem.swift; sourceTree = "<group>"; };
DFFB0E7C6D8E190AFA0176DC /* uz */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uz; path = uz.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
E062C1750EFC8627DE4CAB8E /* MapTilerAuthorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerAuthorization.swift; sourceTree = "<group>"; };
E06AAD6D9D3F5833E7A5A2F9 /* RoomListFilterModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListFilterModels.swift; sourceTree = "<group>"; };
E0F7CCC4A9D1927223F559D5 /* AuthenticationStartScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationStartScreenViewModelProtocol.swift; sourceTree = "<group>"; };
@ -2138,6 +2146,7 @@
E55B5EA766E89FF1F87C3ACB /* RoomNotificationSettingsProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsProxyProtocol.swift; sourceTree = "<group>"; };
E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerProtocol.swift; sourceTree = "<group>"; };
E5F2B6443D1ED8602F328539 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ru.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
E60757AFE04391B43EA568B8 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
E6372DD10DED30E7AD7BCE21 /* RoomListFiltersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListFiltersView.swift; sourceTree = "<group>"; };
E65DA46BD5CA83747AE144F3 /* secrets.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = secrets.xcconfig; sourceTree = "<group>"; };
E66763BD54A3A1D9C6E6F2F1 /* PinnedItemsIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedItemsIndicatorView.swift; sourceTree = "<group>"; };
@ -3919,7 +3928,6 @@
5221DFDF809142A2D6AC82B9 /* RoomScreen.swift */,
4552D3466B1453F287223ADA /* SwipeRightAction.swift */,
464C6BFAA853DC755B9C1F60 /* PinnedItemsBanner */,
B7D3886505ECC85A06DA8258 /* Timeline */,
);
path = View;
sourceTree = "<group>";
@ -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 = "<group>";
@ -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 = "<group>";
};
B7D3886505ECC85A06DA8258 /* Timeline */ = {
isa = PBXGroup;
children = (
);
path = Timeline;
sourceTree = "<group>";
};
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 */,

View File

@ -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)

View File

@ -457,6 +457,7 @@ private struct NavigationSplitCoordinatorView: View {
}
}
.animation(.elementDefault, value: navigationSplitCoordinator.overlayPresentationMode)
.animation(.elementDefault, value: navigationSplitCoordinator.overlayModule)
}
// Handle `horizontalSizeClass` changes breaking the navigation bar
// https://github.com/element-hq/element-x-ios/issues/617

View File

@ -56,6 +56,25 @@ class NavigationRootCoordinator: ObservableObject, CoordinatorProtocol, CustomSt
sheetModule?.coordinator
}
@Published fileprivate var overlayModule: NavigationModule? {
didSet {
if let oldValue {
logPresentationChange("Remove overlay", oldValue)
oldValue.tearDown()
}
if let overlayModule {
logPresentationChange("Set overlay", overlayModule)
overlayModule.coordinator?.start()
}
}
}
/// The currently displayed overlay coordinator
var overlayCoordinator: (any CoordinatorProtocol)? {
overlayModule?.coordinator
}
/// Sets or replaces the presented coordinator
/// - Parameter coordinator: the coordinator to display
func setRootCoordinator(_ coordinator: (any CoordinatorProtocol)?, animated: Bool = true, dismissalCallback: (() -> Void)? = nil) {
@ -90,6 +109,31 @@ class NavigationRootCoordinator: ObservableObject, CoordinatorProtocol, CustomSt
sheetModule = NavigationModule(coordinator, dismissalCallback: dismissalCallback)
}
}
/// Present an overlay on top of the split view
/// - Parameters:
/// - coordinator: the coordinator to display
/// - animated: whether the transition should be animated
/// - dismissalCallback: called when the overlay has been dismissed, programatically or otherwise
func setOverlayCoordinator(_ coordinator: (any CoordinatorProtocol)?,
animated: Bool = true,
dismissalCallback: (() -> Void)? = nil) {
guard let coordinator else {
overlayModule = nil
return
}
if overlayModule?.coordinator === coordinator {
fatalError("Cannot use the same coordinator more than once")
}
var transaction = Transaction()
transaction.disablesAnimations = !animated
withTransaction(transaction) {
overlayModule = NavigationModule(coordinator, dismissalCallback: dismissalCallback)
}
}
// MARK: - CoordinatorProtocol
@ -127,5 +171,14 @@ private struct NavigationRootCoordinatorView: View {
.sheet(item: $rootCoordinator.sheetModule) { module in
module.coordinator?.toPresentable()
}
.overlay {
Group {
if let coordinator = rootCoordinator.overlayModule?.coordinator {
coordinator.toPresentable()
.transition(.opacity)
}
}
.animation(.elementDefault, value: rootCoordinator.overlayModule)
}
}
}

View File

@ -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

View File

@ -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<Bool, ElementCallWidgetDriverError>!
var sendMessageReturnValue: Result<Bool, ElementCallWidgetDriverError>! {
var handleMessageUnderlyingReturnValue: Result<Bool, ElementCallWidgetDriverError>!
var handleMessageReturnValue: Result<Bool, ElementCallWidgetDriverError>! {
get {
if Thread.isMainThread {
return sendMessageUnderlyingReturnValue
return handleMessageUnderlyingReturnValue
} else {
var returnValue: Result<Bool, ElementCallWidgetDriverError>? = 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<Bool, ElementCallWidgetDriverError>)?
var handleMessageClosure: ((String) async -> Result<Bool, ElementCallWidgetDriverError>)?
func sendMessage(_ message: String) async -> Result<Bool, ElementCallWidgetDriverError> {
sendMessageCallsCount += 1
sendMessageReceivedMessage = message
func handleMessage(_ message: String) async -> Result<Bool, ElementCallWidgetDriverError> {
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
}
}
}
@ -11129,17 +11129,17 @@ class RoomProxyMock: RoomProxyProtocol {
return elementCallWidgetDriverDeviceIDReturnValue
}
}
//MARK: - sendCallNotificationIfNeeeded
//MARK: - sendCallNotificationIfNeeded
var sendCallNotificationIfNeeededUnderlyingCallsCount = 0
var sendCallNotificationIfNeeededCallsCount: Int {
var sendCallNotificationIfNeededUnderlyingCallsCount = 0
var sendCallNotificationIfNeededCallsCount: Int {
get {
if Thread.isMainThread {
return sendCallNotificationIfNeeededUnderlyingCallsCount
return sendCallNotificationIfNeededUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = sendCallNotificationIfNeeededUnderlyingCallsCount
returnValue = sendCallNotificationIfNeededUnderlyingCallsCount
}
return returnValue!
@ -11147,27 +11147,27 @@ class RoomProxyMock: RoomProxyProtocol {
}
set {
if Thread.isMainThread {
sendCallNotificationIfNeeededUnderlyingCallsCount = newValue
sendCallNotificationIfNeededUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
sendCallNotificationIfNeeededUnderlyingCallsCount = newValue
sendCallNotificationIfNeededUnderlyingCallsCount = newValue
}
}
}
}
var sendCallNotificationIfNeeededCalled: Bool {
return sendCallNotificationIfNeeededCallsCount > 0
var sendCallNotificationIfNeededCalled: Bool {
return sendCallNotificationIfNeededCallsCount > 0
}
var sendCallNotificationIfNeeededUnderlyingReturnValue: Result<Void, RoomProxyError>!
var sendCallNotificationIfNeeededReturnValue: Result<Void, RoomProxyError>! {
var sendCallNotificationIfNeededUnderlyingReturnValue: Result<Void, RoomProxyError>!
var sendCallNotificationIfNeededReturnValue: Result<Void, RoomProxyError>! {
get {
if Thread.isMainThread {
return sendCallNotificationIfNeeededUnderlyingReturnValue
return sendCallNotificationIfNeededUnderlyingReturnValue
} else {
var returnValue: Result<Void, RoomProxyError>? = nil
DispatchQueue.main.sync {
returnValue = sendCallNotificationIfNeeededUnderlyingReturnValue
returnValue = sendCallNotificationIfNeededUnderlyingReturnValue
}
return returnValue!
@ -11175,22 +11175,22 @@ class RoomProxyMock: RoomProxyProtocol {
}
set {
if Thread.isMainThread {
sendCallNotificationIfNeeededUnderlyingReturnValue = newValue
sendCallNotificationIfNeededUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
sendCallNotificationIfNeeededUnderlyingReturnValue = newValue
sendCallNotificationIfNeededUnderlyingReturnValue = newValue
}
}
}
}
var sendCallNotificationIfNeeededClosure: (() async -> Result<Void, RoomProxyError>)?
var sendCallNotificationIfNeededClosure: (() async -> Result<Void, RoomProxyError>)?
func sendCallNotificationIfNeeeded() async -> Result<Void, RoomProxyError> {
sendCallNotificationIfNeeededCallsCount += 1
if let sendCallNotificationIfNeeededClosure = sendCallNotificationIfNeeededClosure {
return await sendCallNotificationIfNeeededClosure()
func sendCallNotificationIfNeeded() async -> Result<Void, RoomProxyError> {
sendCallNotificationIfNeededCallsCount += 1
if let sendCallNotificationIfNeededClosure = sendCallNotificationIfNeededClosure {
return await sendCallNotificationIfNeededClosure()
} else {
return sendCallNotificationIfNeeededReturnValue
return sendCallNotificationIfNeededReturnValue
}
}
//MARK: - matrixToPermalink

View File

@ -153,7 +153,7 @@ extension RoomProxyMock {
widgetDriver.startBaseURLClientIDColorSchemeReturnValue = .success(url)
elementCallWidgetDriverDeviceIDReturnValue = widgetDriver
sendCallNotificationIfNeeededReturnValue = .success(())
sendCallNotificationIfNeededReturnValue = .success(())
matrixToPermalinkReturnValue = .success(.homeDirectory)
matrixToEventPermalinkReturnValue = .success(.homeDirectory)

View File

@ -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)
}

View File

@ -23,7 +23,7 @@ typealias CallScreenViewModelType = StateStoreViewModel<CallScreenViewState, Cal
class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol {
private let elementCallService: ElementCallServiceProtocol
private let roomProxy: RoomProxyProtocol
private let configuration: ElementCallConfiguration
private let isPictureInPictureEnabled: Bool
private let widgetDriver: ElementCallWidgetDriverProtocol
@ -42,35 +42,28 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
/// - callBaseURL: Which Element Call instance should be used
/// - clientID: Something to identify the current client on the Element Call side
init(elementCallService: ElementCallServiceProtocol,
clientProxy: ClientProxyProtocol,
roomProxy: RoomProxyProtocol,
clientID: String,
elementCallBaseURL: URL,
elementCallBaseURLOverride: URL?,
configuration: ElementCallConfiguration,
elementCallPictureInPictureEnabled: Bool,
colorScheme: ColorScheme,
appHooks: AppHooks) {
guard let deviceID = clientProxy.deviceID else { fatalError("Missing device ID for the call.") }
self.elementCallService = elementCallService
self.roomProxy = roomProxy
self.configuration = configuration
isPictureInPictureEnabled = elementCallPictureInPictureEnabled
widgetDriver = roomProxy.elementCallWidgetDriver(deviceID: deviceID)
switch configuration.kind {
case .genericCallLink(let url):
widgetDriver = GenericCallLinkWidgetDriver(url: url)
case .roomCall(let roomProxy, let clientProxy, _, _, _, _):
guard let deviceID = clientProxy.deviceID else { fatalError("Missing device ID for the call.") }
widgetDriver = roomProxy.elementCallWidgetDriver(deviceID: deviceID)
}
super.init(initialViewState: CallScreenViewState(messageHandler: Self.eventHandlerName,
script: Self.eventHandlerInjectionScript,
certificateValidator: appHooks.certificateValidatorHook))
state.bindings.javaScriptMessageHandler = { [weak self] message in
guard let self,
let message = message as? String else {
return
}
Task {
await self.widgetDriver.sendMessage(message)
}
guard let self, let message = message as? String else { return }
Task { await self.widgetDriver.handleMessage(message) }
}
elementCallService.actions
@ -80,8 +73,8 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
switch action {
case let .setAudioEnabled(enabled, roomID):
guard roomID == roomProxy.id else {
MXLog.error("Received mute request for a different room: \(roomID) != \(roomProxy.id)")
guard roomID == configuration.callID else {
MXLog.error("Received mute request for a different room: \(roomID) != \(configuration.callID)")
return
}
@ -114,48 +107,11 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
case .callEnded:
actionsSubject.send(.dismiss)
case .mediaStateChanged(let audioEnabled, _):
elementCallService.setAudioEnabled(audioEnabled, roomID: roomProxy.id)
elementCallService.setAudioEnabled(audioEnabled, roomID: configuration.callID)
}
}
.store(in: &cancellables)
// Wait for room states to be up to date before starting the call and notifying others
syncUpdateCancellable = clientProxy.actionsPublisher
.filter(\.isSyncUpdate)
.timeout(.seconds(5), scheduler: DispatchQueue.main)
.first() // Timeout will make the publisher complete, use first to handle both branches in the same place
.sink(receiveCompletion: { [weak self] _ in
Task { [weak self] in
guard let self else { return }
let baseURL = if let elementCallBaseURLOverride {
elementCallBaseURLOverride
} else if case .success(let wellKnown) = await clientProxy.getElementWellKnown(), let wellKnownCall = wellKnown?.call {
wellKnownCall.widgetURL
} else {
elementCallBaseURL
}
switch await widgetDriver.start(baseURL: baseURL, clientID: clientID, colorScheme: colorScheme) {
case .success(let url):
state.url = url
case .failure(let error):
MXLog.error("Failed starting ElementCall Widget Driver with error: \(error)")
state.bindings.alertInfo = .init(id: UUID(), title: L10n.errorUnknown, primaryButton: .init(title: L10n.actionOk, action: { [weak self] in
self?.actionsSubject.send(.dismiss)
}))
return
}
await elementCallService.setupCallSession(roomID: roomProxy.id, roomDisplayName: roomProxy.roomTitle)
_ = await roomProxy.sendCallNotificationIfNeeeded()
syncUpdateCancellable = nil
}
}, receiveValue: { _ in })
// Use did start otherwise there's a black box left on the screen during the pip controller animation.
NotificationCenter.default.publisher(for: .init("AVPictureInPictureControllerDidStartNotification"))
.sink { [weak self] notification in
@ -172,6 +128,8 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
Task { try await self.state.bindings.javaScriptEvaluator?("controls.disableCompatPip()") }
}
.store(in: &cancellables)
setupCall()
}
override func process(viewAction: CallScreenViewAction) {
@ -194,6 +152,54 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
// MARK: - Private
private func setupCall() {
switch configuration.kind {
case .genericCallLink(let url):
state.url = url
// We need widget messaging to work before enabling CallKit, otherwise mute, hangup etc do nothing.
case .roomCall(let roomProxy, let clientProxy, let clientID, let elementCallBaseURL, let elementCallBaseURLOverride, let colorScheme):
// Wait for room states to be up to date before starting the call and notifying others
syncUpdateCancellable = clientProxy.actionsPublisher
.filter(\.isSyncUpdate)
.timeout(.seconds(5), scheduler: DispatchQueue.main)
.first() // Timeout will make the publisher complete, use first to handle both branches in the same place
.sink(receiveCompletion: { [weak self] _ in
Task { [weak self] in
guard let self else { return }
let baseURL = if let elementCallBaseURLOverride {
elementCallBaseURLOverride
} else if case .success(let wellKnown) = await clientProxy.getElementWellKnown(), let wellKnownCall = wellKnown?.call {
wellKnownCall.widgetURL
} else {
elementCallBaseURL
}
switch await widgetDriver.start(baseURL: baseURL, clientID: clientID, colorScheme: colorScheme) {
case .success(let url):
state.url = url
case .failure(let error):
MXLog.error("Failed starting ElementCall Widget Driver with error: \(error)")
state.bindings.alertInfo = .init(id: UUID(),
title: L10n.errorUnknown,
primaryButton: .init(title: L10n.actionOk) {
self.actionsSubject.send(.dismiss)
})
return
}
await elementCallService.setupCallSession(roomID: roomProxy.id, roomDisplayName: roomProxy.roomTitle)
_ = await roomProxy.sendCallNotificationIfNeeded()
syncUpdateCancellable = nil
}
}, receiveValue: { _ in })
}
}
private func handleBackwardsNavigation() {
#if targetEnvironment(simulator)
if UIDevice.current.isPhone {
@ -237,7 +243,6 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
do {
let data = try JSONEncoder().encode(message)
let json = String(decoding: data, as: UTF8.self)
_ = await widgetDriver.sendMessage(json)
await postJSONToWidget(json)
} catch {

View File

@ -34,11 +34,11 @@ struct CallScreen: View {
Image(systemSymbol: .chevronBackward)
.fontWeight(.semibold)
}
.offset(y: -8)
// .padding(.leading, -8) // Fixes the button alignment, but harder to tap.
}
}
}
.alert(item: $context.alertInfo)
}
@ViewBuilder
@ -50,8 +50,6 @@ struct CallScreen: View {
// This URL is stable, forces view reloads if this representable is ever reused for another url
.id(context.viewState.url)
.ignoresSafeArea(edges: .bottom)
.presentationDragIndicator(.visible)
.alert(item: $context.alertInfo)
}
}
}
@ -211,7 +209,7 @@ struct CallScreen_Previews: PreviewProvider {
clientProxy.deviceID = "call-device-id"
let roomProxy = RoomProxyMock()
roomProxy.sendCallNotificationIfNeeededReturnValue = .success(())
roomProxy.sendCallNotificationIfNeededReturnValue = .success(())
let widgetDriver = ElementCallWidgetDriverMock()
widgetDriver.underlyingMessagePublisher = .init()
@ -221,13 +219,13 @@ struct CallScreen_Previews: PreviewProvider {
roomProxy.elementCallWidgetDriverDeviceIDReturnValue = widgetDriver
return CallScreenViewModel(elementCallService: ElementCallServiceMock(.init()),
clientProxy: clientProxy,
roomProxy: roomProxy,
clientID: "io.element.elementx",
elementCallBaseURL: "https://call.element.io",
elementCallBaseURLOverride: nil,
configuration: .init(roomProxy: roomProxy,
clientProxy: clientProxy,
clientID: "io.element.elementx",
elementCallBaseURL: "https://call.element.io",
elementCallBaseURLOverride: nil,
colorScheme: .light),
elementCallPictureInPictureEnabled: false,
colorScheme: .light,
appHooks: AppHooks())
}()

View File

@ -1,127 +0,0 @@
//
// Copyright 2023 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
import WebKit
struct GenericCallLinkCoordinatorParameters {
let url: URL
}
private enum GenericCallLinkQueryParameters {
static let appPrompt = "appPrompt"
static let confineToRoom = "confineToRoom"
}
class GenericCallLinkCoordinator: CoordinatorProtocol {
private let parameters: GenericCallLinkCoordinatorParameters
init(parameters: GenericCallLinkCoordinatorParameters) {
self.parameters = parameters
}
func toPresentable() -> 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
}
}
}

View File

@ -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
}
}
}

View File

@ -147,7 +147,7 @@ class ElementCallWidgetDriver: WidgetCapabilitiesProvider, ElementCallWidgetDriv
return .success(url)
}
func sendMessage(_ message: String) async -> Result<Bool, ElementCallWidgetDriverError> {
func handleMessage(_ message: String) async -> Result<Bool, ElementCallWidgetDriverError> {
guard let widgetDriver else {
return .failure(.driverNotSetup)
}

View File

@ -40,5 +40,6 @@ protocol ElementCallWidgetDriverProtocol {
func start(baseURL: URL, clientID: String, colorScheme: ColorScheme) async -> Result<URL, ElementCallWidgetDriverError>
func sendMessage(_ message: String) async -> Result<Bool, ElementCallWidgetDriverError>
/// 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<Bool, ElementCallWidgetDriverError>
}

View File

@ -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<String, Never>()
private let actionsSubject: PassthroughSubject<ElementCallWidgetDriverAction, Never> = .init()
var actions: AnyPublisher<ElementCallWidgetDriverAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(url: URL) {
self.url = url
}
func start(baseURL: URL, clientID: String, colorScheme: ColorScheme) async -> Result<URL, ElementCallWidgetDriverError> {
MXLog.error("Nothing to start, use the configuration's URL directly instead.")
return .success(url)
}
func handleMessage(_ message: String) async -> Result<Bool, ElementCallWidgetDriverError> {
// The web view doesn't send us messages through the Widget API, so nothing to implement (yet?).
.failure(.driverNotSetup)
}
}

View File

@ -609,7 +609,7 @@ class RoomProxy: RoomProxyProtocol {
ElementCallWidgetDriver(room: room, deviceID: deviceID)
}
func sendCallNotificationIfNeeeded() async -> Result<Void, RoomProxyError> {
func sendCallNotificationIfNeeded() async -> Result<Void, RoomProxyError> {
do {
try await room.sendCallNotificationIfNeeded()
return .success(())

View File

@ -141,7 +141,7 @@ protocol RoomProxyProtocol {
func canUserJoinCall(userID: String) async -> Result<Bool, RoomProxyError>
func elementCallWidgetDriver(deviceID: String) -> ElementCallWidgetDriverProtocol
func sendCallNotificationIfNeeeded() async -> Result<Void, RoomProxyError>
func sendCallNotificationIfNeeded() async -> Result<Void, RoomProxyError>
// MARK: - Permalinks

View File

@ -40,6 +40,22 @@ class NavigationRootCoordinatorTests: XCTestCase {
assertCoordinatorsEqual(secondRootCoordinator, navigationRootCoordinator.rootCoordinator)
}
func testOverlay() {
let rootCoordinator = SomeTestCoordinator()
navigationRootCoordinator.setRootCoordinator(rootCoordinator)
let overlayCoordinator = SomeTestCoordinator()
navigationRootCoordinator.setOverlayCoordinator(overlayCoordinator)
assertCoordinatorsEqual(rootCoordinator, navigationRootCoordinator.rootCoordinator)
assertCoordinatorsEqual(overlayCoordinator, navigationRootCoordinator.overlayCoordinator)
navigationRootCoordinator.setOverlayCoordinator(nil)
assertCoordinatorsEqual(rootCoordinator, navigationRootCoordinator.rootCoordinator)
XCTAssertNil(navigationRootCoordinator.overlayCoordinator)
}
func testReplacementDismissalCallbacks() {
XCTAssertNil(navigationRootCoordinator.rootCoordinator)
@ -54,6 +70,18 @@ class NavigationRootCoordinatorTests: XCTestCase {
waitForExpectations(timeout: 1.0)
}
func testOverlayDismissalCallback() {
let overlayCoordinator = SomeTestCoordinator()
let expectation = expectation(description: "Wait for callback")
navigationRootCoordinator.setOverlayCoordinator(overlayCoordinator) {
expectation.fulfill()
}
navigationRootCoordinator.setOverlayCoordinator(nil)
waitForExpectations(timeout: 1.0)
}
// MARK: - Private
private func assertCoordinatorsEqual(_ lhs: CoordinatorProtocol?, _ rhs: CoordinatorProtocol?) {