mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
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:
@@ -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)
|
||||
|
||||
@@ -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>?
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user