mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2025-01-11 06:05:49 +01:00
8038e8e6af
Previously, if the app was not already running when the Safari action extension was used to open a post in the app, the post would open in the in-app Safari instead of using the Ice Cubes UI. The action extension only worked well if Ice Cubes was already running but backgrounded when it was used. This was because of the `hasConnection(with:)` check used to ensure that the current server has a federation relationship with the server the post is on. Early in app launch, the list of federated peers has not come back from the API request yet, so `hasConnection(with:)` was always returning `false`. To fix, issue a request to fetch the peers as part of the URL handling process, before checking `hasConnection(with:)` to make the final navigation decision. As an optimization, only do this if `hasConnection(with:)` returns `false` initially -- if it returns `true`, we already know a connection exists so no need to check again.
168 lines
4.9 KiB
Swift
168 lines
4.9 KiB
Swift
import DesignSystem
|
|
import Env
|
|
import Models
|
|
import Observation
|
|
import SafariServices
|
|
import SwiftUI
|
|
|
|
extension View {
|
|
@MainActor func withSafariRouter() -> some View {
|
|
modifier(SafariRouter())
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
private struct SafariRouter: ViewModifier {
|
|
@Environment(Theme.self) private var theme
|
|
@Environment(UserPreferences.self) private var preferences
|
|
@Environment(RouterPath.self) private var routerPath
|
|
|
|
#if !os(visionOS)
|
|
@State private var safariManager = InAppSafariManager()
|
|
#endif
|
|
|
|
func body(content: Content) -> some View {
|
|
content
|
|
.environment(\.openURL, OpenURLAction { url in
|
|
// Open internal URL.
|
|
routerPath.handle(url: url)
|
|
})
|
|
.onOpenURL { url in
|
|
// Open external URL (from icecubesapp://)
|
|
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)
|
|
}
|
|
.onAppear {
|
|
routerPath.urlHandler = { url in
|
|
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
|
|
}
|
|
}
|
|
#if !targetEnvironment(macCatalyst)
|
|
guard preferences.preferredBrowser == .inAppSafari else { return .systemAction }
|
|
// 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)
|
|
return .systemAction
|
|
#else
|
|
return safariManager.open(url)
|
|
#endif
|
|
#else
|
|
return .systemAction
|
|
#endif
|
|
}
|
|
}
|
|
#if !os(visionOS)
|
|
.background {
|
|
WindowReader { window in
|
|
safariManager.windowScene = window.windowScene
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if !os(visionOS)
|
|
@MainActor
|
|
@Observable private class InAppSafariManager: NSObject, SFSafariViewControllerDelegate {
|
|
var windowScene: UIWindowScene?
|
|
let viewController: UIViewController = .init()
|
|
var window: UIWindow?
|
|
|
|
@MainActor
|
|
func open(_ url: URL) -> OpenURLAction.Result {
|
|
guard let windowScene else { return .systemAction }
|
|
|
|
window = setupWindow(windowScene: windowScene)
|
|
|
|
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)
|
|
}
|
|
|
|
return .handled
|
|
}
|
|
|
|
func setupWindow(windowScene: UIWindowScene) -> UIWindow {
|
|
let window = window ?? UIWindow(windowScene: windowScene)
|
|
|
|
window.rootViewController = viewController
|
|
window.makeKeyAndVisible()
|
|
|
|
switch Theme.shared.selectedScheme {
|
|
case .dark:
|
|
window.overrideUserInterfaceStyle = .dark
|
|
case .light:
|
|
window.overrideUserInterfaceStyle = .light
|
|
}
|
|
|
|
self.window = window
|
|
return window
|
|
}
|
|
|
|
nonisolated func safariViewControllerDidFinish(_: SFSafariViewController) {
|
|
Task { @MainActor in
|
|
window?.resignKey()
|
|
window?.isHidden = false
|
|
window = nil
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
private struct WindowReader: UIViewRepresentable {
|
|
var onUpdate: (UIWindow) -> Void
|
|
|
|
func makeUIView(context _: Context) -> InjectView {
|
|
InjectView(onUpdate: onUpdate)
|
|
}
|
|
|
|
func updateUIView(_: InjectView, context _: Context) {}
|
|
|
|
class InjectView: UIView {
|
|
var onUpdate: (UIWindow) -> Void
|
|
|
|
init(onUpdate: @escaping (UIWindow) -> Void) {
|
|
self.onUpdate = onUpdate
|
|
super.init(frame: .zero)
|
|
isHidden = true
|
|
isUserInteractionEnabled = false
|
|
}
|
|
|
|
@available(*, unavailable)
|
|
required init?(coder _: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func willMove(toWindow newWindow: UIWindow?) {
|
|
super.willMove(toWindow: newWindow)
|
|
|
|
if let window = newWindow {
|
|
onUpdate(window)
|
|
} else {
|
|
DispatchQueue.main.async { [weak self] in
|
|
if let window = self?.window {
|
|
self?.onUpdate(window)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|