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

@ -6,19 +6,19 @@
// //
import Foundation import Foundation
import Combine import Combine
public protocol NavigationManagerDelegate: AnyObject { public protocol NavigationManagerDelegate: AnyObject {
func loadNonGeminiURL(_ url: URL) func loadNonGeminiURL(_ url: URL)
} }
public class NavigationManager: NSObject, ObservableObject { public class NavigationManager: NSObject, ObservableObject, Codable {
public weak var delegate: NavigationManagerDelegate? public weak var delegate: NavigationManagerDelegate?
@Published public var currentURL: URL @Published public var currentURL: URL
@Published public var backStack = [URL]() @Published public var backStack = [HistoryEntry]()
@Published public var forwardStack = [URL]() @Published public var forwardStack = [HistoryEntry]()
public let navigationOperation = PassthroughSubject<Operation, Never>() public let navigationOperation = PassthroughSubject<Operation, Never>()
@ -30,8 +30,31 @@ public class NavigationManager: NSObject, ObservableObject {
return components.string! return components.string!
} }
// public var titleForCurrentURL: String?
private var currentHistoryEntry: HistoryEntry
public init(url: URL) { public init(url: URL) {
self.currentURL = 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) { public func changeURL(_ url: URL) {
@ -58,8 +81,9 @@ public class NavigationManager: NSObject, ObservableObject {
let url = components.url! let url = components.url!
backStack.append(currentURL) backStack.append(currentHistoryEntry)
currentURL = url currentURL = url
currentHistoryEntry = HistoryEntry(url: url, title: nil)
forwardStack = [] forwardStack = []
navigationOperation.send(.go) navigationOperation.send(.go)
@ -79,8 +103,9 @@ public class NavigationManager: NSObject, ObservableObject {
guard count <= backStack.count else { return } guard count <= backStack.count else { return }
var removed = backStack.suffix(count) var removed = backStack.suffix(count)
backStack.removeLast(count) backStack.removeLast(count)
forwardStack.insert(currentURL, at: 0) forwardStack.insert(currentHistoryEntry, at: 0)
currentURL = removed.removeFirst() currentHistoryEntry = removed.removeFirst()
currentURL = currentHistoryEntry.url
forwardStack.insert(contentsOf: removed, at: 0) forwardStack.insert(contentsOf: removed, at: 0)
navigationOperation.send(.backward(count: count)) navigationOperation.send(.backward(count: count))
@ -94,8 +119,9 @@ public class NavigationManager: NSObject, ObservableObject {
guard count <= forwardStack.count else { return } guard count <= forwardStack.count else { return }
var removed = forwardStack.prefix(count) var removed = forwardStack.prefix(count)
forwardStack.removeFirst(count) forwardStack.removeFirst(count)
backStack.append(currentURL) backStack.append(currentHistoryEntry)
currentURL = removed.removeLast() currentHistoryEntry = removed.removeLast()
currentURL = currentHistoryEntry.url
backStack.append(contentsOf: removed) backStack.append(contentsOf: removed)
navigationOperation.send(.forward(count: count)) 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 { public extension NavigationManager {
enum Operation { enum Operation {
case go, reload, forward(count: Int), backward(count: Int) 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 currentBrowserVC.scrollViewDelegate = self
embedChild(currentBrowserVC, in: browserContainer) embedChild(currentBrowserVC, in: browserContainer)
backBrowserVCs = navigator.backStack.map(createBrowserVC(url:)) backBrowserVCs = navigator.backStack.map { createBrowserVC(url: $0.url) }
forwardBrowserVCs = navigator.forwardStack.map(createBrowserVC(url:)) forwardBrowserVCs = navigator.forwardStack.map { createBrowserVC(url: $0.url) }
navBarView = NavigationBarView(navigator: navigator) navBarView = NavigationBarView(navigator: navigator)
navBarView.translatesAutoresizingMaskIntoConstraints = false navBarView.translatesAutoresizingMaskIntoConstraints = false
@ -201,7 +201,7 @@ class BrowserNavigationController: UIViewController {
} }
if location.x < startEdgeNavigationSwipeDistance && velocity.x > 0 && navigator.backStack.count > 0 { 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) embedChild(older, in: browserContainer)
older.view.layer.zPosition = -2 older.view.layer.zPosition = -2
older.view.transform = CGAffineTransform(translationX: -1 * edgeNavigationParallaxFactor * view.bounds.width, y: 0) older.view.transform = CGAffineTransform(translationX: -1 * edgeNavigationParallaxFactor * view.bounds.width, y: 0)
@ -236,7 +236,7 @@ class BrowserNavigationController: UIViewController {
} }
gestureState = .backwards(animator) gestureState = .backwards(animator)
} else if location.x > view.bounds.width - startEdgeNavigationSwipeDistance && velocity.x < 0 && navigator.forwardStack.count > 0 { } 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) embedChild(newer, in: browserContainer)
newer.view.transform = CGAffineTransform(translationX: view.bounds.width, y: 0) newer.view.transform = CGAffineTransform(translationX: view.bounds.width, y: 0)
newer.view.layer.zPosition = 2 newer.view.layer.zPosition = 2

View File

@ -189,7 +189,8 @@ class BrowserWebViewController: UIViewController {
} else if response.status.isSuccess { } else if response.status.isSuccess {
if response.mimeType == "text/gemini", if response.mimeType == "text/gemini",
let text = response.bodyText { let text = response.bodyText {
self.renderDocument(GeminiParser.parse(text: text, baseURL: url)) let doc = GeminiParser.parse(text: text, baseURL: url)
self.renderDocument(doc)
} else { } else {
self.renderFallback(response: response) self.renderFallback(response: response)
} }
@ -236,6 +237,10 @@ class BrowserWebViewController: UIViewController {
private func renderDocument(_ doc: Document) { private func renderDocument(_ doc: Document) {
self.document = doc self.document = doc
if navigator.currentURL == doc.url {
navigator.setTitleForCurrentURL(doc.title)
}
let html = BrowserWebViewController.preamble + renderer.renderDocumentToHTML(doc) + BrowserWebViewController.postamble let html = BrowserWebViewController.preamble + renderer.renderDocumentToHTML(doc) + BrowserWebViewController.postamble
DispatchQueue.main.async { DispatchQueue.main.async {
self.webView.isHidden = false self.webView.isHidden = false

View File

@ -151,18 +151,32 @@ class ToolbarView: UIView {
forwardsButton.isEnabled = navigator.forwardStack.count > 0 forwardsButton.isEnabled = navigator.forwardStack.count > 0
if #available(iOS 14.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 let backCount = min(5, navigator.backStack.count) - index
return UIAction(title: urlForDisplay(url)) { [unowned self] (_) in if #available(iOS 15.0, *),
self.navigator.back(count: backCount) 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) 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 let forwardCount = index + 1
return UIAction(title: urlForDisplay(url)) { [unowned self] (_) in if #available(iOS 15.0, *),
self.navigator.forward(count: forwardCount) 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) forwardsButton.menu = UIMenu(children: forward)
@ -185,10 +199,11 @@ class ToolbarView: UIView {
extension ToolbarView: UIContextMenuInteractionDelegate { extension ToolbarView: UIContextMenuInteractionDelegate {
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { 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 { if interaction.view == backButton {
return UIContextMenuConfiguration(identifier: nil, previewProvider: { nil }) { (_) -> UIMenu? in return UIContextMenuConfiguration(identifier: nil, previewProvider: { nil }) { (_) -> UIMenu? in
let children = self.navigator.backStack.suffix(5).enumerated().map { (index, url) in let children = self.navigator.backStack.suffix(5).enumerated().map { (index, entry) in
UIAction(title: self.urlForDisplay(url)) { (_) in UIAction(title: self.urlForDisplay(entry.url)) { (_) in
self.navigator.back(count: min(5, self.navigator.backStack.count) - index) self.navigator.back(count: min(5, self.navigator.backStack.count) - index)
} }
} }
@ -196,9 +211,9 @@ extension ToolbarView: UIContextMenuInteractionDelegate {
} }
} else if interaction.view == forwardsButton { } else if interaction.view == forwardsButton {
return UIContextMenuConfiguration(identifier: nil, previewProvider: { nil }) { (_) -> UIMenu? in 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 let forwardCount = index + 1
return UIAction(title: self.urlForDisplay(url)) { (_) in return UIAction(title: self.urlForDisplay(entry.url)) { (_) in
self.navigator.forward(count: forwardCount) self.navigator.forward(count: forwardCount)
} }
} }

View File

@ -9,6 +9,8 @@ import Foundation
import BrowserCore import BrowserCore
private let type = "space.vaccor.Gemini.activity.browse" private let type = "space.vaccor.Gemini.activity.browse"
private let encoder = PropertyListEncoder()
private let decoder = PropertyListDecoder()
extension NSUserActivity { extension NSUserActivity {
convenience init(geminiURL url: URL) { convenience init(geminiURL url: URL) {
@ -22,19 +24,16 @@ extension NSUserActivity {
self.init(activityType: type) self.init(activityType: type)
self.userInfo = [ self.userInfo = [
"url": manager.currentURL, "url": manager.currentURL,
"back": manager.backStack,
"forward": manager.forwardStack,
] ]
if let data = try? encoder.encode(manager) {
self.userInfo!["manager"] = data
}
} }
var navigationManager: NavigationManager? { var navigationManager: NavigationManager? {
guard activityType == type, guard activityType == type,
let url = userInfo?["url"] as? URL else { return nil } let data = userInfo?["manager"] as? Data,
let back = userInfo?["back"] as? [URL] ?? [] let manager = try? decoder.decode(NavigationManager.self, from: data) else { return nil }
let forward = userInfo?["forward"] as? [URL] ?? []
let manager = NavigationManager(url: url)
manager.backStack = back
manager.forwardStack = forward
return manager return manager
} }