Gemini/BrowserCore/NavigationManager.swift

151 lines
4.5 KiB
Swift
Raw Permalink Normal View History

2020-07-15 03:15:56 +00:00
//
// NavigationManager.swift
// Gemini
//
// Created by Shadowfacts on 7/14/20.
//
import Foundation
import Combine
2020-07-15 03:15:56 +00:00
public protocol NavigationManagerDelegate: AnyObject {
2020-09-28 02:26:44 +00:00
func loadNonGeminiURL(_ url: URL)
}
public class NavigationManager: NSObject, ObservableObject, Codable {
2020-07-15 03:15:56 +00:00
2020-09-28 02:26:44 +00:00
public weak var delegate: NavigationManagerDelegate?
@Published public var currentURL: URL
@Published public var backStack = [HistoryEntry]()
@Published public var forwardStack = [HistoryEntry]()
2020-07-15 03:15:56 +00:00
public let navigationOperation = PassthroughSubject<Operation, Never>()
2020-09-29 20:28:05 +00:00
public var displayURL: String {
var components = URLComponents(url: currentURL, resolvingAgainstBaseURL: false)!
if components.port == 1965 {
components.port = nil
}
return components.string!
}
private var currentHistoryEntry: HistoryEntry
public init(url: URL) {
2020-07-15 03:15:56 +00:00
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
2020-07-15 03:15:56 +00:00
}
public func changeURL(_ url: URL) {
var components = URLComponents(url: url, resolvingAgainstBaseURL: false)!
if let scheme = url.scheme {
if scheme != "gemini" {
delegate?.loadNonGeminiURL(url)
return
}
} else {
components.scheme = "gemini"
}
// Foundation parses bare hosts (e.g. `example.com`) as having no host and a path of `example.com`
if components.host == nil {
components.host = components.path
components.path = "/"
2020-09-28 02:26:44 +00:00
}
2020-09-29 20:28:17 +00:00
// Some Gemini servers break on empty paths
if components.path.isEmpty {
components.path = "/"
}
let url = components.url!
backStack.append(currentHistoryEntry)
currentURL = url
currentHistoryEntry = HistoryEntry(url: url, title: nil)
2020-07-15 13:18:37 +00:00
forwardStack = []
navigationOperation.send(.go)
2020-07-15 13:18:37 +00:00
}
2020-12-19 20:19:32 +00:00
@objc public func reload() {
2020-09-28 02:11:34 +00:00
let url = currentURL
currentURL = url
2020-12-21 03:27:59 +00:00
navigationOperation.send(.reload)
2020-09-28 02:11:34 +00:00
}
2020-12-19 20:19:32 +00:00
@objc public func goBack() {
back(count: 1)
2020-07-15 13:18:37 +00:00
}
public func back(count: Int) {
guard count <= backStack.count else { return }
var removed = backStack.suffix(count)
backStack.removeLast(count)
forwardStack.insert(currentHistoryEntry, at: 0)
currentHistoryEntry = removed.removeFirst()
currentURL = currentHistoryEntry.url
forwardStack.insert(contentsOf: removed, at: 0)
navigationOperation.send(.backward(count: count))
}
2020-12-19 20:19:32 +00:00
@objc public func goForward() {
forward(count: 1)
2020-07-15 03:15:56 +00:00
}
public func forward(count: Int) {
guard count <= forwardStack.count else { return }
var removed = forwardStack.prefix(count)
forwardStack.removeFirst(count)
backStack.append(currentHistoryEntry)
currentHistoryEntry = removed.removeLast()
currentURL = currentHistoryEntry.url
backStack.append(contentsOf: removed)
navigationOperation.send(.forward(count: count))
}
2020-07-15 03:15:56 +00:00
}
extension NavigationManager {
enum CodingKeys: String, CodingKey {
case currentHistoryEntry
case backStack
case forwardStack
}
}
public extension NavigationManager {
enum Operation {
2020-12-21 03:27:59 +00:00
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?
}
}