mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 16:24:11 +00:00
2777075655
The call-from-Recents handling was wired entirely to the deprecated INStartVideoCallIntent (the activity-type registration and the cast), which also meant the app never handled the modern INStartCallIntent that superseded it in iOS 13. Switch both to INStartCallIntent and derive isVoiceCall from its callCapability. Clears the deprecation warning.
150 lines
5.9 KiB
Swift
150 lines
5.9 KiB
Swift
//
|
||
// Copyright 2025 Element Creations Ltd.
|
||
// Copyright 2022-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 SwiftUI
|
||
|
||
@main
|
||
struct Application: App {
|
||
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
|
||
@Environment(\.openURL) private var openURL
|
||
@Environment(\.openWindow) private var openWindow
|
||
@Environment(\.dismissWindow) private var dismissWindow
|
||
@Environment(\.displayScale) private var displayScale
|
||
|
||
private var appCoordinator: AppCoordinatorProtocol!
|
||
|
||
init() {
|
||
if ProcessInfo.isRunningUITests {
|
||
appCoordinator = UITestsAppCoordinator(appDelegate: appDelegate)
|
||
} else if ProcessInfo.isRunningUnitTests {
|
||
appCoordinator = UnitTestsAppCoordinator(appDelegate: appDelegate)
|
||
} else if ProcessInfo.isRunningAccessibilityTests {
|
||
appCoordinator = AccessibilityTestsAppCoordinator(appDelegate: appDelegate)
|
||
} else {
|
||
appCoordinator = AppCoordinator(appDelegate: appDelegate)
|
||
}
|
||
|
||
SceneDelegate.windowManager = appCoordinator.windowManager
|
||
}
|
||
|
||
var body: some Scene {
|
||
WindowGroup(id: SceneDelegate.mainSceneID) {
|
||
appCoordinator.toPresentable()
|
||
.statusBarHidden(shouldHideStatusBar)
|
||
.overlay(alignment: .top) {
|
||
if #available(iOS 26, *), ProcessInfo.processInfo.isiOSAppOnMac {
|
||
// Fake an old-school titlebar to reduce the "floaty-ness" of everything with liquid glass.
|
||
Divider().ignoresSafeArea()
|
||
}
|
||
}
|
||
.environment(\.openURL, openURLAction(appCoordinator: appCoordinator, windowType: nil))
|
||
.onOpenURL { url in
|
||
openURL(url, isExternalURL: true)
|
||
}
|
||
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { userActivity in
|
||
// Universal Links opened from the Camera app's QR scanner (and NFC tags) are
|
||
// delivered as a browsing-web user activity rather than through `onOpenURL` 🤷♂️
|
||
guard let url = userActivity.webpageURL else { return }
|
||
openURL(url, isExternalURL: true)
|
||
}
|
||
.onContinueUserActivity("INStartCallIntent") { userActivity in
|
||
appCoordinator.handleUserActivity(userActivity)
|
||
}
|
||
.task {
|
||
UserAgentBuilder.displayScale = displayScale
|
||
appCoordinator.start()
|
||
appCoordinator.windowManager.configure(withOpenWindowAction: openWindow,
|
||
dismissWindowAction: dismissWindow)
|
||
}
|
||
}
|
||
.handlesExternalEvents(matching: ["*"])
|
||
.commands {
|
||
CommandGroup(replacing: .newItem) {
|
||
Button(L10n.actionStartChat) { }
|
||
.disabled(true)
|
||
}
|
||
|
||
CommandGroup(replacing: .appSettings) {
|
||
Button(L10n.commonSettings) {
|
||
appCoordinator.handleAppRoute(.settings, windowType: nil)
|
||
}
|
||
.keyboardShortcut(",", modifiers: .command)
|
||
}
|
||
|
||
CommandGroup(after: .windowArrangement) {
|
||
Button("Global Search") {
|
||
appCoordinator.handleAppRoute(.search, windowType: nil)
|
||
}
|
||
.keyboardShortcut("k", modifiers: [.command])
|
||
}
|
||
}
|
||
|
||
// This is invoked in response of the WindowManager receiving a register
|
||
// coordinator request and invoking the `OpenWindowAction` with which
|
||
// it's configured in the task above.
|
||
WindowGroup(for: SecondaryWindowType.self) { $type in
|
||
if let type {
|
||
appCoordinator.windowManager.windowForType(type)
|
||
.environment(\.openURL, openURLAction(appCoordinator: appCoordinator, windowType: type))
|
||
}
|
||
}
|
||
.defaultSize(width: ProcessInfo.processInfo.isiOSAppOnMac ? 600 : 400, height: 800)
|
||
.windowResizability(.contentSize)
|
||
}
|
||
|
||
private func openURLAction(appCoordinator: AppCoordinatorProtocol, windowType: SecondaryWindowType?) -> OpenURLAction {
|
||
.init { url in
|
||
if appCoordinator.handleDeepLink(url, isExternalURL: false, windowType: windowType) {
|
||
return .handled
|
||
}
|
||
|
||
if appCoordinator.handlePotentialPhishingAttempt(url: url, openURLAction: { url in
|
||
openURL(url, isExternalURL: false)
|
||
}) {
|
||
return .handled
|
||
}
|
||
|
||
return .systemAction
|
||
}
|
||
}
|
||
|
||
// MARK: - Private
|
||
|
||
private func openURL(_ url: URL, isExternalURL: Bool) {
|
||
if !appCoordinator.handleDeepLink(url, isExternalURL: isExternalURL, windowType: nil) {
|
||
openURLInSystemBrowser(url)
|
||
}
|
||
}
|
||
|
||
/// Hide the status bar so it doesn't interfere with the screenshot tests
|
||
private var shouldHideStatusBar: Bool {
|
||
ProcessInfo.isRunningUITests
|
||
}
|
||
|
||
/// https://github.com/element-hq/element-x-ios/issues/1824
|
||
/// Avoid opening universal links in other app variants and infinite loops between them
|
||
private func openURLInSystemBrowser(_ originalURL: URL) {
|
||
guard var urlComponents = URLComponents(url: originalURL, resolvingAgainstBaseURL: true) else {
|
||
openURL(originalURL)
|
||
return
|
||
}
|
||
|
||
var queryItems = urlComponents.queryItems ?? []
|
||
queryItems.append(.init(name: "no_universal_links", value: "true"))
|
||
|
||
urlComponents.queryItems = queryItems
|
||
|
||
guard let url = urlComponents.url else {
|
||
openURL(originalURL)
|
||
return
|
||
}
|
||
|
||
openURL(url)
|
||
}
|
||
}
|