Show page titles in back/forwards menus on iOS 15

This commit is contained in:
Shadowfacts 2021-09-28 22:13:28 -04:00
parent ebdd06738a
commit 7dd4f3aa4c
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
5 changed files with 92 additions and 32 deletions

View File

@ -12,13 +12,13 @@ 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<Operation, Never>()
@ -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?
}
}

View File

@ -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

View File

@ -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

View File

@ -151,19 +151,33 @@ 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
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
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, 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)
}
}

View File

@ -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
}