mastodon-app-ufficiale-ipho.../MastodonSDK/Sources/MastodonUI/View/Control/ActionToolbarContainer.swift

309 lines
13 KiB
Swift

//
// ActionToolBarContainer.swift
// Mastodon
//
// Created by sxiaojian on 2021/2/1.
//
import UIKit
import MastodonAsset
import MastodonLocalization
import MastodonExtension
public protocol ActionToolbarContainerDelegate: AnyObject {
func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, buttonDidPressed button: UIButton, action: ActionToolbarContainer.Action)
func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, showReblogs action: UIAccessibilityCustomAction)
func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, showFavorites action: UIAccessibilityCustomAction)
}
public final class ActionToolbarContainer: UIView {
static let replyImage = UIImage(systemName: "arrowshape.turn.up.left")!.withRenderingMode(.alwaysTemplate)
static let reblogImage = UIImage(systemName: "arrow.2.squarepath")!.withRenderingMode(.alwaysTemplate)
static let starImage = UIImage(systemName: "star")!.withRenderingMode(.alwaysTemplate)
static let starFillImage = UIImage(systemName: "star.fill")!.withRenderingMode(.alwaysTemplate)
static let shareImage = UIImage(systemName: "square.and.arrow.up")!.withRenderingMode(.alwaysTemplate)
public let replyButton = HighlightDimmableButton()
public let reblogButton = HighlightDimmableButton()
public let favoriteButton = HighlightDimmableButton()
public let shareButton = HighlightDimmableButton()
public weak var delegate: ActionToolbarContainerDelegate?
private let container = UIStackView()
private let firstContainer = UIStackView()
private let secondContainer = UIStackView()
private var isAccessibilityCategory: Bool?
private var shareButtonWidthConstraint: NSLayoutConstraint?
public override init(frame: CGRect) {
super.init(frame: frame)
_init()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
_init()
}
}
extension ActionToolbarContainer {
private func _init() {
container.translatesAutoresizingMaskIntoConstraints = false
addSubview(container)
NSLayoutConstraint.activate([
container.topAnchor.constraint(equalTo: topAnchor),
container.leadingAnchor.constraint(equalTo: leadingAnchor),
trailingAnchor.constraint(equalTo: container.trailingAnchor),
bottomAnchor.constraint(equalTo: container.bottomAnchor),
])
replyButton.addTarget(self, action: #selector(ActionToolbarContainer.buttonDidPressed(_:)), for: .touchUpInside)
reblogButton.addTarget(self, action: #selector(ActionToolbarContainer.buttonDidPressed(_:)), for: .touchUpInside)
favoriteButton.addTarget(self, action: #selector(ActionToolbarContainer.buttonDidPressed(_:)), for: .touchUpInside)
shareButton.addTarget(self, action: #selector(ActionToolbarContainer.buttonDidPressed(_:)), for: .touchUpInside)
let buttons = [replyButton, reblogButton, favoriteButton, shareButton]
buttons.forEach { button in
button.tintColor = Asset.Colors.Button.actionToolbar.color
button.titleLabel?.font = UIFontMetrics(forTextStyle: .caption1)
.scaledFont(for: .monospacedDigitSystemFont(ofSize: 12, weight: .regular))
button.titleLabel?.adjustsFontForContentSizeCategory = true
button.setTitle("", for: .normal)
button.setTitleColor(.secondaryLabel, for: .normal)
button.expandEdgeInsets = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)
button.setInsets(forContentPadding: .zero, imageTitlePadding: 4)
button.adjustsImageSizeForAccessibilityContentSizeCategory = true
button.setContentCompressionResistancePriority(.defaultHigh + 100, for: .horizontal)
}
// add more expand for menu button
shareButton.expandEdgeInsets = UIEdgeInsets(top: -10, left: -20, bottom: -10, right: -20)
replyButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.reply
reblogButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.reblog // needs update to follow state
favoriteButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.favorite // needs update to follow state
shareButton.accessibilityLabel = L10n.Common.Controls.Actions.share
buttons.forEach { button in
button.contentHorizontalAlignment = .leading
}
replyButton.setImage(ActionToolbarContainer.replyImage, for: .normal)
reblogButton.setImage(ActionToolbarContainer.reblogImage, for: .normal)
favoriteButton.setImage(ActionToolbarContainer.starImage, for: .normal)
shareButton.setImage(ActionToolbarContainer.shareImage, for: .normal)
container.axis = .horizontal
container.distribution = .equalSpacing
replyButton.translatesAutoresizingMaskIntoConstraints = false
reblogButton.translatesAutoresizingMaskIntoConstraints = false
favoriteButton.translatesAutoresizingMaskIntoConstraints = false
shareButton.translatesAutoresizingMaskIntoConstraints = false
firstContainer.translatesAutoresizingMaskIntoConstraints = false
firstContainer.axis = .horizontal
firstContainer.distribution = .equalSpacing
secondContainer.translatesAutoresizingMaskIntoConstraints = false
secondContainer.axis = .horizontal
secondContainer.distribution = .equalSpacing
shareButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
shareButton.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
shareButtonWidthConstraint = replyButton.widthAnchor.constraint(equalTo: shareButton.widthAnchor)
traitCollectionDidChange(nil)
NSLayoutConstraint.activate([
replyButton.heightAnchor.constraint(equalToConstant: 36).priority(.defaultHigh),
replyButton.heightAnchor.constraint(equalTo: reblogButton.heightAnchor).priority(.defaultHigh),
replyButton.heightAnchor.constraint(equalTo: favoriteButton.heightAnchor).priority(.defaultHigh),
replyButton.heightAnchor.constraint(equalTo: shareButton.heightAnchor).priority(.defaultHigh),
replyButton.widthAnchor.constraint(equalTo: reblogButton.widthAnchor).priority(.defaultHigh),
replyButton.widthAnchor.constraint(equalTo: favoriteButton.widthAnchor).priority(.defaultHigh),
])
}
public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
let isAccessibilityCategory = traitCollection.preferredContentSizeCategory.isAccessibilityCategory
guard isAccessibilityCategory != self.isAccessibilityCategory else { return }
self.isAccessibilityCategory = isAccessibilityCategory
if isAccessibilityCategory {
container.axis = .vertical
container.spacing = 12
firstContainer.addArrangedSubview(replyButton)
firstContainer.addArrangedSubview(reblogButton)
container.addArrangedSubview(firstContainer)
secondContainer.addArrangedSubview(favoriteButton)
secondContainer.addArrangedSubview(shareButton)
container.addArrangedSubview(secondContainer)
} else {
container.axis = .horizontal
container.spacing = 0
container.addArrangedSubview(replyButton)
container.addArrangedSubview(reblogButton)
container.addArrangedSubview(favoriteButton)
container.addArrangedSubview(shareButton)
firstContainer.removeFromSuperview()
secondContainer.removeFromSuperview()
}
shareButtonWidthConstraint!.isActive = isAccessibilityCategory
}
}
extension ActionToolbarContainer {
public enum Action: String, CaseIterable {
case reply
case reblog
case like
case share
}
private func isReblogButtonHighlightStateDidChange(to isHighlight: Bool) {
let tintColor = isHighlight ? Asset.Colors.successGreen.color : Asset.Colors.Button.actionToolbar.color
reblogButton.tintColor = tintColor
reblogButton.setTitleColor(tintColor, for: .normal)
reblogButton.setTitleColor(tintColor, for: .highlighted)
}
private func isFavoriteButtonHighlightStateDidChange(to isHighlight: Bool) {
let tintColor = isHighlight ? Asset.Colors.systemOrange.color : Asset.Colors.Button.actionToolbar.color
favoriteButton.tintColor = tintColor
favoriteButton.setTitleColor(tintColor, for: .normal)
favoriteButton.setTitleColor(tintColor, for: .highlighted)
}
}
extension ActionToolbarContainer {
@objc private func buttonDidPressed(_ sender: UIButton) {
let _action: Action?
switch sender {
case replyButton: _action = .reply
case reblogButton: _action = .reblog
case favoriteButton: _action = .like
case shareButton: _action = .share
default: _action = nil
}
guard let action = _action else {
assertionFailure()
return
}
delegate?.actionToolbarContainer(self, buttonDidPressed: sender, action: action)
}
}
extension ActionToolbarContainer {
public func configureReply(count: Int, isEnabled: Bool) {
let title = ActionToolbarContainer.title(from: count)
replyButton.setTitle(title, for: .normal)
replyButton.accessibilityLabel = L10n.Common.Controls.Actions.reply
replyButton.accessibilityValue = L10n.Plural.Count.reply(count)
}
public func configureReblog(count: Int, isEnabled: Bool, isHighlighted: Bool) {
let title = ActionToolbarContainer.title(from: count)
reblogButton.setTitle(title, for: .normal)
reblogButton.accessibilityValue = L10n.Plural.Count.reblogA11y(count)
reblogButton.isEnabled = isEnabled
reblogButton.setImage(ActionToolbarContainer.reblogImage, for: .normal)
let tintColor = isHighlighted ? Asset.Colors.successGreen.color : Asset.Colors.Button.actionToolbar.color
reblogButton.tintColor = tintColor
reblogButton.setTitleColor(tintColor, for: .normal)
reblogButton.setTitleColor(tintColor, for: .highlighted)
if isHighlighted {
reblogButton.accessibilityTraits.insert(.selected)
reblogButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.A11YLabels.unreblog
} else {
reblogButton.accessibilityTraits.remove(.selected)
reblogButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.A11YLabels.reblog
}
reblogButton.accessibilityCustomActions = [
UIAccessibilityCustomAction(name: "Show All Reblogs") { [weak self] action in
guard let self = self else { return false }
self.delegate?.actionToolbarContainer(self, showReblogs: action)
return true
}
]
}
public func configureFavorite(count: Int, isEnabled: Bool, isHighlighted: Bool) {
let title = ActionToolbarContainer.title(from: count)
favoriteButton.setTitle(title, for: .normal)
favoriteButton.accessibilityValue = L10n.Plural.Count.favorite(count)
favoriteButton.isEnabled = isEnabled
let image = isHighlighted ? ActionToolbarContainer.starFillImage : ActionToolbarContainer.starImage
favoriteButton.setImage(image, for: .normal)
let tintColor = isHighlighted ? Asset.Colors.systemOrange.color : Asset.Colors.Button.actionToolbar.color
favoriteButton.tintColor = tintColor
favoriteButton.setTitleColor(tintColor, for: .normal)
favoriteButton.setTitleColor(tintColor, for: .highlighted)
if isHighlighted {
favoriteButton.accessibilityTraits.insert(.selected)
favoriteButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.unfavorite
} else {
favoriteButton.accessibilityTraits.remove(.selected)
favoriteButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.favorite
}
favoriteButton.accessibilityCustomActions = [
UIAccessibilityCustomAction(name: "Show All Favorites") { [weak self] action in
guard let self = self else { return false }
self.delegate?.actionToolbarContainer(self, showFavorites: action)
return true
}
]
}
}
extension ActionToolbarContainer {
private static func title(from number: Int?) -> String {
guard let number = number, number > 0 else { return "" }
return number.asAbbreviatedCountString()
}
}
extension ActionToolbarContainer {
public override var accessibilityElements: [Any]? {
get { [replyButton, reblogButton, favoriteButton, shareButton] }
set { }
}
}
#if DEBUG
import SwiftUI
struct ActionToolbarContainer_Previews: PreviewProvider {
static var previews: some View {
Group {
UIViewPreview(width: 300) {
ActionToolbarContainer()
}
.previewLayout(.fixed(width: 300, height: 44))
.previewDisplayName("Inline")
}
}
}
#endif