IceCubes/IceCubesApp/App/SafariRouter.swift

171 lines
5.0 KiB
Swift
Raw Normal View History

import DesignSystem
2023-01-17 11:36:01 +01:00
import Env
2023-12-18 08:22:59 +01:00
import Models
import Observation
2023-01-17 11:36:01 +01:00
import SafariServices
import SwiftUI
extension View {
@MainActor func withSafariRouter() -> some View {
modifier(SafariRouter())
2023-01-17 11:36:01 +01:00
}
}
@MainActor
private struct SafariRouter: ViewModifier {
2024-05-13 09:27:24 +02:00
@Environment(\.isSecondaryColumn) private var isSecondaryColumn: Bool
2023-09-18 21:03:52 +02:00
@Environment(Theme.self) private var theme
2023-09-19 09:18:20 +02:00
@Environment(UserPreferences.self) private var preferences
@Environment(RouterPath.self) private var routerPath
2023-01-17 11:36:01 +01:00
#if !os(visionOS)
2024-02-14 12:48:14 +01:00
@State private var safariManager = InAppSafariManager()
#endif
2023-02-21 07:23:42 +01:00
2023-01-17 11:36:01 +01:00
func body(content: Content) -> some View {
content
.environment(\.openURL, OpenURLAction { url in
// Open internal URL.
2024-05-13 09:27:24 +02:00
guard !isSecondaryColumn else { return .discarded }
return routerPath.handle(url: url)
2023-01-17 11:36:01 +01:00
})
.onOpenURL { url in
// Open external URL (from icecubesapp://)
2024-05-13 09:27:24 +02:00
guard !isSecondaryColumn else { return }
let urlString = url.absoluteString.replacingOccurrences(of: AppInfo.scheme, with: "https://")
guard let url = URL(string: urlString), url.host != nil else { return }
_ = routerPath.handleDeepLink(url: url)
}
2023-01-17 11:36:01 +01:00
.onAppear {
routerPath.urlHandler = { url in
2023-01-23 21:34:45 +01:00
if url.absoluteString.contains("@twitter.com"), url.absoluteString.hasPrefix("mailto:") {
let username = url.absoluteString
.replacingOccurrences(of: "@twitter.com", with: "")
.replacingOccurrences(of: "mailto:", with: "")
let twitterLink = "https://twitter.com/\(username)"
if let url = URL(string: twitterLink) {
UIApplication.shared.open(url)
return .handled
}
}
2023-12-18 08:22:59 +01:00
#if !targetEnvironment(macCatalyst)
guard preferences.preferredBrowser == .inAppSafari else { return .systemAction }
2024-01-09 19:06:54 +01:00
// SFSafariViewController only supports initial URLs with http:// or https:// schemes.
guard let scheme = url.scheme, ["https", "http"].contains(scheme.lowercased()) else {
return .systemAction
}
#if os(visionOS)
2024-02-14 12:48:14 +01:00
return .systemAction
2024-01-09 19:06:54 +01:00
#else
2024-02-14 12:48:14 +01:00
return safariManager.open(url)
2024-01-09 19:06:54 +01:00
#endif
#else
2024-01-09 19:06:54 +01:00
return .systemAction
#endif
}
}
2024-02-14 12:48:14 +01:00
#if !os(visionOS)
.background {
WindowReader { window in
2023-09-16 14:15:03 +02:00
safariManager.windowScene = window.windowScene
}
2023-01-17 11:36:01 +01:00
}
2024-02-14 12:48:14 +01:00
#endif
2023-01-17 11:36:01 +01:00
}
}
2023-01-17 11:36:01 +01:00
#if !os(visionOS)
@MainActor
2024-02-14 12:48:14 +01:00
@Observable private class InAppSafariManager: NSObject, SFSafariViewControllerDelegate {
var windowScene: UIWindowScene?
let viewController: UIViewController = .init()
var window: UIWindow?
2023-02-21 07:23:42 +01:00
2024-02-14 12:48:14 +01:00
@MainActor
func open(_ url: URL) -> OpenURLAction.Result {
guard let windowScene else { return .systemAction }
2023-02-21 07:23:42 +01:00
2024-02-14 12:48:14 +01:00
window = setupWindow(windowScene: windowScene)
2023-02-21 07:23:42 +01:00
2024-02-14 12:48:14 +01:00
let configuration = SFSafariViewController.Configuration()
configuration.entersReaderIfAvailable = UserPreferences.shared.inAppBrowserReaderView
let safari = SFSafariViewController(url: url, configuration: configuration)
safari.preferredBarTintColor = UIColor(Theme.shared.primaryBackgroundColor)
safari.preferredControlTintColor = UIColor(Theme.shared.tintColor)
safari.delegate = self
DispatchQueue.main.async { [weak self] in
self?.viewController.present(safari, animated: true)
}
2023-02-21 07:23:42 +01:00
2024-02-14 12:48:14 +01:00
return .handled
}
2023-02-21 07:23:42 +01:00
2024-02-14 12:48:14 +01:00
func setupWindow(windowScene: UIWindowScene) -> UIWindow {
let window = window ?? UIWindow(windowScene: windowScene)
2024-02-14 12:48:14 +01:00
window.rootViewController = viewController
window.makeKeyAndVisible()
2023-02-21 07:23:42 +01:00
2024-02-14 12:48:14 +01:00
switch Theme.shared.selectedScheme {
case .dark:
window.overrideUserInterfaceStyle = .dark
case .light:
window.overrideUserInterfaceStyle = .light
}
2023-02-21 07:23:42 +01:00
2024-02-14 12:48:14 +01:00
self.window = window
return window
}
2023-02-21 07:23:42 +01:00
2024-02-14 12:48:14 +01:00
nonisolated func safariViewControllerDidFinish(_: SFSafariViewController) {
Task { @MainActor in
window?.resignKey()
window?.isHidden = false
window = nil
}
}
}
#endif
private struct WindowReader: UIViewRepresentable {
var onUpdate: (UIWindow) -> Void
2023-02-21 07:23:42 +01:00
func makeUIView(context _: Context) -> InjectView {
InjectView(onUpdate: onUpdate)
}
2023-02-21 07:23:42 +01:00
func updateUIView(_: InjectView, context _: Context) {}
class InjectView: UIView {
var onUpdate: (UIWindow) -> Void
2023-02-21 07:23:42 +01:00
init(onUpdate: @escaping (UIWindow) -> Void) {
self.onUpdate = onUpdate
super.init(frame: .zero)
isHidden = true
isUserInteractionEnabled = false
}
2023-02-21 07:23:42 +01:00
@available(*, unavailable)
2023-02-21 07:23:42 +01:00
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
2023-02-21 07:23:42 +01:00
override func willMove(toWindow newWindow: UIWindow?) {
super.willMove(toWindow: newWindow)
2023-02-21 07:23:42 +01:00
if let window = newWindow {
onUpdate(window)
} else {
DispatchQueue.main.async { [weak self] in
if let window = self?.window {
self?.onUpdate(window)
}
}
}
}
2023-01-17 11:36:01 +01:00
}
}