diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift index 115175800..0049539b0 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift @@ -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 { diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift index daf14b96e..096eb4cf4 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift @@ -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] = [] diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift new file mode 100644 index 000000000..a4c7c28d7 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift @@ -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() // 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() + } +} diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift index f3d9f6f80..af2bba14a 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift @@ -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) diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift index 4c983df34..1c9ed673d 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift @@ -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) } }