229 lines
9.2 KiB
Swift
229 lines
9.2 KiB
Swift
//
|
|
// ToolbarView.swift
|
|
// Gemini-iOS
|
|
//
|
|
// Created by Shadowfacts on 12/19/20.
|
|
//
|
|
|
|
import UIKit
|
|
import BrowserCore
|
|
import Combine
|
|
|
|
class ToolbarView: UIView {
|
|
|
|
let navigator: NavigationManager
|
|
|
|
var showTableOfContents: (() -> Void)?
|
|
var showShareSheet: ((UIView) -> Void)?
|
|
var showPreferences: (() -> Void)?
|
|
|
|
private var border: UIView!
|
|
private var buttonsStack: UIStackView!
|
|
private var toolbarButtons: [ToolbarItem: UIButton] = [:]
|
|
|
|
private var cancellables = [AnyCancellable]()
|
|
|
|
init(navigator: NavigationManager) {
|
|
self.navigator = navigator
|
|
|
|
super.init(frame: .zero)
|
|
|
|
backgroundColor = .systemBackground
|
|
|
|
border = UIView()
|
|
border.translatesAutoresizingMaskIntoConstraints = false
|
|
border.backgroundColor = UIColor(white: traitCollection.userInterfaceStyle == .dark ? 0.25 : 0.75, alpha: 1)
|
|
addSubview(border)
|
|
NSLayoutConstraint.activate([
|
|
border.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
border.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
border.topAnchor.constraint(equalTo: topAnchor),
|
|
border.heightAnchor.constraint(equalToConstant: 1),
|
|
])
|
|
|
|
buttonsStack = UIStackView()
|
|
buttonsStack.axis = .horizontal
|
|
buttonsStack.distribution = .fillEqually
|
|
buttonsStack.alignment = .fill
|
|
buttonsStack.translatesAutoresizingMaskIntoConstraints = false
|
|
addSubview(buttonsStack)
|
|
let safeAreaConstraint = buttonsStack.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor)
|
|
safeAreaConstraint.priority = .defaultHigh
|
|
NSLayoutConstraint.activate([
|
|
buttonsStack.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
buttonsStack.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
buttonsStack.topAnchor.constraint(equalTo: topAnchor, constant: 5),
|
|
safeAreaConstraint,
|
|
buttonsStack.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -8)
|
|
])
|
|
|
|
updateNavigationButtons()
|
|
|
|
navigator.navigationOperation
|
|
.sink { [unowned self] (_) in
|
|
self.updateNavigationButtons()
|
|
}
|
|
.store(in: &cancellables)
|
|
|
|
Preferences.shared.$toolbar
|
|
.sink { [unowned self] (newValue) in
|
|
self.createToolbarButtons(newValue)
|
|
}
|
|
.store(in: &cancellables)
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
|
super.traitCollectionDidChange(previousTraitCollection)
|
|
|
|
border.backgroundColor = UIColor(white: traitCollection.userInterfaceStyle == .dark ? 0.25 : 0.75, alpha: 1)
|
|
}
|
|
|
|
private func createToolbarButtons(_ items: [ToolbarItem] = Preferences.shared.toolbar) {
|
|
toolbarButtons = [:]
|
|
buttonsStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
|
|
|
for item in items {
|
|
let button = createButton(item)
|
|
toolbarButtons[item] = button
|
|
buttonsStack.addArrangedSubview(button)
|
|
}
|
|
|
|
updateNavigationButtons()
|
|
}
|
|
|
|
private func createButton(_ item: ToolbarItem) -> UIButton {
|
|
let button = UIButton()
|
|
let symbolConfig = UIImage.SymbolConfiguration(pointSize: 24)
|
|
button.setImage(UIImage(systemName: item.imageName, withConfiguration: symbolConfig)!, for: .normal)
|
|
button.accessibilityLabel = item.displayName
|
|
button.isPointerInteractionEnabled = true
|
|
|
|
switch item {
|
|
case .back:
|
|
button.addTarget(navigator, action: #selector(NavigationManager.goBack), for: .touchUpInside)
|
|
// fallback for when UIButton.menu isn't available
|
|
if #available(iOS 14.0, *) {
|
|
} else {
|
|
button.addInteraction(UIContextMenuInteraction(delegate: self))
|
|
}
|
|
|
|
case .forward:
|
|
button.addTarget(navigator, action: #selector(NavigationManager.goForward), for: .touchUpInside)
|
|
if #available(iOS 14.0, *) {
|
|
} else {
|
|
button.addInteraction(UIContextMenuInteraction(delegate: self))
|
|
}
|
|
|
|
case .reload:
|
|
button.addTarget(navigator, action: #selector(NavigationManager.reload), for: .touchUpInside)
|
|
|
|
case .share:
|
|
button.addTarget(self, action: #selector(sharePressed), for: .touchUpInside)
|
|
|
|
case .home:
|
|
button.addTarget(self, action: #selector(homePressed), for: .touchUpInside)
|
|
|
|
case .tableOfContents:
|
|
button.addTarget(self, action: #selector(tableOfContentsPressed), for: .touchUpInside)
|
|
|
|
case .preferences:
|
|
button.addTarget(self, action: #selector(prefsPressed), for: .touchUpInside)
|
|
}
|
|
|
|
return button
|
|
}
|
|
|
|
private func updateNavigationButtons() {
|
|
if let backButton = toolbarButtons[.back] {
|
|
backButton.isEnabled = navigator.backStack.count > 0
|
|
if #available(iOS 14.0, *) {
|
|
let back = navigator.backStack.suffix(5).enumerated().reversed().map { (index, entry) -> UIAction in
|
|
let backCount = min(5, navigator.backStack.count) - index
|
|
if #available(iOS 15.0, *),
|
|
let title = entry.title {
|
|
return UIAction(title: title, subtitle: BrowserHelper.urlForDisplay(entry.url)) { [unowned self] (_) in
|
|
self.navigator.back(count: backCount)
|
|
}
|
|
} else {
|
|
return UIAction(title: BrowserHelper.urlForDisplay(entry.url)) { [unowned self] (_) in
|
|
self.navigator.back(count: backCount)
|
|
}
|
|
}
|
|
}
|
|
backButton.menu = UIMenu(children: back)
|
|
}
|
|
}
|
|
|
|
if let forwardsButton = toolbarButtons[.forward] {
|
|
forwardsButton.isEnabled = navigator.forwardStack.count > 0
|
|
if #available(iOS 14.0, *) {
|
|
let forward = navigator.forwardStack.prefix(5).enumerated().map { (index, entry) -> UIAction in
|
|
let forwardCount = index + 1
|
|
if #available(iOS 15.0, *),
|
|
let title = entry.title {
|
|
return UIAction(title: title, subtitle: BrowserHelper.urlForDisplay(entry.url)) { [unowned self] (_) in
|
|
self.navigator.forward(count: forwardCount)
|
|
}
|
|
} else {
|
|
return UIAction(title: BrowserHelper.urlForDisplay(entry.url)) { [unowned self] (_) in
|
|
self.navigator.forward(count: forwardCount)
|
|
}
|
|
}
|
|
}
|
|
forwardsButton.menu = UIMenu(children: forward)
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc private func tableOfContentsPressed() {
|
|
showTableOfContents?()
|
|
}
|
|
|
|
@objc private func sharePressed() {
|
|
showShareSheet?(toolbarButtons[.share]!)
|
|
}
|
|
|
|
@objc private func prefsPressed() {
|
|
showPreferences?()
|
|
}
|
|
|
|
@objc private func homePressed() {
|
|
navigator.changeURL(Preferences.shared.homepage)
|
|
}
|
|
|
|
}
|
|
|
|
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 let backButton = toolbarButtons[.back],
|
|
interaction.view == backButton {
|
|
return UIContextMenuConfiguration(identifier: nil, previewProvider: { nil }) { (_) -> UIMenu? in
|
|
let children = self.navigator.backStack.suffix(5).enumerated().map { (index, entry) in
|
|
UIAction(title: BrowserHelper.urlForDisplay(entry.url)) { (_) in
|
|
self.navigator.back(count: min(5, self.navigator.backStack.count) - index)
|
|
}
|
|
}
|
|
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: children)
|
|
}
|
|
} else if let forwardsButton = toolbarButtons[.forward],
|
|
interaction.view == forwardsButton {
|
|
return UIContextMenuConfiguration(identifier: nil, previewProvider: { nil }) { (_) -> UIMenu? in
|
|
let children = self.navigator.forwardStack.prefix(5).enumerated().map { (index, entry) -> UIAction in
|
|
let forwardCount = index + 1
|
|
return UIAction(title: BrowserHelper.urlForDisplay(entry.url)) { (_) in
|
|
self.navigator.forward(count: forwardCount)
|
|
}
|
|
}
|
|
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: children)
|
|
}
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
}
|