Show page titles in back/forwards menus on iOS 15
This commit is contained in:
parent
ebdd06738a
commit
7dd4f3aa4c
|
@ -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?
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue