From e6ebb3a13988d2c36343a28aff0773d70438c7ac Mon Sep 17 00:00:00 2001 From: lumaa-dev Date: Thu, 26 Sep 2024 00:58:52 +0200 Subject: [PATCH] Deep link fixed --- Bubble/Data/URLNavigator.swift | 160 +++++++++++++++++----- Bubble/Views/ContentView.swift | 16 ++- Bubble/Views/Post/TimelineView.swift | 6 - Bubble/Views/Profile/AccountView.swift | 5 - Bubble/Views/Tabs/DiscoveryView.swift | 7 - Bubble/Views/Tabs/NotificationsView.swift | 6 - 6 files changed, 138 insertions(+), 62 deletions(-) diff --git a/Bubble/Data/URLNavigator.swift b/Bubble/Data/URLNavigator.swift index 72354fa..8d2705a 100644 --- a/Bubble/Data/URLNavigator.swift +++ b/Bubble/Data/URLNavigator.swift @@ -4,52 +4,144 @@ import Foundation import SwiftUI extension Navigator { - public func handle(url: URL) -> OpenURLAction.Result { + /// Handles the tapping links in posts, bios, etc... + /// - Parameter uni: Defines if the function is triggered by the ``UniversalNavigator``. + public func handle(url: URL, uni: Bool = false) -> OpenURLAction.Result { + print("\(url.absoluteString) TAPPED") guard let client = self.client else { return .systemAction } let path: String = url.absoluteString.replacingOccurrences(of: AppInfo.scheme, with: "") // remove all path - let urlPath: URL = URL(string: path)! + let urlPath: URL = URL(string: path) ?? URL(string: "https://example.com/")! + if !url.absoluteString.starts(with: AppInfo.scheme) { + if client.isAuth && client.hasConnection(with: url) { + guard let actionType = urlPath.getActionType() else { fatalError("Couldn't get URLNav actionType") } + let server: String = urlPath.host() ?? client.server - let server: String = urlPath.host() ?? client.server - let lastIndex = urlPath.pathComponents.count - 1 + print("actionType: \(actionType)") - let actionType = urlPath.pathComponents[lastIndex - 1] + if actionType == .account { + Task { + do { + print("search acct: \(urlPath.lastPathComponent)@\(server.replacingOccurrences(of: "www.", with: ""))") + let search: SearchResults = try await client.get(endpoint: Search.search(query: "\(urlPath.lastPathComponent)@\(server.replacingOccurrences(of: "www.", with: ""))", type: "accounts", offset: nil, following: nil), forceVersion: .v2) + print(search) + if let acc: Account = search.accounts.first, !search.accounts.isEmpty { + guard !uni else { return OpenURLAction.Result.discarded } + self.navigate(to: .account(acc: acc)) + } else { + guard uni else { return OpenURLAction.Result.discarded } + self.presentedSheet = .safari(url: url) + } + } catch { + print(error) + } - if client.isAuth && client.hasConnection(with: url) { - if urlPath.lastPathComponent.starts(with: "@") { - Task { - do { - print("\(urlPath.lastPathComponent)@\(server.replacingOccurrences(of: "www.", with: ""))") - let search: SearchResults = try await client.get(endpoint: Search.search(query: "\(urlPath.lastPathComponent)@\(server.replacingOccurrences(of: "www.", with: ""))", type: "accounts", offset: nil, following: nil), forceVersion: .v2) - print(search) - let acc: Account = search.accounts.first ?? .placeholder() - self.navigate(to: .account(acc: acc)) - } catch { - print(error) + return OpenURLAction.Result.handled } - } - return OpenURLAction.Result.handled - } else { - self.presentedSheet = .safari(url: url) - } - } else { - Task { - do { - let connections: [String] = try await client.get(endpoint: Instances.peers) - client.addConnections(connections) + } else if actionType == .tag { + Task { + do { + let tag: String = urlPath.lastPathComponent + let search: SearchResults = try await client.get(endpoint: Search.search(query: "#\(tag)", type: "hashtags", offset: nil, following: nil), forceVersion: .v2) + print(search) + if let tgg: Tag = search.hashtags.first, !search.hashtags.isEmpty { + guard !uni else { return OpenURLAction.Result.discarded } + self.navigate(to: .timeline(timeline: .hashtag(tag: tgg.name, accountId: nil))) + } else { + guard uni else { return OpenURLAction.Result.discarded } + self.presentedSheet = .safari(url: url) + } + } catch { + print(error) + } - - if client.hasConnection(with: url) { - _ = self.handle(url: url) - } else { - self.presentedSheet = .safari(url: url) + return OpenURLAction.Result.handled } - } catch { + } else { self.presentedSheet = .safari(url: url) } - } + } else { + print("clicked isn't handled properly") - return OpenURLAction.Result.handled + Task { + do { + let connections: [String] = try await client.get(endpoint: Instances.peers) + client.addConnections(connections) + + if client.hasConnection(with: url) { + _ = self.handle(url: url, uni: uni) + } else { + guard uni else { return OpenURLAction.Result.discarded } + print("clicked isn't connection") + self.presentedSheet = .safari(url: url) + } + } catch { + guard uni else { return OpenURLAction.Result.discarded } + self.presentedSheet = .safari(url: url) + } + + return OpenURLAction.Result.handled + } + } + } else { + print("deeplink detected") + let actions: [String] = path.split(separator: /\/+/).map({ $0.lowercased().replacing(/\?(.)+$/, with: "") }) + if !actions.isEmpty, let mainAction: String = actions.first { + if mainAction == "update" { + guard uni else { return OpenURLAction.Result.discarded } + self.presentedSheet = .update + } else if mainAction == "new" { + guard uni else { return OpenURLAction.Result.discarded } + var newContent: String = "" + if let queries: [String : String] = urlPath.getQueryParameters() { + newContent = queries["text"] ?? "" + } + + self.presentedSheet = .post(content: newContent, replyId: nil, editId: nil) + } + } } + return OpenURLAction.Result.handled } } + +private extension URL { + func getActionType() -> String.ActionType? { + let pathComponents = self.pathComponents + let subLinks = pathComponents.filter { $0 != "/" && !$0.isEmpty } + + return subLinks.first?.getRecognizer() + } + + func getQueryParameters() -> [String: String]? { + guard let components = URLComponents(url: self, resolvingAgainstBaseURL: false), let queryItems = components.queryItems else { + print("Invalid URL or no query items") + return nil + } + + // Convert query items into a dictionary + var queryDict = [String: String]() + for item in queryItems { + queryDict[item.name] = item.value + } + + return queryDict + } +} + +private extension String { + func getRecognizer() -> String.ActionType? { + if self.starts(with: "@") { + return .account + } else if self.starts(with: "tags") { + return .tag + } + + return nil + } + + enum ActionType: String { + case account = "account" + case tag = "tag" + } +} diff --git a/Bubble/Views/ContentView.swift b/Bubble/Views/ContentView.swift index a6aafd4..cdbdc17 100644 --- a/Bubble/Views/ContentView.swift +++ b/Bubble/Views/ContentView.swift @@ -78,13 +78,20 @@ struct ContentView: View { } } } + .environment(\.openURL, OpenURLAction { url in + return self.openURL(url) + }) .onOpenURL(perform: { url in - guard preferences.browserType == .inApp else { return } - uniNavigator.presentedSheet = .safari(url: url) - let handled = uniNavigator.handle(url: url) + _ = openURL(url) }) } - + + private func openURL(_ url: URL) -> OpenURLAction.Result { + _ = uniNavigator.handle(url: url, uni: true) + _ = Navigator.shared.handle(url: url, uni: false) + return .handled + } + func recognizeAccount() async { let appAccount: AppAccount? = AppAccount.loadAsCurrent() if appAccount == nil { @@ -92,6 +99,7 @@ struct ContentView: View { } else { let cli = Client(server: appAccount!.server, oauthToken: appAccount!.oauthToken) accountManager.setClient(cli) + navigator.client = cli uniNavigator.client = cli // Check if token is still working diff --git a/Bubble/Views/Post/TimelineView.swift b/Bubble/Views/Post/TimelineView.swift index bb59f7d..5a26edb 100644 --- a/Bubble/Views/Post/TimelineView.swift +++ b/Bubble/Views/Post/TimelineView.swift @@ -137,12 +137,6 @@ struct TimelineView: View { } } } - .environment(\.openURL, OpenURLAction { url in - // Open internal URL. -// guard preferences.browserType == .inApp else { return .systemAction } - let handled = navigator.handle(url: url) - return handled - }) .environmentObject(navigator) .background(Color.appBackground) .toolbarBackground(Color.appBackground, for: .navigationBar) diff --git a/Bubble/Views/Profile/AccountView.swift b/Bubble/Views/Profile/AccountView.swift index 4eae77c..806efd7 100644 --- a/Bubble/Views/Profile/AccountView.swift +++ b/Bubble/Views/Profile/AccountView.swift @@ -20,11 +20,6 @@ struct AccountView: View { } } } - .environment(\.openURL, OpenURLAction { url in - // Open internal URL. - let handled = navigator.handle(url: url) - return handled - }) .environmentObject(navigator) } } diff --git a/Bubble/Views/Tabs/DiscoveryView.swift b/Bubble/Views/Tabs/DiscoveryView.swift index 33d7a1a..b67eb0b 100644 --- a/Bubble/Views/Tabs/DiscoveryView.swift +++ b/Bubble/Views/Tabs/DiscoveryView.swift @@ -91,13 +91,6 @@ struct DiscoveryView: View { .withAppRouter(navigator) .navigationTitle(Text("discovery")) } - .environment(\.openURL, OpenURLAction { url in - // Open internal URL. - return .systemAction -// guard preferences.browserType == .inApp else { return .systemAction } -// let handled = navigator.handle(url: url) -// return handled - }) .environmentObject(navigator) .task { await fetchTrending() diff --git a/Bubble/Views/Tabs/NotificationsView.swift b/Bubble/Views/Tabs/NotificationsView.swift index f7d3525..5a8d692 100644 --- a/Bubble/Views/Tabs/NotificationsView.swift +++ b/Bubble/Views/Tabs/NotificationsView.swift @@ -94,12 +94,6 @@ struct NotificationsView: View { } } } - .environment(\.openURL, OpenURLAction { url in - // Open internal URL. -// guard preferences.browserType == .inApp else { return .systemAction } - let handled = navigator.handle(url: url) - return handled - }) .environmentObject(navigator) .task { loadingNotifs = true