mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-05 23:38:38 +09:00
399 lines
16 KiB
Swift
399 lines
16 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 Shared
|
|
import SnapKit
|
|
import XCGLogger
|
|
|
|
private let log = Logger.browserLogger
|
|
|
|
protocol TabLocationViewDelegate {
|
|
func tabLocationViewDidTapLocation(_ tabLocationView: TabLocationView)
|
|
func tabLocationViewDidLongPressLocation(_ tabLocationView: TabLocationView)
|
|
func tabLocationViewDidTapReaderMode(_ tabLocationView: TabLocationView)
|
|
func tabLocationViewDidTapPageOptions(_ tabLocationView: TabLocationView, from button: UIButton)
|
|
func tabLocationViewDidLongPressPageOptions(_ tabLocationVIew: TabLocationView)
|
|
|
|
/// - returns: whether the long-press was handled by the delegate; i.e. return `false` when the conditions for even starting handling long-press were not satisfied
|
|
@discardableResult func tabLocationViewDidLongPressReaderMode(_ tabLocationView: TabLocationView) -> Bool
|
|
func tabLocationViewLocationAccessibilityActions(_ tabLocationView: TabLocationView) -> [UIAccessibilityCustomAction]?
|
|
}
|
|
|
|
struct TabLocationViewUX {
|
|
static let HostFontColor = UIColor.black
|
|
static let BaseURLFontColor = UIColor.gray
|
|
static let LocationContentInset = 8
|
|
static let URLBarPadding = 4
|
|
|
|
static let Themes: [String: Theme] = {
|
|
var themes = [String: Theme]()
|
|
var theme = Theme()
|
|
theme.URLFontColor = UIColor.lightGray
|
|
theme.textColor = UIColor(rgb: 0xf9f9fa)
|
|
theme.highlightButtonColor = UIConstants.PrivateModePurple
|
|
theme.buttonTintColor = UIColor(rgb: 0xADADb0)
|
|
theme.backgroundColor = UIColor(rgb: 0x636369)
|
|
themes[Theme.PrivateMode] = theme
|
|
|
|
theme = Theme()
|
|
theme.textColor = UIColor(rgb: 0x27)
|
|
theme.highlightButtonColor = UIColor(rgb: 0x00A2FE)
|
|
theme.buttonTintColor = UIColor(rgb: 0x737373)
|
|
theme.backgroundColor = .white
|
|
themes[Theme.NormalMode] = theme
|
|
|
|
return themes
|
|
}()
|
|
}
|
|
|
|
class TabLocationView: UIView {
|
|
var delegate: TabLocationViewDelegate?
|
|
var longPressRecognizer: UILongPressGestureRecognizer!
|
|
var tapRecognizer: UITapGestureRecognizer!
|
|
|
|
dynamic var baseURLFontColor: UIColor = TabLocationViewUX.BaseURLFontColor {
|
|
didSet { updateTextWithURL() }
|
|
}
|
|
|
|
var url: URL? {
|
|
didSet {
|
|
let wasHidden = lockImageView.isHidden
|
|
lockImageView.isHidden = url?.scheme != "https"
|
|
if wasHidden != lockImageView.isHidden {
|
|
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil)
|
|
}
|
|
updateTextWithURL()
|
|
pageOptionsButton.isHidden = (url == nil)
|
|
setNeedsUpdateConstraints()
|
|
}
|
|
}
|
|
|
|
var readerModeState: ReaderModeState {
|
|
get {
|
|
return readerModeButton.readerModeState
|
|
}
|
|
set (newReaderModeState) {
|
|
if newReaderModeState != self.readerModeButton.readerModeState {
|
|
let wasHidden = readerModeButton.isHidden
|
|
self.readerModeButton.readerModeState = newReaderModeState
|
|
readerModeButton.isHidden = (newReaderModeState == ReaderModeState.unavailable)
|
|
separatorLine.isHidden = readerModeButton.isHidden
|
|
if wasHidden != readerModeButton.isHidden {
|
|
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil)
|
|
if !readerModeButton.isHidden {
|
|
// Delay the Reader Mode accessibility announcement briefly to prevent interruptions.
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) {
|
|
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, Strings.ReaderModeAvailableVoiceOverAnnouncement)
|
|
}
|
|
}
|
|
}
|
|
UIView.animate(withDuration: 0.1, animations: { () -> Void in
|
|
if newReaderModeState == ReaderModeState.unavailable {
|
|
self.readerModeButton.alpha = 0.0
|
|
} else {
|
|
self.readerModeButton.alpha = 1.0
|
|
}
|
|
self.setNeedsUpdateConstraints()
|
|
self.layoutIfNeeded()
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
lazy var placeholder: NSAttributedString = {
|
|
let placeholderText = NSLocalizedString("Search or enter address", comment: "The text shown in the URL bar on about:home")
|
|
return NSAttributedString(string: placeholderText, attributes: [NSForegroundColorAttributeName: UIColor.gray])
|
|
}()
|
|
|
|
lazy var urlTextField: UITextField = {
|
|
let urlTextField = DisplayTextField()
|
|
|
|
self.longPressRecognizer.delegate = self
|
|
urlTextField.addGestureRecognizer(self.longPressRecognizer)
|
|
self.tapRecognizer.delegate = self
|
|
urlTextField.addGestureRecognizer(self.tapRecognizer)
|
|
|
|
// Prevent the field from compressing the toolbar buttons on the 4S in landscape.
|
|
urlTextField.setContentCompressionResistancePriority(250, for: UILayoutConstraintAxis.horizontal)
|
|
urlTextField.attributedPlaceholder = self.placeholder
|
|
urlTextField.accessibilityIdentifier = "url"
|
|
urlTextField.accessibilityActionsSource = self
|
|
urlTextField.font = UIConstants.DefaultChromeFont
|
|
urlTextField.backgroundColor = .clear
|
|
return urlTextField
|
|
}()
|
|
|
|
fileprivate lazy var lockImageView: UIImageView = {
|
|
let lockImageView = UIImageView(image: UIImage.templateImageNamed("lock_verified"))
|
|
lockImageView.isHidden = true
|
|
lockImageView.tintColor = UIColor(rgb: 0x16DA00)
|
|
lockImageView.isAccessibilityElement = true
|
|
lockImageView.contentMode = UIViewContentMode.center
|
|
lockImageView.accessibilityLabel = NSLocalizedString("Secure connection", comment: "Accessibility label for the lock icon, which is only present if the connection is secure")
|
|
return lockImageView
|
|
}()
|
|
|
|
fileprivate lazy var readerModeButton: ReaderModeButton = {
|
|
let readerModeButton = ReaderModeButton(frame: CGRect.zero)
|
|
readerModeButton.isHidden = true
|
|
readerModeButton.addTarget(self, action: #selector(TabLocationView.SELtapReaderModeButton), for: .touchUpInside)
|
|
readerModeButton.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(TabLocationView.SELlongPressReaderModeButton(_:))))
|
|
readerModeButton.isAccessibilityElement = true
|
|
readerModeButton.imageView?.contentMode = UIViewContentMode.scaleAspectFit
|
|
readerModeButton.accessibilityLabel = NSLocalizedString("Reader View", comment: "Accessibility label for the Reader View button")
|
|
readerModeButton.accessibilityIdentifier = "TabLocationView.readerModeButton"
|
|
readerModeButton.accessibilityCustomActions = [UIAccessibilityCustomAction(name: NSLocalizedString("Add to Reading List", comment: "Accessibility label for action adding current page to reading list."), target: self, selector: #selector(TabLocationView.SELreaderModeCustomAction))]
|
|
return readerModeButton
|
|
}()
|
|
|
|
lazy var pageOptionsButton: ToolbarButton = {
|
|
let pageOptionsButton = ToolbarButton(frame: CGRect.zero)
|
|
pageOptionsButton.setImage(UIImage.templateImageNamed("menu-More-Options"), for: .normal)
|
|
pageOptionsButton.isHidden = true
|
|
pageOptionsButton.addTarget(self, action: #selector(TabLocationView.SELDidPressPageOptionsButton), for: .touchUpInside)
|
|
pageOptionsButton.isAccessibilityElement = true
|
|
pageOptionsButton.imageView?.contentMode = .center
|
|
pageOptionsButton.accessibilityLabel = NSLocalizedString("Page Options Menu", comment: "Accessibility label for the Page Options menu button")
|
|
pageOptionsButton.accessibilityIdentifier = "TabLocationView.pageOptionsButton"
|
|
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(TabLocationView.SELDidLongPressPageOptionsButton))
|
|
pageOptionsButton.addGestureRecognizer(longPressGesture)
|
|
return pageOptionsButton
|
|
}()
|
|
|
|
lazy var separatorLine: UIView = {
|
|
let line = UIView()
|
|
line.layer.cornerRadius = 2
|
|
line.backgroundColor = UIColor(rgb: 0xE5E5E5)
|
|
line.isHidden = true
|
|
return line
|
|
}()
|
|
|
|
override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
|
|
longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(TabLocationView.SELlongPressLocation(_:)))
|
|
tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(TabLocationView.SELtapLocation(_:)))
|
|
|
|
addSubview(urlTextField)
|
|
addSubview(lockImageView)
|
|
addSubview(readerModeButton)
|
|
addSubview(pageOptionsButton)
|
|
addSubview(separatorLine)
|
|
|
|
lockImageView.snp.makeConstraints { make in
|
|
make.size.equalTo(24)
|
|
make.centerY.equalTo(self)
|
|
make.leading.equalTo(self).offset(9)
|
|
}
|
|
|
|
pageOptionsButton.snp.makeConstraints { make in
|
|
make.centerY.equalTo(self)
|
|
make.trailing.equalTo(self)
|
|
make.width.equalTo(44)
|
|
make.height.equalTo(self)
|
|
}
|
|
|
|
separatorLine.snp.makeConstraints { make in
|
|
make.width.equalTo(1)
|
|
make.height.equalTo(26)
|
|
make.trailing.equalTo(pageOptionsButton.snp.leading)
|
|
make.centerY.equalTo(self)
|
|
}
|
|
|
|
readerModeButton.snp.makeConstraints { make in
|
|
make.centerY.equalTo(self)
|
|
make.trailing.equalTo(separatorLine.snp.leading).offset(-9)
|
|
make.size.equalTo(24)
|
|
}
|
|
}
|
|
|
|
override var accessibilityElements: [Any]? {
|
|
get {
|
|
return [lockImageView, urlTextField, readerModeButton, pageOptionsButton].filter { !$0.isHidden }
|
|
}
|
|
set {
|
|
super.accessibilityElements = newValue
|
|
}
|
|
}
|
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func updateConstraints() {
|
|
urlTextField.snp.remakeConstraints { make in
|
|
make.top.bottom.equalTo(self)
|
|
|
|
if lockImageView.isHidden {
|
|
make.leading.equalTo(self).offset(TabLocationViewUX.LocationContentInset)
|
|
} else {
|
|
make.leading.equalTo(self.lockImageView.snp.trailing).offset(TabLocationViewUX.URLBarPadding)
|
|
}
|
|
|
|
if readerModeButton.isHidden {
|
|
make.trailing.equalTo(self.pageOptionsButton.snp.leading).offset(-TabLocationViewUX.URLBarPadding)
|
|
} else {
|
|
make.trailing.equalTo(self.readerModeButton.snp.leading).offset(-TabLocationViewUX.URLBarPadding)
|
|
}
|
|
}
|
|
|
|
super.updateConstraints()
|
|
}
|
|
|
|
func SELtapReaderModeButton() {
|
|
delegate?.tabLocationViewDidTapReaderMode(self)
|
|
}
|
|
|
|
func SELlongPressReaderModeButton(_ recognizer: UILongPressGestureRecognizer) {
|
|
if recognizer.state == UIGestureRecognizerState.began {
|
|
delegate?.tabLocationViewDidLongPressReaderMode(self)
|
|
}
|
|
}
|
|
|
|
func SELDidPressPageOptionsButton(_ button: UIButton) {
|
|
delegate?.tabLocationViewDidTapPageOptions(self, from: button)
|
|
}
|
|
|
|
func SELDidLongPressPageOptionsButton(_ recognizer: UILongPressGestureRecognizer) {
|
|
delegate?.tabLocationViewDidLongPressPageOptions(self)
|
|
}
|
|
|
|
func SELlongPressLocation(_ recognizer: UITapGestureRecognizer) {
|
|
if recognizer.state == UIGestureRecognizerState.began {
|
|
delegate?.tabLocationViewDidLongPressLocation(self)
|
|
}
|
|
}
|
|
|
|
func SELtapLocation(_ recognizer: UITapGestureRecognizer) {
|
|
delegate?.tabLocationViewDidTapLocation(self)
|
|
}
|
|
|
|
func SELreaderModeCustomAction() -> Bool {
|
|
return delegate?.tabLocationViewDidLongPressReaderMode(self) ?? false
|
|
}
|
|
|
|
fileprivate func updateTextWithURL() {
|
|
if let host = url?.host, AppConstants.MOZ_PUNYCODE {
|
|
urlTextField.text = url?.absoluteString.replacingOccurrences(of: host, with: host.asciiHostToUTF8())
|
|
} else {
|
|
urlTextField.text = url?.absoluteString
|
|
}
|
|
// remove https:// (the scheme) from the url when displaying
|
|
if let scheme = url?.scheme, let range = url?.absoluteString.range(of: "\(scheme)://") {
|
|
urlTextField.text = url?.absoluteString.replacingCharacters(in: range, with: "")
|
|
}
|
|
}
|
|
}
|
|
|
|
extension TabLocationView: UIGestureRecognizerDelegate {
|
|
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
|
return true
|
|
}
|
|
|
|
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
|
// If the longPressRecognizer is active, fail all other recognizers to avoid conflicts.
|
|
return gestureRecognizer == longPressRecognizer
|
|
}
|
|
}
|
|
|
|
extension TabLocationView: AccessibilityActionsSource {
|
|
func accessibilityCustomActionsForView(_ view: UIView) -> [UIAccessibilityCustomAction]? {
|
|
if view === urlTextField {
|
|
return delegate?.tabLocationViewLocationAccessibilityActions(self)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
extension TabLocationView: Themeable {
|
|
func applyTheme(_ themeName: String) {
|
|
guard let theme = TabLocationViewUX.Themes[themeName] else {
|
|
log.error("Unable to apply unknown theme \(themeName)")
|
|
return
|
|
}
|
|
let isPrivate = themeName == Theme.PrivateMode
|
|
backgroundColor = theme.backgroundColor
|
|
urlTextField.textColor = theme.textColor
|
|
readerModeButton.selectedTintColor = theme.highlightButtonColor
|
|
readerModeButton.unselectedTintColor = theme.buttonTintColor
|
|
pageOptionsButton.selectedTintColor = theme.highlightButtonColor
|
|
|
|
pageOptionsButton.unselectedTintColor = isPrivate ? UIColor(rgb: 0xD2d2d4) : UIColor(rgb: 0x272727)
|
|
pageOptionsButton.tintColor = pageOptionsButton.unselectedTintColor
|
|
separatorLine.backgroundColor = isPrivate ? UIColor(rgb: 0x3f3f43) : UIColor(rgb: 0xE5E5E5)
|
|
}
|
|
}
|
|
|
|
class ReaderModeButton: UIButton {
|
|
var selectedTintColor: UIColor?
|
|
var unselectedTintColor: UIColor?
|
|
override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
adjustsImageWhenHighlighted = false
|
|
setImage(UIImage.templateImageNamed("reader"), for: .normal)
|
|
}
|
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override var isSelected: Bool {
|
|
didSet {
|
|
self.tintColor = (isHighlighted || isSelected) ? selectedTintColor : unselectedTintColor
|
|
}
|
|
}
|
|
|
|
override open var isHighlighted: Bool {
|
|
didSet {
|
|
self.tintColor = (isHighlighted || isSelected) ? selectedTintColor : unselectedTintColor
|
|
}
|
|
}
|
|
|
|
override var tintColor: UIColor! {
|
|
didSet {
|
|
self.imageView?.tintColor = self.tintColor
|
|
}
|
|
}
|
|
|
|
var _readerModeState: ReaderModeState = ReaderModeState.unavailable
|
|
|
|
var readerModeState: ReaderModeState {
|
|
get {
|
|
return _readerModeState
|
|
}
|
|
set (newReaderModeState) {
|
|
_readerModeState = newReaderModeState
|
|
switch _readerModeState {
|
|
case .available:
|
|
self.isEnabled = true
|
|
self.isSelected = false
|
|
case .unavailable:
|
|
self.isEnabled = false
|
|
self.isSelected = false
|
|
case .active:
|
|
self.isEnabled = true
|
|
self.isSelected = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private class DisplayTextField: UITextField {
|
|
weak var accessibilityActionsSource: AccessibilityActionsSource?
|
|
|
|
override var accessibilityCustomActions: [UIAccessibilityCustomAction]? {
|
|
get {
|
|
return accessibilityActionsSource?.accessibilityCustomActionsForView(self)
|
|
}
|
|
set {
|
|
super.accessibilityCustomActions = newValue
|
|
}
|
|
}
|
|
|
|
fileprivate override var canBecomeFirstResponder: Bool {
|
|
return false
|
|
}
|
|
}
|