diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 89806ab27f..074ba56d45 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -119,6 +119,7 @@ 292827744227DF61C930BDDB /* CreateRoomScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB0D6CB491777E7FC6B5BA12 /* CreateRoomScreen.swift */; }; 29491EE7AE37E239E839C5A3 /* LocationSharingScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BEBF0E59F25E842EDB6FD11 /* LocationSharingScreenModels.swift */; }; 2955F4C160CFD7794D819C64 /* EffectsScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 024F7398C5FC12586FB10E9D /* EffectsScene.swift */; }; + 296B988AB2280207D558C5BF /* AppRoutesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F8CA15700B6BA365624AA7C /* AppRoutesTests.swift */; }; 29EE1791E0AFA1ABB7F23D2F /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 3853B78FB8531B83936C5DA6 /* SwiftState */; }; 2A90DD14DE5C891BFA433950 /* TimelineReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE0E6043EFCF6FD2A341861 /* TimelineReplyView.swift */; }; 2AAB2A77F1762A2648078A30 /* InteractiveQuickLook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 638A81B97D51591D0FCFA598 /* InteractiveQuickLook.swift */; }; @@ -157,11 +158,12 @@ 34C752A73717C691582DC6C7 /* UnsupportedRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B8500C152BC59445647DA8 /* UnsupportedRoomTimelineItem.swift */; }; 34F1261CEF6D6A00D559B520 /* SettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CFD5EB0B0EEA4549FB49784 /* SettingsScreen.swift */; }; 352C439BE0F75E101EF11FB1 /* RoomScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2886615BEBAE33A0AA4D5F8 /* RoomScreenModels.swift */; }; + 355B11D08CE0CEF97A813236 /* AppRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27A9E3FBE8A66B5A17AD7F74 /* AppRoutes.swift */; }; 35E975CFDA60E05362A7CF79 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 1222DB76B917EB8A55365BA5 /* target.yml */; }; 368C8758FCD079E6AAA18C2C /* NoticeRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */; }; 36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */; }; - 36AD4DD4C798E22584ED3200 /* URLRouting in Frameworks */ = {isa = PBXBuildFile; productRef = E9BAB8A793FE3B54CDD47102 /* URLRouting */; }; - 36CD6E11B37396E14F032CB6 /* Version in Frameworks */ = {isa = PBXBuildFile; productRef = A05AF81DDD14AD58CB0E1B9B /* Version */; }; + 36AD4DD4C798E22584ED3200 /* Version in Frameworks */ = {isa = PBXBuildFile; productRef = A05AF81DDD14AD58CB0E1B9B /* Version */; }; + 36CD6E11B37396E14F032CB6 /* Emojibase in Frameworks */ = {isa = PBXBuildFile; productRef = C05729B1684C331F5FFE9232 /* Emojibase */; }; 37D789F24199B32E3FD1AA7B /* FileRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 216F0DDC98F2A2C162D09C28 /* FileRoomTimelineItemContent.swift */; }; 383055C6ABE5BE058CEE1DDB /* WelcomeScreenScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57FE5EF0AFFE360C66420AAE /* WelcomeScreenScreenCoordinator.swift */; }; 38546A6010A2CF240EC9AF73 /* BindableState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EA1D2CBAEA5D0BD00B90D1B /* BindableState.swift */; }; @@ -200,7 +202,6 @@ 43F35A7E5703D64DB0519C59 /* ServerSelectionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD469F7513574341181F7EAA /* ServerSelectionScreen.swift */; }; 440123E29E2F9B001A775BBE /* TimelineItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D505843AB66822EB91F0DF0 /* TimelineItemProxy.swift */; }; 44121202B4A260C98BF615A7 /* RoomMembersListScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5B7A755E985FA14469E86B2 /* RoomMembersListScreenUITests.swift */; }; - 44F0E1B576C7599DF8022071 /* WysiwygComposer in Frameworks */ = {isa = PBXBuildFile; productRef = CA07D57389DACE18AEB6A5E2 /* WysiwygComposer */; }; 46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */; }; 46A261AA898344A1F3C406B1 /* ReportContentScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCE3636E3D01477C8B2E9D0 /* ReportContentScreenModels.swift */; }; 46BA7F4B4D3A7164DED44B88 /* FullscreenDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565F1B2B300597C616B37888 /* FullscreenDialog.swift */; }; @@ -484,7 +485,7 @@ A009BDFB0A6816D4C392ADCB /* SettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AF715D4FD4710EBB637D661 /* SettingsScreenViewModelProtocol.swift */; }; A021827B528F1EDC9101CA58 /* AppCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBC776F301D374A3298C69DA /* AppCoordinatorProtocol.swift */; }; A0A0D2A9564BDA3FDE2E360F /* FormattedBodyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = F73FF1A33198F5FAE9D34B1F /* FormattedBodyText.swift */; }; - A0D7E5BD0298A97DCBDCE40B /* Emojibase in Frameworks */ = {isa = PBXBuildFile; productRef = C05729B1684C331F5FFE9232 /* Emojibase */; }; + A0D7E5BD0298A97DCBDCE40B /* WysiwygComposer in Frameworks */ = {isa = PBXBuildFile; productRef = CA07D57389DACE18AEB6A5E2 /* WysiwygComposer */; }; A10D6CCDE2010C09EEA1A593 /* HomeScreenRoomList.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7661EFFCAA307A97D71132A /* HomeScreenRoomList.swift */; }; A14A9419105A1CD42F0511C4 /* UserIndicatorModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43005941B3A2C9671E23C85 /* UserIndicatorModalView.swift */; }; A17FAD2EBC53E17B5FD384DB /* InviteUsersScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22730A30C50AC2E3D5BA8642 /* InviteUsersScreenViewModelProtocol.swift */; }; @@ -555,6 +556,7 @@ 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 */; }; B5903E48CF43259836BF2DBF /* EncryptedRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56C1BCB9E83B09A45387FCA2 /* EncryptedRoomTimelineView.swift */; }; B5E455C9689EA600EDB3E9E0 /* NavigationRootCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA28F29C9F93E93CC3C2C715 /* NavigationRootCoordinator.swift */; }; @@ -789,7 +791,6 @@ FCDA202B246F75BA28E10C5F /* MapTilerAuthorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = E062C1750EFC8627DE4CAB8E /* MapTilerAuthorization.swift */; }; FD762761C5D0C30E6255C3D8 /* ServerConfirmationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABA4CF2F5B4F68D02E412004 /* ServerConfirmationScreenViewModelProtocol.swift */; }; FE4593FC2A02AAF92E089565 /* ElementAnimations.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF1593DD87F974F8509BB619 /* ElementAnimations.swift */; }; - FF149F0A3550A54C50ECBE7A /* AppRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C843CF833BF6485B64AC87E1 /* AppRouter.swift */; }; FF34BF2AF731340AF9414A18 /* SwipeRightAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4552D3466B1453F287223ADA /* SwipeRightAction.swift */; }; FFD3E4FF948E06C7585317FC /* TimelineStyler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892E29C98C4E8182C9037F84 /* TimelineStyler.swift */; }; /* End PBXBuildFile section */ @@ -964,6 +965,7 @@ 260004737C573A56FA01E86E /* Encodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encodable.swift; sourceTree = ""; }; 277C20CDD5B64510401B6D0D /* ServerConfigurationScreenViewStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfigurationScreenViewStateTests.swift; sourceTree = ""; }; 27A1AD6389A4659AF0CEAE62 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = ""; }; + 27A9E3FBE8A66B5A17AD7F74 /* AppRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRoutes.swift; sourceTree = ""; }; 27B8315A340B46F98B9C5AF0 /* TimelineTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTableViewController.swift; sourceTree = ""; }; 27EA0F71A3A400A202E15318 /* CreatePollScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePollScreen.swift; sourceTree = ""; }; 287FC98AF2664EAD79C0D902 /* UIDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIDevice.swift; sourceTree = ""; }; @@ -1079,6 +1081,7 @@ 5098DA7799946A61E34A2373 /* FileRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineItem.swift; sourceTree = ""; }; 50E31AB0E77BB70E2BC77463 /* MatrixUserShareLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixUserShareLink.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 = ""; }; @@ -1285,6 +1288,7 @@ 9E685274772980BDEFF6691E /* UNUserNotificationCenter+Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNUserNotificationCenter+Settings.swift"; sourceTree = ""; }; 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIConstants.swift; sourceTree = ""; }; 9F85164F9475FF2867F71AAA /* RoomTimelineController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineController.swift; sourceTree = ""; }; + 9F8CA15700B6BA365624AA7C /* AppRoutesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRoutesTests.swift; sourceTree = ""; }; A00C7A331B72C0F05C00392F /* RoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModelProtocol.swift; sourceTree = ""; }; A05707BF550D770168A406DB /* LoginViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModelTests.swift; sourceTree = ""; }; A057F2FDC14866C3026A89A4 /* NotificationManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManagerProtocol.swift; sourceTree = ""; }; @@ -1416,7 +1420,6 @@ C796FC1DFDBCDD5573D0360F /* WaitlistScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenViewModelTests.swift; sourceTree = ""; }; C830A64609CBD152F06E0457 /* NotificationConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationConstants.swift; sourceTree = ""; }; C833673B334A0651AB46F30B /* StaticLocationScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLocationScreenViewModelTests.swift; sourceTree = ""; }; - C843CF833BF6485B64AC87E1 /* AppRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouter.swift; sourceTree = ""; }; C8F2A7A4E3F5060F52ACFFB0 /* RedactedRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedactedRoomTimelineView.swift; sourceTree = ""; }; C936FDD017808FE416742D64 /* PollRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollRoomTimelineView.swift; sourceTree = ""; }; C97F8963B14EB0AF3940DDBF /* NotificationSettingsEditScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenRoomCell.swift; sourceTree = ""; }; @@ -1649,10 +1652,9 @@ EAC6FE2CD4F50A43068ADCD8 /* SwiftState in Frameworks */, 754602A7B2AAD443C4228ED4 /* GZIP in Frameworks */, B0CB16349B96262AA65A04AF /* Sentry in Frameworks */, - 36AD4DD4C798E22584ED3200 /* URLRouting in Frameworks */, - 36CD6E11B37396E14F032CB6 /* Version in Frameworks */, - A0D7E5BD0298A97DCBDCE40B /* Emojibase in Frameworks */, - 44F0E1B576C7599DF8022071 /* WysiwygComposer in Frameworks */, + 36AD4DD4C798E22584ED3200 /* Version in Frameworks */, + 36CD6E11B37396E14F032CB6 /* Emojibase in Frameworks */, + A0D7E5BD0298A97DCBDCE40B /* WysiwygComposer in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2572,6 +2574,7 @@ children = ( 58C2527813FDAE23E72A9063 /* AnalyticsSettingsScreenViewModelTests.swift */, C687844F60BFF532D49A994C /* AnalyticsTests.swift */, + 9F8CA15700B6BA365624AA7C /* AppRoutesTests.swift */, 893777A4997BBDB68079D4F5 /* ArrayTests.swift */, AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */, 37CA26F55123E36B50DB0B3A /* AttributedStringTests.swift */, @@ -2678,7 +2681,7 @@ 780F74C73E826685A9DB289B /* Navigation */ = { isa = PBXGroup; children = ( - C843CF833BF6485B64AC87E1 /* AppRouter.swift */, + 27A9E3FBE8A66B5A17AD7F74 /* AppRoutes.swift */, B8F28602AC7AC881AED37EBA /* NavigationCoordinators.swift */, 9A22A05E472533ED3C5A31B3 /* NavigationModule.swift */, CA28F29C9F93E93CC3C2C715 /* NavigationRootCoordinator.swift */, @@ -3139,6 +3142,7 @@ A448A3A8F764174C60CD0CA1 /* Other */ = { isa = PBXGroup; children = ( + 514923AA9640C34F39E0500A /* GenericCallLinkCoordinator.swift */, BF34A2FD6797535C95AC918D /* PlaceholderScreenCoordinator.swift */, 854BCEAF2A832176FAACD2CB /* SplashScreenCoordinator.swift */, ); @@ -3932,7 +3936,6 @@ 9573B94B1C86C6DF751AF3FD /* SwiftState */, 997C7385E1A07E061D7E2100 /* GZIP */, 7731767AE437BA3BD2CC14A8 /* Sentry */, - E9BAB8A793FE3B54CDD47102 /* URLRouting */, A05AF81DDD14AD58CB0E1B9B /* Version */, C05729B1684C331F5FFE9232 /* Emojibase */, CA07D57389DACE18AEB6A5E2 /* WysiwygComposer */, @@ -4066,7 +4069,6 @@ E9C4F3A12AA1F65C13A8C8EB /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */, 6582B5AF3F104B0F7E031E7D /* XCRemoteSwiftPackageReference "SwiftState" */, 9A472EE0218FE7DCF5283429 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */, - 0020F10A9DA1895036A72013 /* XCRemoteSwiftPackageReference "swift-url-routing" */, EC6D0C817B1C21D9D096505A /* XCRemoteSwiftPackageReference "Version" */, 945C99D66CE013061F6A3C71 /* XCRemoteSwiftPackageReference "matrix-wysiwyg-composer-swift" */, ); @@ -4287,6 +4289,7 @@ files = ( A9A5801D5EE3D4D91F6DDADB /* AnalyticsSettingsScreenViewModelTests.swift in Sources */, 890F0D453FE388756479AC97 /* AnalyticsTests.swift in Sources */, + 296B988AB2280207D558C5BF /* AppRoutesTests.swift in Sources */, 3EC698F80DDEEFA273857841 /* ArrayTests.swift in Sources */, 90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */, 5100F53E6884A15F9BA07CC3 /* AttributedStringTests.swift in Sources */, @@ -4411,7 +4414,7 @@ A021827B528F1EDC9101CA58 /* AppCoordinatorProtocol.swift in Sources */, 4FF90E2242DBD596E1ED2E27 /* AppCoordinatorStateMachine.swift in Sources */, 9D9690D2FD4CD26FF670620F /* AppDelegate.swift in Sources */, - FF149F0A3550A54C50ECBE7A /* AppRouter.swift in Sources */, + 355B11D08CE0CEF97A813236 /* AppRoutes.swift in Sources */, 12CCA59536EDD99A3272CF77 /* AppSettings.swift in Sources */, 9462C62798F47E39DCC182D2 /* Application.swift in Sources */, 74604ACFDBE7F54260E7B617 /* ApplicationProtocol.swift in Sources */, @@ -4525,6 +4528,7 @@ 85AFBB433AD56704A880F8A0 /* FramePreferenceKey.swift in Sources */, 46BA7F4B4D3A7164DED44B88 /* FullscreenDialog.swift in Sources */, F18CA61A58C77C84F551B8E7 /* GeneratedMocks.swift in Sources */, + B53D292A5CA61E371C4CD785 /* GenericCallLinkCoordinator.swift in Sources */, 4295E5F850897710A51AE114 /* GeoURI.swift in Sources */, 964B9D2EC38C488C360CE0C9 /* HomeScreen.swift in Sources */, 8CC12086CBF91A7E10CDC205 /* HomeScreenCoordinator.swift in Sources */, @@ -5526,14 +5530,6 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 0020F10A9DA1895036A72013 /* XCRemoteSwiftPackageReference "swift-url-routing" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/pointfreeco/swift-url-routing"; - requirement = { - kind = upToNextMinorVersion; - minimumVersion = 0.5.0; - }; - }; 0CBF57301AA172C21F76CE86 /* XCRemoteSwiftPackageReference "maplibre-gl-native-distribution" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/maplibre/maplibre-gl-native-distribution"; @@ -5953,11 +5949,6 @@ package = D283517192CAC3E2E6920765 /* XCRemoteSwiftPackageReference "Kingfisher" */; productName = Kingfisher; }; - E9BAB8A793FE3B54CDD47102 /* URLRouting */ = { - isa = XCSwiftPackageProductDependency; - package = 0020F10A9DA1895036A72013 /* XCRemoteSwiftPackageReference "swift-url-routing" */; - productName = URLRouting; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = AC22997D58D612146053154D /* Project object */; diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index cc1b24424f..d24b99c069 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -178,15 +178,6 @@ "version" : "1.0.0" } }, - { - "identity" : "swift-case-paths", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-case-paths", - "state" : { - "revision" : "fc45e7b2cfece9dd80b5a45e6469ffe67fe67984", - "version" : "0.14.1" - } - }, { "identity" : "swift-collections", "kind" : "remoteSourceControl", @@ -205,15 +196,6 @@ "version" : "1.0.2" } }, - { - "identity" : "swift-parsing", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-parsing", - "state" : { - "revision" : "27c941bbd22a4bbc53005a15a0440443fd892f70", - "version" : "0.12.1" - } - }, { "identity" : "swift-snapshot-testing", "kind" : "remoteSourceControl", @@ -223,15 +205,6 @@ "version" : "1.11.1" } }, - { - "identity" : "swift-url-routing", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-url-routing", - "state" : { - "revision" : "2f4f0404b3de0a0711feb7190f724d8a80bc1cfd", - "version" : "0.5.0" - } - }, { "identity" : "swiftstate", "kind" : "remoteSourceControl", @@ -258,15 +231,6 @@ "revision" : "1fe824b80d89201652e7eca7c9252269a1d85e25", "version" : "2.0.1" } - }, - { - "identity" : "xctest-dynamic-overlay", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", - "state" : { - "revision" : "50843cbb8551db836adec2290bb4bc6bac5c1865", - "version" : "0.9.0" - } } ], "version" : 2 diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index cfbc6ad1ff..bc354544b0 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -138,6 +138,21 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate, func handleUniversalLink(_ url: URL) { // Parse into an AppRoute to redirect these in a type safe way. + if let route = AppRouteURLParser.route(from: url) { + switch route { + case .genericCallLink(let url): + if let userSessionFlowCoordinator { + userSessionFlowCoordinator.handleAppRoute(route, animated: true) + } else { + navigationRootCoordinator.setSheetCoordinator(GenericCallLinkCoordinator(parameters: .init(url: url))) + } + default: + break + } + + return + } + // Until we have an OIDC callback AppRoute, handle it manually. if url.absoluteString.starts(with: appSettings.oidcRedirectURL.absoluteString) { MXLog.error("OIDC callback through Universal Links not implemented.") diff --git a/ElementX/Sources/Application/Navigation/AppRouter.swift b/ElementX/Sources/Application/Navigation/AppRouter.swift deleted file mode 100644 index 1b4a5ac6f6..0000000000 --- a/ElementX/Sources/Application/Navigation/AppRouter.swift +++ /dev/null @@ -1,58 +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 Foundation - -import URLRouting - -enum AppRoute { - case roomList - case room(roomID: String) - case roomDetails(roomID: String) - case invites -} - -struct AppRouterManager { - private let deeplinkRouter = OneOf { - Route(.case(AppRoute.room(roomID:))) { - // Check with product if this is the expect path - Path { "room" } - Query { - Field("id") { Parse(.string) } - } - } - } - - private let permalinkRouter = OneOf { - Route(.case(AppRoute.room(roomID:))) { - Host("matrix.to") - Path { - "#" - Parse(.string) - } - } - } - - func route(from url: URL) -> AppRoute? { - var route: AppRoute? - if let deeplinkRoute = try? deeplinkRouter.match(url: url) { - route = deeplinkRoute - } else if let permalinkRoute = try? permalinkRouter.match(url: url) { - route = permalinkRoute - } - return route - } -} diff --git a/ElementX/Sources/Application/Navigation/AppRoutes.swift b/ElementX/Sources/Application/Navigation/AppRoutes.swift new file mode 100644 index 0000000000..4093e5e0b8 --- /dev/null +++ b/ElementX/Sources/Application/Navigation/AppRoutes.swift @@ -0,0 +1,55 @@ +// +// 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 Foundation + +enum AppRoute: Equatable { + case roomList + case room(roomID: String) + case roomDetails(roomID: String) + case invites + case genericCallLink(url: URL) +} + +enum AppRouteURLParser { + private enum KnownHosts: String, CaseIterable { + case elementIo = "element.io" + case appElementIo = "app.element.io" + case stagingElementIo = "staging.element.io" + case developElementIo = "develop.element.io" + case mobileElementIo = "mobile.element.io" + case callElementIo = "call.element.io" + } + + static func route(from url: URL) -> AppRoute? { + guard let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), + let host = urlComponents.host else { + MXLog.error("Failed parsing URL: \(url)") + return nil + } + + guard KnownHosts.allCases.map(\.rawValue).contains(host) else { + return .genericCallLink(url: url) + } + + if host == KnownHosts.callElementIo.rawValue { + return .genericCallLink(url: url) + } + + // Deep linking not supported at the moment + return nil + } +} diff --git a/ElementX/Sources/Application/Navigation/NavigationRootCoordinator.swift b/ElementX/Sources/Application/Navigation/NavigationRootCoordinator.swift index 7bcae3af8f..0d074c2bbd 100644 --- a/ElementX/Sources/Application/Navigation/NavigationRootCoordinator.swift +++ b/ElementX/Sources/Application/Navigation/NavigationRootCoordinator.swift @@ -35,6 +35,26 @@ class NavigationRootCoordinator: ObservableObject, CoordinatorProtocol, CustomSt rootModule?.coordinator } + @Published fileprivate var sheetModule: NavigationModule? { + didSet { + if let oldValue { + logPresentationChange("Remove sheet", oldValue) + oldValue.tearDown() + } + + if let sheetModule { + logPresentationChange("Set sheet", sheetModule) + sheetModule.coordinator?.start() + } + } + } + + // The currently presented sheet coordinator + // Sheets will be presented through the NavigationSplitCoordinator if provided + var sheetCoordinator: (any CoordinatorProtocol)? { + sheetModule?.coordinator + } + /// Sets or replaces the presented coordinator /// - Parameter coordinator: the coordinator to display func setRootCoordinator(_ coordinator: (any CoordinatorProtocol)?, dismissalCallback: (() -> Void)? = nil) { @@ -45,6 +65,25 @@ class NavigationRootCoordinator: ObservableObject, CoordinatorProtocol, CustomSt rootModule = NavigationModule(coordinator, dismissalCallback: dismissalCallback) } + + /// - dismissalCallback: called when the sheet has been dismissed, programatically or otherwise + func setSheetCoordinator(_ coordinator: (any CoordinatorProtocol)?, animated: Bool = true, dismissalCallback: (() -> Void)? = nil) { + guard let coordinator else { + sheetModule = nil + return + } + + if sheetModule?.coordinator === coordinator { + fatalError("Cannot use the same coordinator more than once") + } + + var transaction = Transaction() + transaction.disablesAnimations = !animated + + withTransaction(transaction) { + sheetModule = NavigationModule(coordinator, dismissalCallback: dismissalCallback) + } + } // MARK: - CoordinatorProtocol @@ -79,5 +118,8 @@ private struct NavigationRootCoordinatorView: View { rootCoordinator.rootModule?.coordinator?.toPresentable() } .animation(.elementDefault, value: rootCoordinator.rootModule) + .sheet(item: $rootCoordinator.sheetModule) { module in + module.coordinator?.toPresentable() + } } } diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index bc30116afe..59971dc716 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -83,6 +83,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { stateMachine.tryEvent(.dismissRoom, userInfo: EventUserInfo(animated: animated)) case .invites: break + case .genericCallLink: + break } } diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index 19fb2111a4..89d68a7a69 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -105,27 +105,22 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { } // MARK: - FlowCoordinatorProtocol - + func handleAppRoute(_ appRoute: AppRoute, animated: Bool) { - // Tidy up any state before applying the new route. - switch stateMachine.state { - case .initial, .migration: - return // Not ready to handle a route. - case .roomList: - break // Nothing to tidy up on the home screen. - case .feedbackScreen, .sessionVerificationScreen, .settingsScreen, .startChatScreen, .invitesScreen, .welcomeScreen: - navigationSplitCoordinator.setSheetCoordinator(nil, animated: animated) - } - - // Apply the route. - switch appRoute { - case .room, .roomDetails, .roomList: - roomFlowCoordinator.handleAppRoute(appRoute, animated: animated) - case .invites: - if UIDevice.current.isPhone { - roomFlowCoordinator.clearRoute(animated: animated) + clearPresentedSheets(animated: animated) { [weak self] in + guard let self else { return } + + switch appRoute { + case .room, .roomDetails, .roomList: + self.roomFlowCoordinator.handleAppRoute(appRoute, animated: animated) + case .invites: + if UIDevice.current.isPhone { + self.roomFlowCoordinator.clearRoute(animated: animated) + } + self.stateMachine.processEvent(.showInvitesScreen, userInfo: .init(animated: animated)) + case .genericCallLink(let url): + self.navigationSplitCoordinator.setSheetCoordinator(GenericCallLinkCoordinator(parameters: .init(url: url)), animated: animated) } - stateMachine.processEvent(.showInvitesScreen, userInfo: .init(animated: animated)) } } @@ -135,6 +130,20 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { // MARK: - Private + private func clearPresentedSheets(animated: Bool, completion: @escaping () -> Void) { + if navigationSplitCoordinator.sheetCoordinator == nil { + completion() + return + } + + navigationSplitCoordinator.setSheetCoordinator(nil, animated: animated) + + // Prevents system crashes when presenting a sheet if another one was already shown + DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { + completion() + } + } + private func setupStateMachine() { stateMachine.addTransitionHandler { [weak self] context in guard let self else { return } diff --git a/ElementX/Sources/Other/CollapsibleFlowLayout/CollapsibleReactionLayout.swift b/ElementX/Sources/Other/CollapsibleFlowLayout/CollapsibleReactionLayout.swift index 641402a03b..856601a6e0 100644 --- a/ElementX/Sources/Other/CollapsibleFlowLayout/CollapsibleReactionLayout.swift +++ b/ElementX/Sources/Other/CollapsibleFlowLayout/CollapsibleReactionLayout.swift @@ -198,7 +198,7 @@ struct CollapsibleReactionLayout: Layout { } var secondLastRow = rows[rows.count - 2] let collapseButton = secondLastRow.removeLast() - lastRow.prepend(collapseButton) + lastRow.insert(collapseButton, at: 0) rows[rows.count - 2] = secondLastRow rows[rows.count - 1] = lastRow } diff --git a/ElementX/Sources/Screens/Other/GenericCallLinkCoordinator.swift b/ElementX/Sources/Screens/Other/GenericCallLinkCoordinator.swift new file mode 100644 index 0000000000..cd0b3e0a89 --- /dev/null +++ b/ElementX/Sources/Screens/Other/GenericCallLinkCoordinator.swift @@ -0,0 +1,101 @@ +// +// 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 GenericLinkCoordinatorParameters { + let url: URL +} + +class GenericCallLinkCoordinator: CoordinatorProtocol { + private let parameters: GenericLinkCoordinatorParameters + + init(parameters: GenericLinkCoordinatorParameters) { + self.parameters = parameters + } + + func toPresentable() -> AnyView { + AnyView( + WebView(url: 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(initialURL: url) + } + + func updateUIView(_ webView: WKWebView, context: Context) { + webView.load(URLRequest(url: url)) + } + + @MainActor + class Coordinator: NSObject, WKUIDelegate, WKNavigationDelegate { + private let initialURL: URL + private(set) var webView: WKWebView! + + init(initialURL: URL) { + self.initialURL = initialURL + 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 == initialURL.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 == initialURL.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/SupportingFiles/ElementX.entitlements b/ElementX/SupportingFiles/ElementX.entitlements index 6fa8aae892..892e31c515 100644 --- a/ElementX/SupportingFiles/ElementX.entitlements +++ b/ElementX/SupportingFiles/ElementX.entitlements @@ -11,6 +11,7 @@ applinks:staging.element.io applinks:develop.element.io applinks:mobile.element.io + applinks:call.element.io webcredentials:*.element.io com.apple.developer.usernotifications.communication diff --git a/ElementX/SupportingFiles/Info.plist b/ElementX/SupportingFiles/Info.plist index 9af4795e3d..b3c3819fe2 100644 --- a/ElementX/SupportingFiles/Info.plist +++ b/ElementX/SupportingFiles/Info.plist @@ -56,6 +56,8 @@ UIBackgroundModes fetch + audio + voip UILaunchScreen diff --git a/ElementX/SupportingFiles/target.yml b/ElementX/SupportingFiles/target.yml index efecddbae6..c6080821de 100644 --- a/ElementX/SupportingFiles/target.yml +++ b/ElementX/SupportingFiles/target.yml @@ -75,7 +75,9 @@ targets: NSPhotoLibraryAddUsageDescription: Allows saving photos and videos to your library. NSLocationWhenInUseUsageDescription: When you share your location to people, $(APP_DISPLAY_NAME) needs access to show them a map. UIBackgroundModes: [ - fetch + fetch, + audio, + voip ] BGTaskSchedulerPermittedIdentifiers: [ io.element.elementx.background.refresh @@ -175,7 +177,6 @@ targets: - package: SwiftState - package: GZIP - package: Sentry - - package: URLRouting - package: Version - package: Emojibase - package: WysiwygComposer diff --git a/UnitTests/Sources/AppRoutesTests.swift b/UnitTests/Sources/AppRoutesTests.swift new file mode 100644 index 0000000000..c4a8790e48 --- /dev/null +++ b/UnitTests/Sources/AppRoutesTests.swift @@ -0,0 +1,30 @@ +// +// 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 XCTest + +@testable import ElementX + +class AppRouterTests: XCTestCase { + func testRoutes() { + guard let url = URL(string: "https://call.element.io/test") else { + XCTFail("URL invalid") + return + } + + XCTAssertEqual(AppRouteURLParser.route(from: url), AppRoute.genericCallLink(url: url)) + } +} diff --git a/project.yml b/project.yml index f8eaf6c47b..46e484490e 100644 --- a/project.yml +++ b/project.yml @@ -106,9 +106,6 @@ packages: SwiftUIIntrospect: url: https://github.com/siteline/SwiftUI-Introspect minorVersion: 0.9.0 - URLRouting: - url: https://github.com/pointfreeco/swift-url-routing - minorVersion: 0.5.0 Version: url: https://github.com/mxcl/Version minorVersion: 2.0.0