diff --git a/BrowserCore/NavigationManager.swift b/BrowserCore/NavigationManager.swift index 8822eae..93ab83a 100644 --- a/BrowserCore/NavigationManager.swift +++ b/BrowserCore/NavigationManager.swift @@ -6,19 +6,19 @@ // import Foundation -import Combine +import Combine public protocol NavigationManagerDelegate: AnyObject { func loadNonGeminiURL(_ url: URL) } -public class NavigationManager: NSObject, ObservableObject { +public class NavigationManager: NSObject, ObservableObject, Codable { public weak var delegate: NavigationManagerDelegate? @Published public var currentURL: URL - @Published public var backStack = [URL]() - @Published public var forwardStack = [URL]() + @Published public var backStack = [HistoryEntry]() + @Published public var forwardStack = [HistoryEntry]() public let navigationOperation = PassthroughSubject() @@ -30,8 +30,31 @@ public class NavigationManager: NSObject, ObservableObject { return components.string! } +// public var titleForCurrentURL: String? + private var currentHistoryEntry: HistoryEntry + public init(url: URL) { self.currentURL = url + self.currentHistoryEntry = HistoryEntry(url: url, title: nil) + } + + public required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.currentHistoryEntry = try container.decode(HistoryEntry.self, forKey: .currentHistoryEntry) + self.currentURL = self.currentHistoryEntry.url + self.backStack = try container.decode([HistoryEntry].self, forKey: .backStack) + self.forwardStack = try container.decode([HistoryEntry].self, forKey: .forwardStack) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(currentHistoryEntry, forKey: .currentHistoryEntry) + try container.encode(backStack, forKey: .backStack) + try container.encode(forwardStack, forKey: .forwardStack) + } + + public func setTitleForCurrentURL(_ title: String?) { + currentHistoryEntry.title = title } public func changeURL(_ url: URL) { @@ -58,8 +81,9 @@ public class NavigationManager: NSObject, ObservableObject { let url = components.url! - backStack.append(currentURL) + backStack.append(currentHistoryEntry) currentURL = url + currentHistoryEntry = HistoryEntry(url: url, title: nil) forwardStack = [] navigationOperation.send(.go) @@ -79,8 +103,9 @@ public class NavigationManager: NSObject, ObservableObject { guard count <= backStack.count else { return } var removed = backStack.suffix(count) backStack.removeLast(count) - forwardStack.insert(currentURL, at: 0) - currentURL = removed.removeFirst() + forwardStack.insert(currentHistoryEntry, at: 0) + currentHistoryEntry = removed.removeFirst() + currentURL = currentHistoryEntry.url forwardStack.insert(contentsOf: removed, at: 0) navigationOperation.send(.backward(count: count)) @@ -94,8 +119,9 @@ public class NavigationManager: NSObject, ObservableObject { guard count <= forwardStack.count else { return } var removed = forwardStack.prefix(count) forwardStack.removeFirst(count) - backStack.append(currentURL) - currentURL = removed.removeLast() + backStack.append(currentHistoryEntry) + currentHistoryEntry = removed.removeLast() + currentURL = currentHistoryEntry.url backStack.append(contentsOf: removed) navigationOperation.send(.forward(count: count)) @@ -103,8 +129,23 @@ public class NavigationManager: NSObject, ObservableObject { } +extension NavigationManager { + enum CodingKeys: String, CodingKey { + case currentHistoryEntry + case backStack + case forwardStack + } +} + public extension NavigationManager { enum Operation { case go, reload, forward(count: Int), backward(count: Int) } } + +public extension NavigationManager { + struct HistoryEntry: Codable { + public let url: URL + public internal(set) var title: String? + } +} diff --git a/Gemini-iOS/BrowserNavigationController.swift b/Gemini-iOS/BrowserNavigationController.swift index c036600..00447b2 100644 --- a/Gemini-iOS/BrowserNavigationController.swift +++ b/Gemini-iOS/BrowserNavigationController.swift @@ -82,8 +82,8 @@ class BrowserNavigationController: UIViewController { currentBrowserVC.scrollViewDelegate = self embedChild(currentBrowserVC, in: browserContainer) - backBrowserVCs = navigator.backStack.map(createBrowserVC(url:)) - forwardBrowserVCs = navigator.forwardStack.map(createBrowserVC(url:)) + backBrowserVCs = navigator.backStack.map { createBrowserVC(url: $0.url) } + forwardBrowserVCs = navigator.forwardStack.map { createBrowserVC(url: $0.url) } navBarView = NavigationBarView(navigator: navigator) navBarView.translatesAutoresizingMaskIntoConstraints = false @@ -201,7 +201,7 @@ class BrowserNavigationController: UIViewController { } if location.x < startEdgeNavigationSwipeDistance && velocity.x > 0 && navigator.backStack.count > 0 { - let older = backBrowserVCs.last ?? BrowserWebViewController(navigator: navigator, url: navigator.backStack.last!) + let older = backBrowserVCs.last ?? BrowserWebViewController(navigator: navigator, url: navigator.backStack.last!.url) embedChild(older, in: browserContainer) older.view.layer.zPosition = -2 older.view.transform = CGAffineTransform(translationX: -1 * edgeNavigationParallaxFactor * view.bounds.width, y: 0) @@ -236,7 +236,7 @@ class BrowserNavigationController: UIViewController { } gestureState = .backwards(animator) } else if location.x > view.bounds.width - startEdgeNavigationSwipeDistance && velocity.x < 0 && navigator.forwardStack.count > 0 { - let newer = forwardBrowserVCs.first ?? BrowserWebViewController(navigator: navigator, url: navigator.backStack.first!) + let newer = forwardBrowserVCs.first ?? BrowserWebViewController(navigator: navigator, url: navigator.backStack.first!.url) embedChild(newer, in: browserContainer) newer.view.transform = CGAffineTransform(translationX: view.bounds.width, y: 0) newer.view.layer.zPosition = 2 diff --git a/Gemini-iOS/BrowserWebViewController.swift b/Gemini-iOS/BrowserWebViewController.swift index ca157ce..944ede0 100644 --- a/Gemini-iOS/BrowserWebViewController.swift +++ b/Gemini-iOS/BrowserWebViewController.swift @@ -189,7 +189,8 @@ class BrowserWebViewController: UIViewController { } else if response.status.isSuccess { if response.mimeType == "text/gemini", let text = response.bodyText { - self.renderDocument(GeminiParser.parse(text: text, baseURL: url)) + let doc = GeminiParser.parse(text: text, baseURL: url) + self.renderDocument(doc) } else { self.renderFallback(response: response) } @@ -236,6 +237,10 @@ class BrowserWebViewController: UIViewController { private func renderDocument(_ doc: Document) { self.document = doc + if navigator.currentURL == doc.url { + navigator.setTitleForCurrentURL(doc.title) + } + let html = BrowserWebViewController.preamble + renderer.renderDocumentToHTML(doc) + BrowserWebViewController.postamble DispatchQueue.main.async { self.webView.isHidden = false diff --git a/Gemini-iOS/ToolbarView.swift b/Gemini-iOS/ToolbarView.swift index dfd8fc2..4030942 100644 --- a/Gemini-iOS/ToolbarView.swift +++ b/Gemini-iOS/ToolbarView.swift @@ -151,18 +151,32 @@ class ToolbarView: UIView { forwardsButton.isEnabled = navigator.forwardStack.count > 0 if #available(iOS 14.0, *) { - let back = navigator.backStack.suffix(5).enumerated().reversed().map { (index, url) -> UIAction in + let back = navigator.backStack.suffix(5).enumerated().reversed().map { (index, entry) -> UIAction in let backCount = min(5, navigator.backStack.count) - index - return UIAction(title: urlForDisplay(url)) { [unowned self] (_) in - self.navigator.back(count: backCount) + if #available(iOS 15.0, *), + let title = entry.title { + return UIAction(title: title, subtitle: urlForDisplay(entry.url)) { [unowned self] (_) in + self.navigator.back(count: backCount) + } + } else { + return UIAction(title: urlForDisplay(entry.url)) { [unowned self] (_) in + self.navigator.back(count: backCount) + } } } backButton.menu = UIMenu(children: back) - let forward = navigator.forwardStack.prefix(5).enumerated().map { (index, url) -> UIAction in + let forward = navigator.forwardStack.prefix(5).enumerated().map { (index, entry) -> UIAction in let forwardCount = index + 1 - return UIAction(title: urlForDisplay(url)) { [unowned self] (_) in - self.navigator.forward(count: forwardCount) + if #available(iOS 15.0, *), + let title = entry.title { + return UIAction(title: title, subtitle: urlForDisplay(entry.url)) { [unowned self] (_) in + self.navigator.forward(count: forwardCount) + } + } else { + return UIAction(title: urlForDisplay(entry.url)) { [unowned self] (_) in + self.navigator.forward(count: forwardCount) + } } } forwardsButton.menu = UIMenu(children: forward) @@ -185,10 +199,11 @@ class ToolbarView: UIView { extension ToolbarView: UIContextMenuInteractionDelegate { func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { + // this path is only used on =iOS 14, we don't create a UIContextMenuInteraction if interaction.view == backButton { return UIContextMenuConfiguration(identifier: nil, previewProvider: { nil }) { (_) -> UIMenu? in - let children = self.navigator.backStack.suffix(5).enumerated().map { (index, url) in - UIAction(title: self.urlForDisplay(url)) { (_) in + let children = self.navigator.backStack.suffix(5).enumerated().map { (index, entry) in + UIAction(title: self.urlForDisplay(entry.url)) { (_) in self.navigator.back(count: min(5, self.navigator.backStack.count) - index) } } @@ -196,9 +211,9 @@ extension ToolbarView: UIContextMenuInteractionDelegate { } } else if interaction.view == forwardsButton { return UIContextMenuConfiguration(identifier: nil, previewProvider: { nil }) { (_) -> UIMenu? in - let children = self.navigator.forwardStack.prefix(5).enumerated().map { (index, url) -> UIAction in + let children = self.navigator.forwardStack.prefix(5).enumerated().map { (index, entry) -> UIAction in let forwardCount = index + 1 - return UIAction(title: self.urlForDisplay(url)) { (_) in + return UIAction(title: self.urlForDisplay(entry.url)) { (_) in self.navigator.forward(count: forwardCount) } } diff --git a/Gemini-iOS/UserActivities.swift b/Gemini-iOS/UserActivities.swift index 2da987d..017a880 100644 --- a/Gemini-iOS/UserActivities.swift +++ b/Gemini-iOS/UserActivities.swift @@ -9,6 +9,8 @@ import Foundation import BrowserCore private let type = "space.vaccor.Gemini.activity.browse" +private let encoder = PropertyListEncoder() +private let decoder = PropertyListDecoder() extension NSUserActivity { convenience init(geminiURL url: URL) { @@ -22,19 +24,16 @@ extension NSUserActivity { self.init(activityType: type) self.userInfo = [ "url": manager.currentURL, - "back": manager.backStack, - "forward": manager.forwardStack, ] + if let data = try? encoder.encode(manager) { + self.userInfo!["manager"] = data + } } var navigationManager: NavigationManager? { guard activityType == type, - let url = userInfo?["url"] as? URL else { return nil } - let back = userInfo?["back"] as? [URL] ?? [] - let forward = userInfo?["forward"] as? [URL] ?? [] - let manager = NavigationManager(url: url) - manager.backStack = back - manager.forwardStack = forward + let data = userInfo?["manager"] as? Data, + let manager = try? decoder.decode(NavigationManager.self, from: data) else { return nil } return manager }