Dactyloidae/mobile/ios/Client/Frontend/Browser/TabToolbar.swift
2026-06-26 21:04:09 -07:00

315 lines
13 KiB
Swift

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import Foundation
import UIKit
import SnapKit
import Shared
import XCGLogger
private let log = Logger.browserLogger
protocol TabToolbarProtocol: class {
weak var tabToolbarDelegate: TabToolbarDelegate? { get set }
var tabsButton: TabsButton { get }
var menuButton: ToolbarButton { get }
var forwardButton: ToolbarButton { get }
var backButton: ToolbarButton { get }
var stopReloadButton: ToolbarButton { get }
var actionButtons: [Themeable & UIButton] { get }
func updateBackStatus(_ canGoBack: Bool)
func updateForwardStatus(_ canGoForward: Bool)
func updateReloadStatus(_ isLoading: Bool)
func updatePageStatus(_ isWebPage: Bool)
func updateTabCount(_ count: Int, animated: Bool)
}
protocol TabToolbarDelegate: class {
func tabToolbarDidPressBack(_ tabToolbar: TabToolbarProtocol, button: UIButton)
func tabToolbarDidPressForward(_ tabToolbar: TabToolbarProtocol, button: UIButton)
func tabToolbarDidLongPressBack(_ tabToolbar: TabToolbarProtocol, button: UIButton)
func tabToolbarDidLongPressForward(_ tabToolbar: TabToolbarProtocol, button: UIButton)
func tabToolbarDidPressReload(_ tabToolbar: TabToolbarProtocol, button: UIButton)
func tabToolbarDidLongPressReload(_ tabToolbar: TabToolbarProtocol, button: UIButton)
func tabToolbarDidPressStop(_ tabToolbar: TabToolbarProtocol, button: UIButton)
func tabToolbarDidPressMenu(_ tabToolbar: TabToolbarProtocol, button: UIButton)
func tabToolbarDidPressTabs(_ tabToolbar: TabToolbarProtocol, button: UIButton)
func tabToolbarDidLongPressTabs(_ tabToolbar: TabToolbarProtocol, button: UIButton)
}
@objc
open class TabToolbarHelper: NSObject {
let toolbar: TabToolbarProtocol
let ImageReload = UIImage.templateImageNamed("nav-refresh")
let ImageStop = UIImage.templateImageNamed("nav-stop")
var loading: Bool = false {
didSet {
if loading {
toolbar.stopReloadButton.setImage(ImageStop, for: .normal)
toolbar.stopReloadButton.accessibilityLabel = NSLocalizedString("Stop", comment: "Accessibility Label for the tab toolbar Stop button")
} else {
toolbar.stopReloadButton.setImage(ImageReload, for: .normal)
toolbar.stopReloadButton.accessibilityLabel = NSLocalizedString("Reload", comment: "Accessibility Label for the tab toolbar Reload button")
}
}
}
fileprivate func setTheme(theme: String, forButtons buttons: [Themeable]) {
buttons.forEach { $0.applyTheme(theme) }
}
init(toolbar: TabToolbarProtocol) {
self.toolbar = toolbar
super.init()
toolbar.backButton.setImage(UIImage.templateImageNamed("nav-back"), for: .normal)
toolbar.backButton.accessibilityLabel = NSLocalizedString("Back", comment: "Accessibility label for the Back button in the tab toolbar.")
let longPressGestureBackButton = UILongPressGestureRecognizer(target: self, action: #selector(TabToolbarHelper.SELdidLongPressBack(_:)))
toolbar.backButton.addGestureRecognizer(longPressGestureBackButton)
toolbar.backButton.addTarget(self, action: #selector(TabToolbarHelper.SELdidClickBack), for: UIControlEvents.touchUpInside)
toolbar.forwardButton.setImage(UIImage.templateImageNamed("nav-forward"), for: .normal)
toolbar.forwardButton.accessibilityLabel = NSLocalizedString("Forward", comment: "Accessibility Label for the tab toolbar Forward button")
let longPressGestureForwardButton = UILongPressGestureRecognizer(target: self, action: #selector(TabToolbarHelper.SELdidLongPressForward(_:)))
toolbar.forwardButton.addGestureRecognizer(longPressGestureForwardButton)
toolbar.forwardButton.addTarget(self, action: #selector(TabToolbarHelper.SELdidClickForward), for: UIControlEvents.touchUpInside)
toolbar.stopReloadButton.setImage(UIImage.templateImageNamed("nav-refresh"), for: .normal)
toolbar.stopReloadButton.accessibilityLabel = NSLocalizedString("Reload", comment: "Accessibility Label for the tab toolbar Reload button")
let longPressGestureStopReloadButton = UILongPressGestureRecognizer(target: self, action: #selector(TabToolbarHelper.SELdidLongPressStopReload(_:)))
toolbar.stopReloadButton.addGestureRecognizer(longPressGestureStopReloadButton)
toolbar.stopReloadButton.addTarget(self, action: #selector(TabToolbarHelper.SELdidClickStopReload), for: UIControlEvents.touchUpInside)
toolbar.tabsButton.addTarget(self, action: #selector(TabToolbarHelper.SELdidClickTabs), for: .touchUpInside)
let longPressGestureTabsButton = UILongPressGestureRecognizer(target: self, action: #selector(TabToolbarHelper.SELdidLongPressTabs(_:)))
toolbar.tabsButton.addGestureRecognizer(longPressGestureTabsButton)
toolbar.menuButton.contentMode = UIViewContentMode.center
toolbar.menuButton.setImage(UIImage.templateImageNamed("nav-menu"), for: .normal)
toolbar.menuButton.accessibilityLabel = Strings.AppMenuButtonAccessibilityLabel
toolbar.menuButton.addTarget(self, action: #selector(TabToolbarHelper.SELdidClickMenu), for: UIControlEvents.touchUpInside)
toolbar.menuButton.accessibilityIdentifier = "TabToolbar.menuButton"
setTheme(theme: Theme.NormalMode, forButtons: toolbar.actionButtons)
}
func SELdidClickBack() {
toolbar.tabToolbarDelegate?.tabToolbarDidPressBack(toolbar, button: toolbar.backButton)
}
func SELdidLongPressBack(_ recognizer: UILongPressGestureRecognizer) {
if recognizer.state == UIGestureRecognizerState.began {
toolbar.tabToolbarDelegate?.tabToolbarDidLongPressBack(toolbar, button: toolbar.backButton)
}
}
func SELdidClickTabs() {
toolbar.tabToolbarDelegate?.tabToolbarDidPressTabs(toolbar, button: toolbar.tabsButton)
}
func SELdidLongPressTabs(_ recognizer: UILongPressGestureRecognizer) {
toolbar.tabToolbarDelegate?.tabToolbarDidLongPressTabs(toolbar, button: toolbar.tabsButton)
}
func SELdidClickForward() {
toolbar.tabToolbarDelegate?.tabToolbarDidPressForward(toolbar, button: toolbar.forwardButton)
}
func SELdidLongPressForward(_ recognizer: UILongPressGestureRecognizer) {
if recognizer.state == UIGestureRecognizerState.began {
toolbar.tabToolbarDelegate?.tabToolbarDidLongPressForward(toolbar, button: toolbar.forwardButton)
}
}
func SELdidClickMenu() {
toolbar.tabToolbarDelegate?.tabToolbarDidPressMenu(toolbar, button: toolbar.menuButton)
}
func SELdidClickStopReload() {
if loading {
toolbar.tabToolbarDelegate?.tabToolbarDidPressStop(toolbar, button: toolbar.stopReloadButton)
} else {
toolbar.tabToolbarDelegate?.tabToolbarDidPressReload(toolbar, button: toolbar.stopReloadButton)
}
}
func SELdidLongPressStopReload(_ recognizer: UILongPressGestureRecognizer) {
if recognizer.state == UIGestureRecognizerState.began && !loading {
toolbar.tabToolbarDelegate?.tabToolbarDidLongPressReload(toolbar, button: toolbar.stopReloadButton)
}
}
func updateReloadStatus(_ isLoading: Bool) {
loading = isLoading
}
}
class ToolbarButton: UIButton {
static let Themes: [String: Theme] = {
var themes = [String: Theme]()
var theme = Theme()
theme.buttonTintColor = UIColor(rgb: 0xd2d2d4)
theme.highlightButtonColor = UIColor(rgb: 0xAC39FF)
theme.disabledButtonColor = UIColor.gray
themes[Theme.PrivateMode] = theme
theme = Theme()
theme.buttonTintColor = UIColor(rgb: 0x272727)
theme.highlightButtonColor = UIColor(rgb: 0x00A2FE)
theme.disabledButtonColor = UIColor.lightGray
themes[Theme.NormalMode] = theme
return themes
}()
var selectedTintColor: UIColor!
var unselectedTintColor: UIColor!
var disabledTintColor: UIColor!
override init(frame: CGRect) {
super.init(frame: frame)
adjustsImageWhenHighlighted = false
selectedTintColor = tintColor
unselectedTintColor = tintColor
disabledTintColor = UIColor.gray
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override open var isHighlighted: Bool {
didSet {
self.tintColor = isHighlighted ? selectedTintColor : unselectedTintColor
}
}
override open var isEnabled: Bool {
didSet {
self.tintColor = isEnabled ? unselectedTintColor : disabledTintColor
}
}
override var tintColor: UIColor! {
didSet {
self.imageView?.tintColor = self.tintColor
}
}
}
extension ToolbarButton: Themeable {
func applyTheme(_ themeName: String) {
guard let theme = ToolbarButton.Themes[themeName] else {
log.error("Unable to apply unknown theme \(themeName)")
return
}
selectedTintColor = theme.highlightButtonColor
disabledTintColor = theme.disabledButtonColor
unselectedTintColor = theme.buttonTintColor
tintColor = isEnabled ? unselectedTintColor : disabledTintColor
imageView?.tintColor = tintColor
}
}
class TabToolbar: Toolbar, TabToolbarProtocol {
weak var tabToolbarDelegate: TabToolbarDelegate?
let tabsButton: TabsButton
let menuButton: ToolbarButton
let forwardButton: ToolbarButton
let backButton: ToolbarButton
let stopReloadButton: ToolbarButton
let actionButtons: [Themeable & UIButton]
var helper: TabToolbarHelper?
static let Themes: [String: Theme] = {
var themes = [String: Theme]()
var theme = Theme()
theme.backgroundColor = UIColor(rgb: 0x38383D)
themes[Theme.PrivateMode] = theme
theme = Theme()
theme.backgroundColor = UIConstants.AppBackgroundColor
themes[Theme.NormalMode] = theme
return themes
}()
// This has to be here since init() calls it
fileprivate override init(frame: CGRect) {
// And these have to be initialized in here or the compiler will get angry
backButton = ToolbarButton()
backButton.accessibilityIdentifier = "TabToolbar.backButton"
forwardButton = ToolbarButton()
forwardButton.accessibilityIdentifier = "TabToolbar.forwardButton"
stopReloadButton = ToolbarButton()
stopReloadButton.accessibilityIdentifier = "TabToolbar.stopReloadButton"
tabsButton = TabsButton()
tabsButton.accessibilityIdentifier = "TabToolbar.tabsButton"
menuButton = ToolbarButton()
menuButton.accessibilityIdentifier = "TabToolbar.menuButton"
actionButtons = [backButton, forwardButton, stopReloadButton, tabsButton, menuButton]
super.init(frame: frame)
helper = TabToolbarHelper(toolbar: self)
addButtons(actionButtons)
accessibilityNavigationStyle = .combined
accessibilityLabel = NSLocalizedString("Navigation Toolbar", comment: "Accessibility label for the navigation toolbar displayed at the bottom of the screen.")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func updateBackStatus(_ canGoBack: Bool) {
backButton.isEnabled = canGoBack
}
func updateForwardStatus(_ canGoForward: Bool) {
forwardButton.isEnabled = canGoForward
}
func updateReloadStatus(_ isLoading: Bool) {
helper?.updateReloadStatus(isLoading)
}
func updatePageStatus(_ isWebPage: Bool) {
stopReloadButton.isEnabled = isWebPage
}
func updateTabCount(_ count: Int, animated: Bool) {
tabsButton.updateTabCount(count, animated: animated)
}
override func draw(_ rect: CGRect) {
if let context = UIGraphicsGetCurrentContext() {
drawLine(context, start: CGPoint(x: 0, y: 0), end: CGPoint(x: frame.width, y: 0))
}
}
fileprivate func drawLine(_ context: CGContext, start: CGPoint, end: CGPoint) {
context.setStrokeColor(UIColor.black.withAlphaComponent(0.05).cgColor)
context.setLineWidth(2)
context.move(to: CGPoint(x: start.x, y: start.y))
context.addLine(to: CGPoint(x: end.x, y: end.y))
context.strokePath()
}
}
extension TabToolbar: Themeable {
func applyTheme(_ themeName: String) {
guard let theme = TabToolbar.Themes[themeName] else {
log.error("Unable to apply unknown theme \(themeName)")
return
}
backgroundColor = theme.backgroundColor!
helper?.setTheme(theme: themeName, forButtons: actionButtons)
}
}