diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index e97a50460..e0ff42b75 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -578,7 +578,6 @@ 62C5876C4254C58C2086F0DE /* HomeScreenContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3B4B58B79A6FA250B24A1EC /* HomeScreenContent.swift */; }; 633400018E07D2DC7175B16E /* LiveLocationShareProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA12A7F5EF5C6D0B992869ED /* LiveLocationShareProxy.swift */; }; 633501761094E09DFBEBFFAD /* CopyTextButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B682FE2C44C5E163E7023B05 /* CopyTextButton.swift */; }; - 63780F9DA06573E38A471ECA /* GenericCallLinkWidgetDriver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C202C1C7E330F124981A31 /* GenericCallLinkWidgetDriver.swift */; }; 6386EA3C898AD1A4BC1DC8A5 /* TimelineMediaPreviewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD40B92FCF20165658296AD /* TimelineMediaPreviewModifier.swift */; }; 639A0A27383EC655B0E81E95 /* SpaceScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 459A3921046977CBF4F3C359 /* SpaceScreenViewModelProtocol.swift */; }; 63CDC201A5980F304F6D0A1C /* WaveformInteractionModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFEE91FB8ABB5F5884B6D940 /* WaveformInteractionModifier.swift */; }; @@ -1859,7 +1858,6 @@ 284FEEB0789B8894E52A7F34 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 287FC98AF2664EAD79C0D902 /* UIDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIDevice.swift; sourceTree = ""; }; 28C19F54A0C4FC9AB7ABD583 /* TextRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItemContent.swift; sourceTree = ""; }; - 28C202C1C7E330F124981A31 /* GenericCallLinkWidgetDriver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericCallLinkWidgetDriver.swift; sourceTree = ""; }; 28EA8BE9EEDBD17555141C7E /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = el; path = el.lproj/Localizable.stringsdict; sourceTree = ""; }; 2910422CB628D3B2BBE47449 /* SeparatorRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineView.swift; sourceTree = ""; }; 292EEE1F71DCC205C45728F7 /* ReportRoomScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportRoomScreenCoordinator.swift; sourceTree = ""; }; @@ -5531,7 +5529,6 @@ 6FC8B21E86B137BE4E91F82A /* ElementCallServiceProtocol.swift */, 309AD8BAE6437C31BA7157BF /* ElementCallWidgetDriver.swift */, A6C11AD9813045E44F950410 /* ElementCallWidgetDriverProtocol.swift */, - 28C202C1C7E330F124981A31 /* GenericCallLinkWidgetDriver.swift */, ); path = ElementCall; sourceTree = ""; @@ -8352,7 +8349,6 @@ 5705511EBE083295EF98F998 /* FrequentlyUsedEmoji.swift in Sources */, 46BA7F4B4D3A7164DED44B88 /* FullscreenDialog.swift in Sources */, F18CA61A58C77C84F551B8E7 /* GeneratedMocks.swift in Sources */, - 63780F9DA06573E38A471ECA /* GenericCallLinkWidgetDriver.swift in Sources */, 4295E5F850897710A51AE114 /* GeoURI.swift in Sources */, F0DACC95F24128A54CD537E4 /* GlobalSearchScreen.swift in Sources */, 9F11E743EA01482E78A438B0 /* GlobalSearchScreenCell.swift in Sources */, diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index 8ffa8b5b4..04f1629a1 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -274,12 +274,6 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg case .accountProvisioningLink: handleAppRoute(route, windowType: windowType) - case .genericCallLink(let url): - if let userSessionFlowCoordinator { - userSessionFlowCoordinator.handleAppRoute(route, animated: true) - } else { - presentCallScreen(genericCallLink: url) - } case .userProfile(let userID): if isExternalURL { handleAppRoute(route, @@ -878,35 +872,6 @@ 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, - allowPictureInPicture: false, - appSettings: appSettings, - appHooks: appHooks, - analytics: ServiceLocator.shared.analytics)) - - callScreenCoordinator.actions - .sink { [weak self] action in - guard let self else { return } - switch action { - case .pictureInPictureIsAvailable: - break - case .pictureInPictureStarted, .pictureInPictureStopped: - // Don't allow PiP when signed out - the user could login at which point we'd - // need to hand over the call from here to the user session flow coordinator. - MXLog.error("Picture in Picture not supported before login.") - case .dismiss: - navigationRootCoordinator.setOverlayCoordinator(nil) - } - } - .store(in: &cancellables) - - navigationRootCoordinator.setOverlayCoordinator(callScreenCoordinator, animated: false) - } private func configureNotificationManager() { notificationManager.setUserSession(userSession) diff --git a/ElementX/Sources/Application/Navigation/AppRoutes.swift b/ElementX/Sources/Application/Navigation/AppRoutes.swift index 32d8743cf..b2e11985c 100644 --- a/ElementX/Sources/Application/Navigation/AppRoutes.swift +++ b/ElementX/Sources/Application/Navigation/AppRoutes.swift @@ -41,8 +41,6 @@ enum AppRoute: Hashable { case userProfile(userID: String) /// An Element Call running in a particular room case call(roomID: String, isVoiceCall: Bool) - /// An Element Call link generated outside of a chat room. - case genericCallLink(url: URL) /// The settings screen. case settings /// The setting screen for key backup. @@ -84,8 +82,7 @@ struct AppRouteURLParser { AppGroupURLParser(), MatrixPermalinkParser(), ElementWebURLParser(domains: appSettings.elementWebHosts), - AccountProvisioningURLParser(domain: appSettings.accountProvisioningHost), - ElementCallURLParser() + AccountProvisioningURLParser(domain: appSettings.accountProvisioningHost) ] } @@ -135,45 +132,6 @@ private struct AppGroupURLParser: URLParser { } } -/// The parser for Element Call links. This always returns a `.genericCallLink`. -private struct ElementCallURLParser: URLParser { - private let knownHosts = ["call.element.io"] - private let customSchemeURLQueryParameterName = "url" - - func route(from url: URL) -> AppRoute? { - // Element Call not supported, WebRTC not available - // https://github.com/element-hq/element-x-ios/issues/1794 - if ProcessInfo.processInfo.isiOSAppOnMac { - return nil - } - - // First try processing URLs with custom schemes - if let scheme = url.scheme, - scheme == InfoPlistReader.app.elementCallScheme { - guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { - return nil - } - - guard let encodedURLString = components.queryItems?.first(where: { $0.name == customSchemeURLQueryParameterName })?.value, - let callURL = URL(string: encodedURLString), - callURL.scheme == "https" // Don't allow URLs from potentially unsafe domains - else { - MXLog.error("Invalid custom scheme call parameters: \(url)") - return nil - } - - return .genericCallLink(url: callURL) - } - - // Otherwise try to interpret it as an universal link - guard let host = url.host, knownHosts.contains(host) else { - return nil - } - - return .genericCallLink(url: url) - } -} - private struct MatrixPermalinkParser: URLParser { func route(from url: URL) -> AppRoute? { guard let entity = parseMatrixEntityFrom(uri: url.absoluteString) else { return nil } diff --git a/ElementX/Sources/Application/Navigation/NavigationRootCoordinator.swift b/ElementX/Sources/Application/Navigation/NavigationRootCoordinator.swift index 8456ec3b3..cdafb4a27 100644 --- a/ElementX/Sources/Application/Navigation/NavigationRootCoordinator.swift +++ b/ElementX/Sources/Application/Navigation/NavigationRootCoordinator.swift @@ -48,25 +48,6 @@ import SwiftUI sheetModule?.coordinator } - 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 - } - /// The lowest-level `AlertInfo`, directly available to the root of the app. var alertInfo: AlertInfo? @@ -104,31 +85,6 @@ import SwiftUI 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 @@ -167,15 +123,5 @@ private struct NavigationRootCoordinatorView: View { .sheet(item: $rootCoordinator.sheetModule) { module in module.coordinator?.toPresentable() } - .accessibilityHidden(rootCoordinator.overlayModule?.coordinator != nil) - .overlay { - Group { - if let coordinator = rootCoordinator.overlayModule?.coordinator { - coordinator.toPresentable() - .transition(.opacity) - } - } - .animation(.elementDefault, value: rootCoordinator.overlayModule) - } } } diff --git a/ElementX/Sources/FlowCoordinators/ChatsTabFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/ChatsTabFlowCoordinator.swift index 7928e0e0b..b09d8c6da 100644 --- a/ElementX/Sources/FlowCoordinators/ChatsTabFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/ChatsTabFlowCoordinator.swift @@ -172,7 +172,7 @@ class ChatsTabFlowCoordinator: FlowCoordinatorProtocol { } case .globalSearch: presentGlobalSearch() - case .accountProvisioningLink, .settings, .chatBackupSettings, .call, .genericCallLink: + case .accountProvisioningLink, .settings, .chatBackupSettings, .call: break // These routes cannot be handled. } } diff --git a/ElementX/Sources/FlowCoordinators/EncryptionSettingsFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/EncryptionSettingsFlowCoordinator.swift index ec2b47f96..bb93b4a25 100644 --- a/ElementX/Sources/FlowCoordinators/EncryptionSettingsFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/EncryptionSettingsFlowCoordinator.swift @@ -83,7 +83,7 @@ class EncryptionSettingsFlowCoordinator: FlowCoordinatorProtocol { case .roomList, .room, .roomAlias, .childRoom, .childRoomAlias, .roomDetails, .roomMemberDetails, .userProfile, .thread, .event, .eventOnRoomAlias, .childEvent, .childEventOnRoomAlias, - .call, .genericCallLink, .settings, .share, .transferOwnership, + .call, .settings, .share, .transferOwnership, .globalSearch: // These routes aren't in this flow so clear the entire stack. clearRoute(animated: animated) diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index 8d3a08dd2..c7226cd88 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -199,7 +199,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { } case .roomAlias, .childRoomAlias, .eventOnRoomAlias, .childEventOnRoomAlias: break // These are converted to a room ID route one level above. - case .accountProvisioningLink, .roomList, .userProfile, .call, .genericCallLink, .settings, .chatBackupSettings, .globalSearch: + case .accountProvisioningLink, .roomList, .userProfile, .call, .settings, .chatBackupSettings, .globalSearch: break // These routes can't be handled. case .transferOwnership(let roomID): guard self.roomID == roomID else { fatalError("Navigation route doesn't belong to this room flow.") } diff --git a/ElementX/Sources/FlowCoordinators/RoomMembersFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomMembersFlowCoordinator.swift index fef82b955..0e8400685 100644 --- a/ElementX/Sources/FlowCoordinators/RoomMembersFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomMembersFlowCoordinator.swift @@ -119,7 +119,7 @@ final class RoomMembersFlowCoordinator: FlowCoordinatorProtocol { case .roomAlias, .childRoomAlias, .eventOnRoomAlias, .childEventOnRoomAlias: break // These are converted to a room ID route one level above. case .accountProvisioningLink, .roomList, .room, .roomDetails, .event, - .userProfile, .call, .genericCallLink, .settings, .chatBackupSettings, + .userProfile, .call, .settings, .chatBackupSettings, .share, .transferOwnership, .thread, .globalSearch: break } diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index 37eabbceb..25cc3479a 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -132,8 +132,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { } case .call(let roomID, let isVoiceCall): Task { await presentCallScreen(roomID: roomID, isVoiceCall: isVoiceCall) } - case .genericCallLink(let url): - presentCallScreen(genericCallLink: url) case .roomList, .room, .roomAlias, .childRoom, .childRoomAlias, .roomDetails, .roomMemberDetails, .userProfile, .event, .eventOnRoomAlias, .childEvent, .childEventOnRoomAlias, @@ -401,10 +399,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { // MARK: - Calls - private func presentCallScreen(genericCallLink url: URL) { - presentCallScreen(configuration: .init(genericCallLink: url)) - } - private func presentCallScreen(roomID: String, isVoiceCall: Bool) async { guard case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(roomID) else { return diff --git a/ElementX/Sources/Screens/CallScreen/CallScreenModels.swift b/ElementX/Sources/Screens/CallScreen/CallScreenModels.swift index 509207ac1..be14267ec 100644 --- a/ElementX/Sources/Screens/CallScreen/CallScreenModels.swift +++ b/ElementX/Sources/Screens/CallScreen/CallScreenModels.swift @@ -19,7 +19,6 @@ enum CallScreenViewModelAction { struct CallScreenViewState: BindableState { let script: String? var url: URL? - let isGenericCallLink: Bool let certificateValidator: CertificateValidatorHookProtocol diff --git a/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift b/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift index 19bfbdb39..ba38a380a 100644 --- a/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift +++ b/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift @@ -48,18 +48,10 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol self.analyticsService = analyticsService isPictureInPictureAllowed = allowPictureInPicture - var isGenericCallLink = false - switch configuration.kind { - case .genericCallLink(let url): - widgetDriver = GenericCallLinkWidgetDriver(url: url) - isGenericCallLink = true - case .roomCall(let roomProxy, let clientProxy, _, _, _, _, _): - guard let deviceID = clientProxy.deviceID else { fatalError("Missing device ID for the call.") } - widgetDriver = roomProxy.elementCallWidgetDriver(deviceID: deviceID) - } + guard let deviceID = configuration.clientProxy.deviceID else { fatalError("Missing device ID for the call.") } + widgetDriver = configuration.roomProxy.elementCallWidgetDriver(deviceID: deviceID) super.init(initialViewState: CallScreenViewState(script: CallScreenJavaScriptMessageName.allCasesInjectionScript, - isGenericCallLink: isGenericCallLink, certificateValidator: appHooks.certificateValidatorHook)) elementCallService.actions @@ -162,67 +154,60 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol } 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. + Task { [weak self] in + guard let self else { return } - case .roomCall(let roomProxy, _, let clientID, let voiceOnly, let elementCallBaseURL, let elementCallBaseURLOverride, let colorScheme): - Task { [weak self] in - guard let self else { return } - - let baseURL = if let elementCallBaseURLOverride { - elementCallBaseURLOverride - } else { - elementCallBaseURL - } - - // We only set the analytics configuration if analytics are enabled - let analyticsConfiguration: ElementCallAnalyticsConfiguration? = if analyticsService.isEnabled { - .init(posthogAPIHost: appSettings.elementCallPosthogAPIHost, - posthogAPIKey: appSettings.elementCallPosthogAPIKey, - sentryDSN: appSettings.elementCallPosthogSentryDSN) - } else { - nil - } - let rageshakeURL: String? = if case let .url(baseURL) = appSettings.bugReportRageshakeURL.publisher.value { - baseURL.absoluteString - } else { - nil - } - - switch await widgetDriver.start(baseURL: baseURL, - clientID: clientID, - colorScheme: colorScheme, - voiceOnly: voiceOnly, - rageshakeURL: rageshakeURL, - analyticsConfiguration: analyticsConfiguration) { - 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.infoPublisher.value.displayName ?? roomProxy.id) + let baseURL = if let baseURLOverride = configuration.elementCallBaseURLOverride { + baseURLOverride + } else { + configuration.elementCallBaseURL } - timeoutTask = Task { [weak self] in - try? await Task.sleep(for: .seconds(10)) - guard !Task.isCancelled, let self else { return } - MXLog.error("Failed to join Element Call: Timeout") + // We only set the analytics configuration if analytics are enabled + let analyticsConfiguration: ElementCallAnalyticsConfiguration? = if analyticsService.isEnabled { + .init(posthogAPIHost: appSettings.elementCallPosthogAPIHost, + posthogAPIKey: appSettings.elementCallPosthogAPIKey, + sentryDSN: appSettings.elementCallPosthogSentryDSN) + } else { + nil + } + let rageshakeURL: String? = if case let .url(baseURL) = appSettings.bugReportRageshakeURL.publisher.value { + baseURL.absoluteString + } else { + nil + } + + switch await widgetDriver.start(baseURL: baseURL, + clientID: configuration.clientID, + colorScheme: configuration.colorScheme, + voiceOnly: configuration.voiceOnly, + rageshakeURL: rageshakeURL, + analyticsConfiguration: analyticsConfiguration) { + 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.commonError, - message: L10n.errorUnknown, - primaryButton: .init(title: L10n.actionDismiss) { [weak self] in self?.actionsSubject.send(.dismiss) }) - timeoutTask = nil + title: L10n.errorUnknown, + primaryButton: .init(title: L10n.actionOk) { + self.actionsSubject.send(.dismiss) + }) + return } + + await elementCallService.setupCallSession(roomID: configuration.roomProxy.id, + roomDisplayName: configuration.roomProxy.infoPublisher.value.displayName ?? configuration.roomProxy.id) + } + + timeoutTask = Task { [weak self] in + try? await Task.sleep(for: .seconds(10)) + guard !Task.isCancelled, let self else { return } + MXLog.error("Failed to join Element Call: Timeout") + state.bindings.alertInfo = .init(id: UUID(), + title: L10n.commonError, + message: L10n.errorUnknown, + primaryButton: .init(title: L10n.actionDismiss) { [weak self] in self?.actionsSubject.send(.dismiss) }) + timeoutTask = nil } } diff --git a/ElementX/Sources/Screens/CallScreen/View/CallScreen.swift b/ElementX/Sources/Screens/CallScreen/View/CallScreen.swift index a7222a76b..19f09dd27 100644 --- a/ElementX/Sources/Screens/CallScreen/View/CallScreen.swift +++ b/ElementX/Sources/Screens/CallScreen/View/CallScreen.swift @@ -22,11 +22,10 @@ struct CallScreen: View { .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color.compound.bgCanvasDefault.ignoresSafeArea()) .navigationBarTitleDisplayMode(.inline) - .toolbar(context.viewState.isGenericCallLink ? .visible : .hidden, for: .navigationBar) + .toolbar(.hidden, for: .navigationBar) .toolbar { toolbar } } .alert(item: $context.alertInfo) - .preferredColorScheme(context.viewState.isGenericCallLink ? .dark : nil) } @ViewBuilder diff --git a/ElementX/Sources/Services/ElementCall/ElementCallConfiguration.swift b/ElementX/Sources/Services/ElementCall/ElementCallConfiguration.swift index a33cfdcd3..b6009ea85 100644 --- a/ElementX/Sources/Services/ElementCall/ElementCallConfiguration.swift +++ b/ElementX/Sources/Services/ElementCall/ElementCallConfiguration.swift @@ -8,76 +8,18 @@ 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: JoinedRoomProxyProtocol, - clientProxy: ClientProxyProtocol, - clientID: String, - voiceOnly: Bool, - 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: JoinedRoomProxyProtocol, - clientProxy: ClientProxyProtocol, - clientID: String, - elementCallBaseURL: URL, - elementCallBaseURLOverride: URL?, - voiceOnly: Bool, - colorScheme: ColorScheme) { - kind = .roomCall(roomProxy: roomProxy, - clientProxy: clientProxy, - clientID: clientID, - voiceOnly: voiceOnly, - elementCallBaseURL: elementCallBaseURL, - elementCallBaseURLOverride: elementCallBaseURLOverride, - colorScheme: colorScheme) - } + let roomProxy: JoinedRoomProxyProtocol + let clientProxy: ClientProxyProtocol + let clientID: String + let elementCallBaseURL: URL + let elementCallBaseURLOverride: URL? + let voiceOnly: Bool + let colorScheme: ColorScheme /// A string representing the call being configured. var callRoomID: String { - switch kind { - case .genericCallLink(let url): - url.absoluteString - case .roomCall(let roomProxy, _, _, _, _, _, _): - roomProxy.id - } + roomProxy.id } } diff --git a/ElementX/Sources/Services/ElementCall/GenericCallLinkWidgetDriver.swift b/ElementX/Sources/Services/ElementCall/GenericCallLinkWidgetDriver.swift deleted file mode 100644 index 52a65aaf0..000000000 --- a/ElementX/Sources/Services/ElementCall/GenericCallLinkWidgetDriver.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright 2025 Element Creations Ltd. -// Copyright 2024-2025 New Vector Ltd. -// -// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. -// Please see LICENSE files in the repository root for full details. -// - -import Combine -import SwiftUI - -class GenericCallLinkWidgetDriver: ElementCallWidgetDriverProtocol { - private let url: URL - - let widgetID = UUID().uuidString - let messagePublisher = PassthroughSubject() - - private let actionsSubject: PassthroughSubject = .init() - var actions: AnyPublisher { - actionsSubject.eraseToAnyPublisher() - } - - init(url: URL) { - self.url = url - } - - func start(baseURL: URL, - clientID: String, - colorScheme: ColorScheme, - voiceOnly: Bool, - rageshakeURL: String?, - analyticsConfiguration: ElementCallAnalyticsConfiguration?) async -> Result { - MXLog.error("Nothing to start, use the configuration's URL directly instead.") - return .success(url) - } - - func handleMessage(_ message: String) async -> Result { - // The web view doesn't send us messages through the Widget API, so nothing to implement (yet?). - .failure(.driverNotSetup) - } -} diff --git a/ElementX/SupportingFiles/ElementX.entitlements b/ElementX/SupportingFiles/ElementX.entitlements index 6b0737e17..87136945b 100644 --- a/ElementX/SupportingFiles/ElementX.entitlements +++ b/ElementX/SupportingFiles/ElementX.entitlements @@ -11,8 +11,6 @@ applinks:staging.element.io applinks:develop.element.io applinks:mobile.element.io - applinks:call.element.io - applinks:call.element.dev applinks:matrix.to webcredentials:*.element.io diff --git a/ElementX/SupportingFiles/Info.plist b/ElementX/SupportingFiles/Info.plist index 18bf29057..81247db2a 100644 --- a/ElementX/SupportingFiles/Info.plist +++ b/ElementX/SupportingFiles/Info.plist @@ -37,16 +37,6 @@ $(MARKETING_VERSION) CFBundleURLTypes - - CFBundleTypeRole - Editor - CFBundleURLName - Element Call - CFBundleURLSchemes - - io.element.call - - CFBundleTypeRole Editor diff --git a/ElementX/SupportingFiles/target.yml b/ElementX/SupportingFiles/target.yml index 6f0abbf71..02ac35e77 100644 --- a/ElementX/SupportingFiles/target.yml +++ b/ElementX/SupportingFiles/target.yml @@ -51,13 +51,6 @@ targets: CFBundleShortVersionString: $(MARKETING_VERSION) CFBundleVersion: $(CURRENT_PROJECT_VERSION) CFBundleURLTypes: [ - { - CFBundleTypeRole: Editor, - CFBundleURLName: "Element Call", - CFBundleURLSchemes: [ - io.element.call - ] - }, { CFBundleTypeRole: Editor, CFBundleURLName: "Application", @@ -126,8 +119,6 @@ targets: - applinks:staging.element.io - applinks:develop.element.io - applinks:mobile.element.io - - applinks:call.element.io - - applinks:call.element.dev - applinks:matrix.to # - applinks:localhost?mode=developer # developer mode only works with https (but self-signed is fine). - webcredentials:*.element.io diff --git a/UnitTests/Sources/AppRouteURLParserTests.swift b/UnitTests/Sources/AppRouteURLParserTests.swift index 95cf3ac4f..f90a26de6 100644 --- a/UnitTests/Sources/AppRouteURLParserTests.swift +++ b/UnitTests/Sources/AppRouteURLParserTests.swift @@ -20,43 +20,6 @@ struct AppRouteURLParserTests { appRouteURLParser = AppRouteURLParser(appSettings: appSettings) } - @Test - func elementCallRoutes() throws { - let url = try #require(URL(string: "https://call.element.io/test")) - - #expect(appRouteURLParser.route(from: url) == AppRoute.genericCallLink(url: url)) - - let customSchemeURL = try #require(URL(string: "io.element.call:/?url=https%3A%2F%2Fcall.element.io%2Ftest")) - - #expect(appRouteURLParser.route(from: customSchemeURL) == AppRoute.genericCallLink(url: url)) - } - - @Test - func customDomainUniversalLinkCallRoutes() throws { - let url = try #require(URL(string: "https://somecustomdomain.element.io/test")) - - #expect(appRouteURLParser.route(from: url) == nil) - } - - @Test - func customSchemeLinkCallRoutes() throws { - let urlString = "https://somecustomdomain.element.io/test?param=123" - let url = try #require(URL(string: urlString)) - - let encodedURLString = try #require(urlString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)) - - let customSchemeURL = try #require(URL(string: "io.element.call:/?url=\(encodedURLString)")) - - #expect(appRouteURLParser.route(from: customSchemeURL) == AppRoute.genericCallLink(url: url)) - } - - @Test - func httpCustomSchemeLinkCallRoutes() throws { - let customSchemeURL = try #require(URL(string: "io.element.call:/?url=http%3A%2F%2Fcall.element.io%2Ftest")) - - #expect(appRouteURLParser.route(from: customSchemeURL) == nil) - } - @Test func matrixUserURL() throws { let userID = "@test:matrix.org" diff --git a/UnitTests/Sources/NavigationRootCoordinatorTests.swift b/UnitTests/Sources/NavigationRootCoordinatorTests.swift index 4f4360b8a..ea7bafe03 100644 --- a/UnitTests/Sources/NavigationRootCoordinatorTests.swift +++ b/UnitTests/Sources/NavigationRootCoordinatorTests.swift @@ -33,23 +33,6 @@ struct NavigationRootCoordinatorTests { assertCoordinatorsEqual(secondRootCoordinator, navigationRootCoordinator.rootCoordinator) } - @Test - func overlay() { - 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) - #expect(navigationRootCoordinator.overlayCoordinator == nil) - } - // MARK: - Dismissal Callbacks @Test @@ -67,19 +50,6 @@ struct NavigationRootCoordinatorTests { } } - @Test - func overlayDismissalCallback() async { - let overlayCoordinator = SomeTestCoordinator() - - await confirmation("Wait for callback") { confirm in - navigationRootCoordinator.setOverlayCoordinator(overlayCoordinator) { - confirm() - } - - navigationRootCoordinator.setOverlayCoordinator(nil) - } - } - // MARK: - Private private func assertCoordinatorsEqual(_ lhs: CoordinatorProtocol?, _ rhs: CoordinatorProtocol?) {