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