Remove support for handling SPA calls within the app. (#5515)
* Remove support for handling SPA call links. They were a stop-gap solution whilst we were building support for embedded room calling. * Simplify ElementCallConfiguration now that there is only 1 type of call to handle. * Remove the unused overlayModule from NavigationRoomCoordinator.
This commit is contained in:
@@ -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 = "<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>"; };
|
||||
292EEE1F71DCC205C45728F7 /* ReportRoomScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportRoomScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
@@ -5531,7 +5529,6 @@
|
||||
6FC8B21E86B137BE4E91F82A /* ElementCallServiceProtocol.swift */,
|
||||
309AD8BAE6437C31BA7157BF /* ElementCallWidgetDriver.swift */,
|
||||
A6C11AD9813045E44F950410 /* ElementCallWidgetDriverProtocol.swift */,
|
||||
28C202C1C7E330F124981A31 /* GenericCallLinkWidgetDriver.swift */,
|
||||
);
|
||||
path = ElementCall;
|
||||
sourceTree = "<group>";
|
||||
@@ -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 */,
|
||||
|
||||
@@ -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,
|
||||
@@ -879,35 +873,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)
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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<UUID>?
|
||||
|
||||
@@ -105,31 +86,6 @@ import SwiftUI
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.") }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -19,7 +19,6 @@ enum CallScreenViewModelAction {
|
||||
struct CallScreenViewState: BindableState {
|
||||
let script: String?
|
||||
var url: URL?
|
||||
let isGenericCallLink: Bool
|
||||
|
||||
let certificateValidator: CertificateValidatorHookProtocol
|
||||
|
||||
|
||||
@@ -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,19 +154,13 @@ 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.
|
||||
|
||||
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
|
||||
let baseURL = if let baseURLOverride = configuration.elementCallBaseURLOverride {
|
||||
baseURLOverride
|
||||
} else {
|
||||
elementCallBaseURL
|
||||
configuration.elementCallBaseURL
|
||||
}
|
||||
|
||||
// We only set the analytics configuration if analytics are enabled
|
||||
@@ -192,9 +178,9 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
|
||||
}
|
||||
|
||||
switch await widgetDriver.start(baseURL: baseURL,
|
||||
clientID: clientID,
|
||||
colorScheme: colorScheme,
|
||||
voiceOnly: voiceOnly,
|
||||
clientID: configuration.clientID,
|
||||
colorScheme: configuration.colorScheme,
|
||||
voiceOnly: configuration.voiceOnly,
|
||||
rageshakeURL: rageshakeURL,
|
||||
analyticsConfiguration: analyticsConfiguration) {
|
||||
case .success(let url):
|
||||
@@ -209,8 +195,8 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
|
||||
return
|
||||
}
|
||||
|
||||
await elementCallService.setupCallSession(roomID: roomProxy.id,
|
||||
roomDisplayName: roomProxy.infoPublisher.value.displayName ?? roomProxy.id)
|
||||
await elementCallService.setupCallSession(roomID: configuration.roomProxy.id,
|
||||
roomDisplayName: configuration.roomProxy.infoPublisher.value.displayName ?? configuration.roomProxy.id)
|
||||
}
|
||||
|
||||
timeoutTask = Task { [weak self] in
|
||||
@@ -224,7 +210,6 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
|
||||
timeoutTask = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This should always match the web app value
|
||||
private static let earpieceID = "earpiece-id"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<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,
|
||||
voiceOnly: Bool,
|
||||
rageshakeURL: String?,
|
||||
analyticsConfiguration: ElementCallAnalyticsConfiguration?) 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)
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,6 @@
|
||||
<string>applinks:staging.element.io</string>
|
||||
<string>applinks:develop.element.io</string>
|
||||
<string>applinks:mobile.element.io</string>
|
||||
<string>applinks:call.element.io</string>
|
||||
<string>applinks:call.element.dev</string>
|
||||
<string>applinks:matrix.to</string>
|
||||
<string>webcredentials:*.element.io</string>
|
||||
</array>
|
||||
|
||||
@@ -37,16 +37,6 @@
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>Element Call</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>io.element.call</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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?) {
|
||||
|
||||
Reference in New Issue
Block a user