Move the post author information to a custom subview that handles accessibility

This commit is contained in:
Jed Fox 2022-10-31 12:01:14 -04:00
parent 87e05ecdab
commit 3876855bc9
No known key found for this signature in database
GPG Key ID: 0B61D18EA54B47E1
5 changed files with 340 additions and 219 deletions

View File

@ -97,12 +97,7 @@ extension StatusThreadRootTableViewCell {
get {
var elements = [
statusView.headerContainerView,
statusView.avatarButton,
statusView.authorNameLabel,
statusView.menuButton,
statusView.authorUsernameLabel,
statusView.dateLabel,
statusView.contentSensitiveeToggleButton,
statusView.authorView,
statusView.spoilerOverlayView,
statusView.contentMetaText.textView,
statusView.mediaGridContainerView,
@ -112,10 +107,6 @@ extension StatusThreadRootTableViewCell {
statusView.statusMetricView
]
if !statusView.viewModel.isMediaSensitive {
elements.removeAll(where: { $0 === statusView.contentSensitiveeToggleButton })
}
if statusView.viewModel.isContentReveal {
elements.removeAll(where: { $0 === statusView.spoilerOverlayView })
} else {

View File

@ -431,7 +431,7 @@ extension NotificationView: AdaptiveContainerView {
}
extension NotificationView {
public typealias AuthorMenuContext = StatusView.AuthorMenuContext
public typealias AuthorMenuContext = StatusAuthorView.AuthorMenuContext
public func setupAuthorMenu(menuContext: AuthorMenuContext) -> UIMenu {
var actions: [MastodonMenu.Action] = []

View File

@ -0,0 +1,299 @@
//
// StatusAuthorView.swift
//
//
// Created by Jed Fox on 2022-10-31.
//
import os.log
import UIKit
import Combine
import Meta
import MetaTextKit
import MastodonAsset
import MastodonLocalization
public class StatusAuthorView: UIStackView {
let logger = Logger(subsystem: "StatusAuthorView", category: "View")
private var _disposeBag = Set<AnyCancellable>() // which lifetime same to view scope
weak var statusView: StatusView?
// accessibility actions
var authorActions = [UIAccessibilityCustomAction]()
// avatar
public let avatarButton = AvatarButton()
// author name
public let authorNameLabel = MetaLabel(style: .statusName)
// author username
public let authorUsernameLabel = MetaLabel(style: .statusUsername)
public let usernameTrialingDotLabel: MetaLabel = {
let label = MetaLabel(style: .statusUsername)
label.configure(content: PlaintextMetaContent(string: "·"))
return label
}()
// timestamp
public let dateLabel = MetaLabel(style: .statusUsername)
public let menuButton: UIButton = {
let button = HitTestExpandedButton(type: .system)
button.expandEdgeInsets = UIEdgeInsets(top: -20, left: -10, bottom: -5, right: -10)
button.tintColor = Asset.Colors.Label.secondary.color
let image = UIImage(systemName: "ellipsis", withConfiguration: UIImage.SymbolConfiguration(font: .systemFont(ofSize: 15)))
button.setImage(image, for: .normal)
button.accessibilityLabel = L10n.Common.Controls.Status.Actions.menu
return button
}()
public let contentSensitiveeToggleButton: UIButton = {
let button = HitTestExpandedButton(type: .system)
button.expandEdgeInsets = UIEdgeInsets(top: -5, left: -10, bottom: -20, right: -10)
button.tintColor = Asset.Colors.Label.secondary.color
button.imageView?.contentMode = .scaleAspectFill
button.imageView?.clipsToBounds = false
let image = UIImage(systemName: "eye.slash.fill", withConfiguration: UIImage.SymbolConfiguration(font: .systemFont(ofSize: 15)))
button.setImage(image, for: .normal)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
_init()
}
required init(coder: NSCoder) {
super.init(coder: coder)
_init()
}
func layout(style: StatusView.Style) {
switch style {
case .inline: layoutBase()
case .plain: layoutBase()
case .report: layoutReport()
case .notification: layoutBase()
case .notificationQuote: layoutNotificationQuote()
case .composeStatusReplica: layoutComposeStatusReplica()
case .composeStatusAuthor: layoutComposeStatusAuthor()
}
}
public override var accessibilityElements: [Any]? {
get { [] }
set {}
}
public override var accessibilityCustomActions: [UIAccessibilityCustomAction]? {
get {
var actions = authorActions
if !contentSensitiveeToggleButton.isHidden {
actions.append(UIAccessibilityCustomAction(
name: contentSensitiveeToggleButton.accessibilityLabel!,
image: contentSensitiveeToggleButton.image(for: .normal),
actionHandler: { _ in
self.contentSensitiveeToggleButtonDidPressed(self.contentSensitiveeToggleButton)
return true
}
))
}
return actions
}
set {}
}
public override func accessibilityActivate() -> Bool {
guard let statusView = statusView else { return false }
statusView.delegate?.statusView(statusView, authorAvatarButtonDidPressed: avatarButton)
return true
}
}
extension StatusAuthorView {
func _init() {
axis = .horizontal
spacing = 12
isAccessibilityElement = true
UIContentSizeCategory.publisher
.sink { [unowned self] category in
axis = category > .accessibilityLarge ? .vertical : .horizontal
alignment = category > .accessibilityLarge ? .leading : .center
}
.store(in: &_disposeBag)
// avatar button
avatarButton.addTarget(self, action: #selector(StatusAuthorView.authorAvatarButtonDidPressed(_:)), for: .touchUpInside)
authorNameLabel.isUserInteractionEnabled = false
authorUsernameLabel.isUserInteractionEnabled = false
// contentSensitiveeToggleButton
contentSensitiveeToggleButton.addTarget(self, action: #selector(StatusAuthorView.contentSensitiveeToggleButtonDidPressed(_:)), for: .touchUpInside)
// dateLabel
dateLabel.isUserInteractionEnabled = false
}
}
extension StatusAuthorView {
public struct AuthorMenuContext {
public let name: String
public let isMuting: Bool
public let isBlocking: Bool
public let isMyself: Bool
}
public func setupAuthorMenu(menuContext: AuthorMenuContext) -> (UIMenu, [UIAccessibilityCustomAction]) {
var actions: [MastodonMenu.Action] = []
actions = [
.muteUser(.init(
name: menuContext.name,
isMuting: menuContext.isMuting
)),
.blockUser(.init(
name: menuContext.name,
isBlocking: menuContext.isBlocking
)),
.reportUser(
.init(name: menuContext.name)
),
]
if menuContext.isMyself {
actions.append(.deleteStatus)
}
let menu = MastodonMenu.setupMenu(
actions: actions,
delegate: self.statusView!
)
let accessibilityActions = MastodonMenu.setupAccessibilityActions(
actions: actions,
delegate: self.statusView!
)
return (menu, accessibilityActions)
}
}
extension StatusAuthorView {
@objc private func authorAvatarButtonDidPressed(_ sender: UIButton) {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
guard let statusView = statusView else { return }
statusView.delegate?.statusView(statusView, authorAvatarButtonDidPressed: avatarButton)
}
@objc private func contentSensitiveeToggleButtonDidPressed(_ sender: UIButton) {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
guard let statusView = statusView else { return }
statusView.delegate?.statusView(statusView, contentSensitiveeToggleButtonDidPressed: sender)
}
}
extension StatusAuthorView {
// author container: H - [ avatarButton | authorMetaContainer ]
private func layoutBase() {
// avatarButton
let authorAvatarButtonSize = CGSize(width: 46, height: 46)
avatarButton.size = authorAvatarButtonSize
avatarButton.avatarImageView.imageViewSize = authorAvatarButtonSize
avatarButton.translatesAutoresizingMaskIntoConstraints = false
addArrangedSubview(avatarButton)
NSLayoutConstraint.activate([
avatarButton.widthAnchor.constraint(equalToConstant: authorAvatarButtonSize.width).priority(.required - 1),
avatarButton.heightAnchor.constraint(equalToConstant: authorAvatarButtonSize.height).priority(.required - 1),
])
avatarButton.setContentHuggingPriority(.required - 1, for: .vertical)
avatarButton.setContentCompressionResistancePriority(.required - 1, for: .vertical)
// authorMetaContainer: V - [ authorPrimaryMetaContainer | authorSecondaryMetaContainer ]
let authorMetaContainer = UIStackView()
authorMetaContainer.axis = .vertical
authorMetaContainer.spacing = 4
addArrangedSubview(authorMetaContainer)
// authorPrimaryMetaContainer: H - [ authorNameLabel | (padding) | menuButton ]
let authorPrimaryMetaContainer = UIStackView()
authorPrimaryMetaContainer.axis = .horizontal
authorPrimaryMetaContainer.spacing = 10
authorMetaContainer.addArrangedSubview(authorPrimaryMetaContainer)
// authorNameLabel
authorPrimaryMetaContainer.addArrangedSubview(authorNameLabel)
authorNameLabel.setContentHuggingPriority(.required - 10, for: .horizontal)
authorNameLabel.setContentCompressionResistancePriority(.required - 10, for: .horizontal)
authorPrimaryMetaContainer.addArrangedSubview(UIView())
// menuButton
authorPrimaryMetaContainer.addArrangedSubview(menuButton)
menuButton.setContentHuggingPriority(.required - 2, for: .horizontal)
menuButton.setContentCompressionResistancePriority(.required - 2, for: .horizontal)
// authorSecondaryMetaContainer: H - [ authorUsername | usernameTrialingDotLabel | dateLabel | (padding) | contentSensitiveeToggleButton ]
let authorSecondaryMetaContainer = UIStackView()
authorSecondaryMetaContainer.axis = .horizontal
authorSecondaryMetaContainer.spacing = 4
authorMetaContainer.addArrangedSubview(authorSecondaryMetaContainer)
authorSecondaryMetaContainer.addArrangedSubview(authorUsernameLabel)
authorUsernameLabel.setContentHuggingPriority(.required - 8, for: .horizontal)
authorUsernameLabel.setContentCompressionResistancePriority(.required - 8, for: .horizontal)
authorSecondaryMetaContainer.addArrangedSubview(usernameTrialingDotLabel)
usernameTrialingDotLabel.setContentHuggingPriority(.required - 2, for: .horizontal)
usernameTrialingDotLabel.setContentCompressionResistancePriority(.required - 2, for: .horizontal)
authorSecondaryMetaContainer.addArrangedSubview(dateLabel)
dateLabel.setContentHuggingPriority(.required - 1, for: .horizontal)
dateLabel.setContentCompressionResistancePriority(.required - 1, for: .horizontal)
authorSecondaryMetaContainer.addArrangedSubview(UIView())
contentSensitiveeToggleButton.translatesAutoresizingMaskIntoConstraints = false
authorSecondaryMetaContainer.addArrangedSubview(contentSensitiveeToggleButton)
NSLayoutConstraint.activate([
contentSensitiveeToggleButton.heightAnchor.constraint(equalTo: authorUsernameLabel.heightAnchor, multiplier: 1.0).priority(.required - 1),
contentSensitiveeToggleButton.widthAnchor.constraint(equalTo: contentSensitiveeToggleButton.heightAnchor, multiplier: 1.0).priority(.required - 1),
])
authorUsernameLabel.setContentHuggingPriority(.required - 1, for: .vertical)
authorUsernameLabel.setContentCompressionResistancePriority(.required - 1, for: .vertical)
contentSensitiveeToggleButton.setContentHuggingPriority(.defaultLow, for: .vertical)
contentSensitiveeToggleButton.setContentHuggingPriority(.defaultLow, for: .horizontal)
contentSensitiveeToggleButton.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
contentSensitiveeToggleButton.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
}
func layoutReport() {
layoutBase()
menuButton.removeFromSuperview()
}
func layoutNotificationQuote() {
layoutBase()
contentSensitiveeToggleButton.removeFromSuperview()
menuButton.removeFromSuperview()
}
func layoutComposeStatusReplica() {
layoutBase()
avatarButton.isUserInteractionEnabled = false
menuButton.removeFromSuperview()
}
func layoutComposeStatusAuthor() {
layoutBase()
avatarButton.isUserInteractionEnabled = false
menuButton.removeFromSuperview()
usernameTrialingDotLabel.removeFromSuperview()
dateLabel.removeFromSuperview()
}
}

View File

@ -208,6 +208,7 @@ extension StatusView.ViewModel {
}
private func bindAuthor(statusView: StatusView) {
let authorView = statusView.authorView
// avatar
Publishers.CombineLatest(
$authorAvatarImage.removeDuplicates(),
@ -221,30 +222,31 @@ extension StatusView.ViewModel {
return AvatarImageView.Configuration(url: url)
}
}()
statusView.avatarButton.avatarImageView.configure(configuration: configuration)
statusView.avatarButton.avatarImageView.configure(cornerConfiguration: .init(corner: .fixed(radius: 12)))
authorView.avatarButton.avatarImageView.configure(configuration: configuration)
authorView.avatarButton.avatarImageView.configure(cornerConfiguration: .init(corner: .fixed(radius: 12)))
}
.store(in: &disposeBag)
// name
$authorName
.sink { metaContent in
let metaContent = metaContent ?? PlaintextMetaContent(string: " ")
statusView.authorNameLabel.configure(content: metaContent)
authorView.authorNameLabel.configure(content: metaContent)
}
.store(in: &disposeBag)
// username
$authorUsername
let usernamePublisher = $authorUsername
.map { text -> String in
guard let text = text else { return "" }
return "@\(text)"
}
usernamePublisher
.sink { username in
let metaContent = PlaintextMetaContent(string: username)
statusView.authorUsernameLabel.configure(content: metaContent)
authorView.authorUsernameLabel.configure(content: metaContent)
}
.store(in: &disposeBag)
// timestamp
Publishers.CombineLatest(
let timestampPublisher = Publishers.CombineLatest(
$timestamp,
timestampUpdatePublisher.prepend(Date()).eraseToAnyPublisher()
)
@ -256,14 +258,23 @@ extension StatusView.ViewModel {
return text
}
.removeDuplicates()
.assign(to: &$timestampText)
timestampPublisher.assign(to: &$timestampText)
$timestampText
.sink { [weak self] text in
guard let _ = self else { return }
statusView.dateLabel.configure(content: PlaintextMetaContent(string: text))
authorView.dateLabel.configure(content: PlaintextMetaContent(string: text))
}
.store(in: &disposeBag)
// accessibility label
Publishers.CombineLatest3($authorName, usernamePublisher, timestampPublisher)
.map { name, username, timestamp in
"\(name?.string ?? "") \(username), \(timestamp)"
}
.assign(to: \.accessibilityLabel, on: authorView)
.store(in: &disposeBag)
}
private func bindContent(statusView: StatusView) {
@ -326,7 +337,7 @@ extension StatusView.ViewModel {
// eye: when media is hidden
// eye-slash: when media display
let image = isSensitiveToggled ? UIImage(systemName: "eye.slash.fill") : UIImage(systemName: "eye.fill")
statusView.contentSensitiveeToggleButton.setImage(image, for: .normal)
statusView.authorView.contentSensitiveeToggleButton.setImage(image, for: .normal)
}
.store(in: &disposeBag)
}
@ -566,6 +577,7 @@ extension StatusView.ViewModel {
}
private func bindMenu(statusView: StatusView) {
let authorView = statusView.authorView
Publishers.CombineLatest4(
$authorName,
$isMuting,
@ -574,18 +586,20 @@ extension StatusView.ViewModel {
)
.sink { authorName, isMuting, isBlocking, isMyself in
guard let name = authorName?.string else {
statusView.menuButton.menu = nil
statusView.authorView.menuButton.menu = nil
return
}
let menuContext = StatusView.AuthorMenuContext(
let menuContext = StatusAuthorView.AuthorMenuContext(
name: name,
isMuting: isMuting,
isBlocking: isBlocking,
isMyself: isMyself
)
statusView.menuButton.menu = statusView.setupAuthorMenu(menuContext: menuContext)
statusView.menuButton.showsMenuAsPrimaryAction = true
let (menu, actions) = authorView.setupAuthorMenu(menuContext: menuContext)
authorView.menuButton.menu = menu
authorView.authorActions = actions
authorView.menuButton.showsMenuAsPrimaryAction = true
}
.store(in: &disposeBag)
}
@ -653,7 +667,7 @@ extension StatusView.ViewModel {
isContentReveal ? L10n.Scene.Compose.Accessibility.enableContentWarning : L10n.Scene.Compose.Accessibility.disableContentWarning
}
.sink { label in
statusView.contentSensitiveeToggleButton.accessibilityLabel = label
statusView.authorView.contentSensitiveeToggleButton.accessibilityLabel = label
}
.store(in: &disposeBag)

View File

@ -75,52 +75,8 @@ public final class StatusView: UIView {
// author
let authorAdaptiveMarginContainerView = AdaptiveMarginContainerView()
let authorContainerView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.spacing = 12
return stackView
}()
// avatar
public let avatarButton = AvatarButton()
// author name
public let authorNameLabel = MetaLabel(style: .statusName)
// author username
public let authorUsernameLabel = MetaLabel(style: .statusUsername)
public let usernameTrialingDotLabel: MetaLabel = {
let label = MetaLabel(style: .statusUsername)
label.configure(content: PlaintextMetaContent(string: "·"))
return label
}()
public let authorView = StatusAuthorView()
// timestamp
public let dateLabel = MetaLabel(style: .statusUsername)
public let menuButton: UIButton = {
let button = HitTestExpandedButton(type: .system)
button.expandEdgeInsets = UIEdgeInsets(top: -20, left: -10, bottom: -5, right: -10)
button.tintColor = Asset.Colors.Label.secondary.color
let image = UIImage(systemName: "ellipsis", withConfiguration: UIImage.SymbolConfiguration(font: .systemFont(ofSize: 15)))
button.setImage(image, for: .normal)
button.accessibilityLabel = L10n.Common.Controls.Status.Actions.menu
return button
}()
public let contentSensitiveeToggleButton: UIButton = {
let button = HitTestExpandedButton(type: .system)
button.expandEdgeInsets = UIEdgeInsets(top: -5, left: -10, bottom: -20, right: -10)
button.tintColor = Asset.Colors.Label.secondary.color
button.imageView?.contentMode = .scaleAspectFill
button.imageView?.clipsToBounds = false
let image = UIImage(systemName: "eye.slash.fill", withConfiguration: UIImage.SymbolConfiguration(font: .systemFont(ofSize: 15)))
button.setImage(image, for: .normal)
return button
}()
// content
let contentAdaptiveMarginContainerView = AdaptiveMarginContainerView()
let contentContainer = UIStackView()
@ -239,7 +195,7 @@ public final class StatusView: UIView {
viewModel.objects.removeAll()
viewModel.prepareForReuse()
avatarButton.avatarImageView.cancelTask()
authorView.avatarButton.avatarImageView.cancelTask()
if var snapshot = pollTableViewDiffableDataSource?.snapshot() {
snapshot.deleteAllItems()
if #available(iOS 15.0, *) {
@ -288,18 +244,10 @@ extension StatusView {
let headerTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
headerTapGestureRecognizer.addTarget(self, action: #selector(StatusView.headerDidPressed(_:)))
headerContainerView.addGestureRecognizer(headerTapGestureRecognizer)
// avatar button
avatarButton.addTarget(self, action: #selector(StatusView.authorAvatarButtonDidPressed(_:)), for: .touchUpInside)
authorNameLabel.isUserInteractionEnabled = false
authorUsernameLabel.isUserInteractionEnabled = false
// contentSensitiveeToggleButton
contentSensitiveeToggleButton.addTarget(self, action: #selector(StatusView.contentSensitiveeToggleButtonDidPressed(_:)), for: .touchUpInside)
// dateLabel
dateLabel.isUserInteractionEnabled = false
// author view
authorView.statusView = self
// content warning
let spoilerOverlayViewTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
spoilerOverlayView.addGestureRecognizer(spoilerOverlayViewTapGestureRecognizer)
@ -336,16 +284,6 @@ extension StatusView {
assert(sender.view === headerContainerView)
delegate?.statusView(self, headerDidPressed: headerContainerView)
}
@objc private func authorAvatarButtonDidPressed(_ sender: UIButton) {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
delegate?.statusView(self, authorAvatarButtonDidPressed: avatarButton)
}
@objc private func contentSensitiveeToggleButtonDidPressed(_ sender: UIButton) {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
delegate?.statusView(self, contentSensitiveeToggleButtonDidPressed: sender)
}
@objc private func pollVoteButtonDidPressed(_ sender: UIButton) {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
@ -394,6 +332,8 @@ extension StatusView.Style {
case .composeStatusReplica: composeStatusReplica(statusView: statusView)
case .composeStatusAuthor: composeStatusAuthor(statusView: statusView)
}
statusView.authorView.layout(style: self)
}
private func base(statusView: StatusView) {
@ -425,81 +365,9 @@ extension StatusView.Style {
statusView.headerIconImageView.setContentCompressionResistancePriority(.defaultLow - 100, for: .vertical)
statusView.headerIconImageView.setContentCompressionResistancePriority(.defaultLow - 100, for: .horizontal)
// author container: H - [ avatarButton | author meta container | contentWarningToggleButton ]
statusView.authorAdaptiveMarginContainerView.contentView = statusView.authorContainerView
statusView.authorAdaptiveMarginContainerView.contentView = statusView.authorView
statusView.authorAdaptiveMarginContainerView.margin = StatusView.containerLayoutMargin
statusView.containerStackView.addArrangedSubview(statusView.authorAdaptiveMarginContainerView)
UIContentSizeCategory.publisher
.sink { category in
statusView.authorContainerView.axis = category > .accessibilityLarge ? .vertical : .horizontal
statusView.authorContainerView.alignment = category > .accessibilityLarge ? .leading : .center
}
.store(in: &statusView._disposeBag)
// avatarButton
let authorAvatarButtonSize = CGSize(width: 46, height: 46)
statusView.avatarButton.size = authorAvatarButtonSize
statusView.avatarButton.avatarImageView.imageViewSize = authorAvatarButtonSize
statusView.avatarButton.translatesAutoresizingMaskIntoConstraints = false
statusView.authorContainerView.addArrangedSubview(statusView.avatarButton)
NSLayoutConstraint.activate([
statusView.avatarButton.widthAnchor.constraint(equalToConstant: authorAvatarButtonSize.width).priority(.required - 1),
statusView.avatarButton.heightAnchor.constraint(equalToConstant: authorAvatarButtonSize.height).priority(.required - 1),
])
statusView.avatarButton.setContentHuggingPriority(.required - 1, for: .vertical)
statusView.avatarButton.setContentCompressionResistancePriority(.required - 1, for: .vertical)
// authrMetaContainer: V - [ authorPrimaryMetaContainer | authorSecondaryMetaContainer ]
let authorMetaContainer = UIStackView()
authorMetaContainer.axis = .vertical
authorMetaContainer.spacing = 4
statusView.authorContainerView.addArrangedSubview(authorMetaContainer)
// authorPrimaryMetaContainer: H - [ authorNameLabel | (padding) | menuButton ]
let authorPrimaryMetaContainer = UIStackView()
authorPrimaryMetaContainer.axis = .horizontal
authorPrimaryMetaContainer.spacing = 10
authorMetaContainer.addArrangedSubview(authorPrimaryMetaContainer)
// authorNameLabel
authorPrimaryMetaContainer.addArrangedSubview(statusView.authorNameLabel)
statusView.authorNameLabel.setContentHuggingPriority(.required - 10, for: .horizontal)
statusView.authorNameLabel.setContentCompressionResistancePriority(.required - 10, for: .horizontal)
authorPrimaryMetaContainer.addArrangedSubview(UIView())
// menuButton
authorPrimaryMetaContainer.addArrangedSubview(statusView.menuButton)
statusView.menuButton.setContentHuggingPriority(.required - 2, for: .horizontal)
statusView.menuButton.setContentCompressionResistancePriority(.required - 2, for: .horizontal)
// authorSecondaryMetaContainer: H - [ authorUsername | usernameTrialingDotLabel | dateLabel | (padding) | contentSensitiveeToggleButton ]
let authorSecondaryMetaContainer = UIStackView()
authorSecondaryMetaContainer.axis = .horizontal
authorSecondaryMetaContainer.spacing = 4
authorMetaContainer.addArrangedSubview(authorSecondaryMetaContainer)
authorSecondaryMetaContainer.addArrangedSubview(statusView.authorUsernameLabel)
statusView.authorUsernameLabel.setContentHuggingPriority(.required - 8, for: .horizontal)
statusView.authorUsernameLabel.setContentCompressionResistancePriority(.required - 8, for: .horizontal)
authorSecondaryMetaContainer.addArrangedSubview(statusView.usernameTrialingDotLabel)
statusView.usernameTrialingDotLabel.setContentHuggingPriority(.required - 2, for: .horizontal)
statusView.usernameTrialingDotLabel.setContentCompressionResistancePriority(.required - 2, for: .horizontal)
authorSecondaryMetaContainer.addArrangedSubview(statusView.dateLabel)
statusView.dateLabel.setContentHuggingPriority(.required - 1, for: .horizontal)
statusView.dateLabel.setContentCompressionResistancePriority(.required - 1, for: .horizontal)
authorSecondaryMetaContainer.addArrangedSubview(UIView())
statusView.contentSensitiveeToggleButton.translatesAutoresizingMaskIntoConstraints = false
authorSecondaryMetaContainer.addArrangedSubview(statusView.contentSensitiveeToggleButton)
NSLayoutConstraint.activate([
statusView.contentSensitiveeToggleButton.heightAnchor.constraint(equalTo: statusView.authorUsernameLabel.heightAnchor, multiplier: 1.0).priority(.required - 1),
statusView.contentSensitiveeToggleButton.widthAnchor.constraint(equalTo: statusView.contentSensitiveeToggleButton.heightAnchor, multiplier: 1.0).priority(.required - 1),
])
statusView.authorUsernameLabel.setContentHuggingPriority(.required - 1, for: .vertical)
statusView.authorUsernameLabel.setContentCompressionResistancePriority(.required - 1, for: .vertical)
statusView.contentSensitiveeToggleButton.setContentHuggingPriority(.defaultLow, for: .vertical)
statusView.contentSensitiveeToggleButton.setContentHuggingPriority(.defaultLow, for: .horizontal)
statusView.contentSensitiveeToggleButton.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
statusView.contentSensitiveeToggleButton.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
// content container: V - [ contentMetaText ]
statusView.contentContainer.axis = .vertical
@ -605,7 +473,6 @@ extension StatusView.Style {
func report(statusView: StatusView) {
base(statusView: statusView) // override the base style
statusView.menuButton.removeFromSuperview()
statusView.actionToolbarAdaptiveMarginContainerView.removeFromSuperview()
}
@ -621,26 +488,18 @@ extension StatusView.Style {
statusView.contentAdaptiveMarginContainerView.bottomLayoutConstraint?.constant = 16 // fix bottom margin missing issue
statusView.pollAdaptiveMarginContainerView.bottomLayoutConstraint?.constant = 16 // fix bottom margin missing issue
statusView.contentSensitiveeToggleButton.removeFromSuperview()
statusView.menuButton.removeFromSuperview()
statusView.actionToolbarAdaptiveMarginContainerView.removeFromSuperview()
}
func composeStatusReplica(statusView: StatusView) {
base(statusView: statusView)
statusView.avatarButton.isUserInteractionEnabled = false
statusView.menuButton.removeFromSuperview()
statusView.actionToolbarAdaptiveMarginContainerView.removeFromSuperview()
}
func composeStatusAuthor(statusView: StatusView) {
base(statusView: statusView)
statusView.avatarButton.isUserInteractionEnabled = false
statusView.menuButton.removeFromSuperview()
statusView.usernameTrialingDotLabel.removeFromSuperview()
statusView.dateLabel.removeFromSuperview()
statusView.contentAdaptiveMarginContainerView.removeFromSuperview()
statusView.spoilerOverlayView.removeFromSuperview()
statusView.mediaContainerView.removeFromSuperview()
@ -656,7 +515,7 @@ extension StatusView {
}
func setContentSensitiveeToggleButtonDisplay(isDisplay: Bool = true) {
contentSensitiveeToggleButton.isHidden = !isDisplay
authorView.contentSensitiveeToggleButton.isHidden = !isDisplay
}
func setSpoilerOverlayViewHidden(isHidden: Bool) {
@ -696,48 +555,6 @@ extension StatusView: AdaptiveContainerView {
}
}
extension StatusView {
public struct AuthorMenuContext {
public let name: String
public let isMuting: Bool
public let isBlocking: Bool
public let isMyself: Bool
}
public func setupAuthorMenu(menuContext: AuthorMenuContext) -> UIMenu {
var actions: [MastodonMenu.Action] = []
actions = [
.muteUser(.init(
name: menuContext.name,
isMuting: menuContext.isMuting
)),
.blockUser(.init(
name: menuContext.name,
isBlocking: menuContext.isBlocking
)),
.reportUser(
.init(name: menuContext.name)
),
]
if menuContext.isMyself {
actions.append(.deleteStatus)
}
let menu = MastodonMenu.setupMenu(
actions: actions,
delegate: self
)
return menu
}
}
// MARK: - UITextViewDelegate
extension StatusView: UITextViewDelegate {
@ -823,7 +640,7 @@ extension StatusView: StatusMetricViewDelegate {
extension StatusView: MastodonMenuDelegate {
public func menuAction(_ action: MastodonMenu.Action) {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
delegate?.statusView(self, menuButton: menuButton, didSelectAction: action)
delegate?.statusView(self, menuButton: authorView.menuButton, didSelectAction: action)
}
}