mirror of
https://github.com/metabolist/metatext.git
synced 2024-12-12 01:06:26 +00:00
1191 lines
52 KiB
Swift
1191 lines
52 KiB
Swift
// Copyright © 2020 Metabolist. All rights reserved.
|
|
|
|
// swiftlint:disable file_length
|
|
import Combine
|
|
import Mastodon
|
|
import SDWebImage
|
|
import UIKit
|
|
import ViewModels
|
|
|
|
final class StatusView: UIView {
|
|
let avatarImageView = SDAnimatedImageView()
|
|
let rebloggerAvatarImageView = SDAnimatedImageView()
|
|
let avatarButton = UIButton()
|
|
let infoIcon = UIImageView()
|
|
let infoLabel = AnimatedAttachmentLabel()
|
|
let rebloggerButton = UIButton()
|
|
let displayNameLabel = AnimatedAttachmentLabel()
|
|
let accountLabel = UILabel()
|
|
let nameButton = UIButton()
|
|
let timeLabel = UILabel()
|
|
let bodyView = StatusBodyView()
|
|
let showThreadIndicator = UIButton(type: .system)
|
|
let contextParentTimeLabel = UILabel()
|
|
let visibilityImageView = UIImageView()
|
|
let applicationButton = UIButton(type: .system)
|
|
let rebloggedByButton = UIButton()
|
|
let favoritedByButton = UIButton()
|
|
let replyButton = UIButton()
|
|
let reblogButton = UIButton()
|
|
let favoriteButton = UIButton()
|
|
let shareButton = UIButton()
|
|
let menuButton = UIButton()
|
|
let buttonsStackView = UIStackView()
|
|
let reportSelectionSwitch = UISwitch()
|
|
|
|
private let containerStackView = UIStackView()
|
|
private let sideStackView = UIStackView()
|
|
private let mainStackView = UIStackView()
|
|
private let avatarContainerView = UIView()
|
|
private let nameAccountContainerStackView = UIStackView()
|
|
private let nameAccountTimeStackView = UIStackView()
|
|
private let contextParentTimeApplicationStackView = UIStackView()
|
|
private let timeVisibilityDividerLabel = UILabel()
|
|
private let visibilityApplicationDividerLabel = UILabel()
|
|
private let contextParentTopNameAccountSpacingView = UIView()
|
|
private let contextParentBottomNameAccountSpacingView = UIView()
|
|
private let interactionsDividerView = UIView()
|
|
private let interactionsStackView = UIStackView()
|
|
private let buttonsDividerView = UIView()
|
|
private let inReplyToView = UIView()
|
|
private let hasReplyFollowingView = UIView()
|
|
private var statusConfiguration: StatusContentConfiguration
|
|
private let avatarWidthConstraint: NSLayoutConstraint
|
|
private let avatarHeightConstraint: NSLayoutConstraint
|
|
private var cancellables = Set<AnyCancellable>()
|
|
|
|
init(configuration: StatusContentConfiguration) {
|
|
self.statusConfiguration = configuration
|
|
|
|
avatarWidthConstraint = avatarImageView.widthAnchor.constraint(equalToConstant: .avatarDimension)
|
|
avatarHeightConstraint = avatarImageView.heightAnchor.constraint(equalToConstant: .avatarDimension)
|
|
avatarHeightConstraint.priority = .justBelowMax
|
|
|
|
super.init(frame: .zero)
|
|
|
|
initialSetup()
|
|
applyStatusConfiguration()
|
|
}
|
|
|
|
@available(*, unavailable)
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func accessibilityActivate() -> Bool {
|
|
if reportSelectionSwitch.isHidden, !statusConfiguration.viewModel.shouldShowContent {
|
|
statusConfiguration.viewModel.toggleShowContent()
|
|
accessibilityAttributedLabel = accessibilityAttributedLabel(forceShowContent: true)
|
|
|
|
return true
|
|
} else {
|
|
return super.accessibilityActivate()
|
|
}
|
|
}
|
|
}
|
|
|
|
extension StatusView {
|
|
static func estimatedHeight(width: CGFloat,
|
|
identityContext: IdentityContext,
|
|
status: Status,
|
|
configuration: CollectionItem.StatusConfiguration) -> CGFloat {
|
|
var height = CGFloat.defaultSpacing * 2
|
|
let bodyWidth = width - .defaultSpacing - .avatarDimension
|
|
|
|
if status.reblog != nil || configuration.isPinned {
|
|
height += UIFont.preferredFont(forTextStyle: .caption1).lineHeight + .compactSpacing
|
|
}
|
|
|
|
if configuration.isContextParent {
|
|
height += .avatarDimension + .minimumButtonDimension * 2.5 + .hairline * 2 + .compactSpacing * 4
|
|
} else {
|
|
height += UIFont.preferredFont(forTextStyle: .headline).lineHeight
|
|
+ .compactSpacing + .minimumButtonDimension / 2
|
|
}
|
|
|
|
height += StatusBodyView.estimatedHeight(
|
|
width: bodyWidth,
|
|
identityContext: identityContext,
|
|
status: status,
|
|
configuration: configuration)
|
|
+ .compactSpacing
|
|
|
|
if configuration.isReplyOutOfContext {
|
|
height += UIFont.preferredFont(forTextStyle: .callout).lineHeight + .compactSpacing
|
|
}
|
|
|
|
return height
|
|
}
|
|
|
|
func refreshAccessibilityLabel() {
|
|
accessibilityAttributedLabel = accessibilityAttributedLabel(forceShowContent: false)
|
|
}
|
|
}
|
|
|
|
extension StatusView: UIContentView {
|
|
var configuration: UIContentConfiguration {
|
|
get { statusConfiguration }
|
|
set {
|
|
guard let statusConfiguration = newValue as? StatusContentConfiguration else { return }
|
|
|
|
self.statusConfiguration = statusConfiguration
|
|
|
|
applyStatusConfiguration()
|
|
}
|
|
}
|
|
}
|
|
|
|
extension StatusView: UITextViewDelegate {
|
|
func textView(
|
|
_ textView: UITextView,
|
|
shouldInteractWith URL: URL,
|
|
in characterRange: NSRange,
|
|
interaction: UITextItemInteraction) -> Bool {
|
|
switch interaction {
|
|
case .invokeDefaultAction:
|
|
statusConfiguration.viewModel.urlSelected(URL)
|
|
return false
|
|
case .preview: return false
|
|
case .presentActions: return false
|
|
@unknown default: return false
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension StatusView {
|
|
static let actionButtonTitleEdgeInsets = UIEdgeInsets(top: 0, left: 2, bottom: 0, right: 0)
|
|
static let reblogAvatarDimension: CGFloat = .avatarDimension * 7 / 8
|
|
|
|
var actionButtons: [UIButton] {
|
|
[replyButton, reblogButton, favoriteButton, shareButton, menuButton]
|
|
}
|
|
|
|
// swiftlint:disable function_body_length
|
|
func initialSetup() {
|
|
addSubview(containerStackView)
|
|
containerStackView.translatesAutoresizingMaskIntoConstraints = false
|
|
containerStackView.spacing = .defaultSpacing
|
|
|
|
infoIcon.tintColor = .secondaryLabel
|
|
infoIcon.contentMode = .scaleAspectFit
|
|
infoIcon.setContentCompressionResistancePriority(.required, for: .vertical)
|
|
|
|
sideStackView.axis = .vertical
|
|
sideStackView.alignment = .trailing
|
|
sideStackView.spacing = .compactSpacing
|
|
sideStackView.addArrangedSubview(infoIcon)
|
|
sideStackView.addArrangedSubview(UIView())
|
|
containerStackView.addArrangedSubview(sideStackView)
|
|
|
|
mainStackView.axis = .vertical
|
|
mainStackView.spacing = .compactSpacing
|
|
containerStackView.addArrangedSubview(mainStackView)
|
|
|
|
infoLabel.font = .preferredFont(forTextStyle: .caption1)
|
|
infoLabel.textColor = .secondaryLabel
|
|
infoLabel.adjustsFontForContentSizeCategory = true
|
|
infoLabel.isUserInteractionEnabled = true
|
|
infoLabel.setContentHuggingPriority(.required, for: .vertical)
|
|
mainStackView.addArrangedSubview(infoLabel)
|
|
|
|
infoLabel.addSubview(rebloggerButton)
|
|
rebloggerButton.translatesAutoresizingMaskIntoConstraints = false
|
|
rebloggerButton.addAction(
|
|
UIAction { [weak self] _ in self?.statusConfiguration.viewModel.rebloggerAccountSelected() },
|
|
for: .touchUpInside)
|
|
|
|
let rebloggerTouchStartAction = UIAction { [weak self] _ in self?.infoLabel.alpha = 0.75 }
|
|
|
|
rebloggerButton.addAction(rebloggerTouchStartAction, for: .touchDown)
|
|
rebloggerButton.addAction(rebloggerTouchStartAction, for: .touchDragEnter)
|
|
|
|
let rebloggerTouchEnd = UIAction { [weak self] _ in self?.infoLabel.alpha = 1 }
|
|
|
|
rebloggerButton.addAction(rebloggerTouchEnd, for: .touchDragExit)
|
|
rebloggerButton.addAction(rebloggerTouchEnd, for: .touchUpInside)
|
|
rebloggerButton.addAction(rebloggerTouchEnd, for: .touchUpOutside)
|
|
rebloggerButton.addAction(rebloggerTouchEnd, for: .touchCancel)
|
|
|
|
displayNameLabel.font = .preferredFont(forTextStyle: .headline)
|
|
displayNameLabel.adjustsFontForContentSizeCategory = true
|
|
displayNameLabel.setContentHuggingPriority(.required, for: .horizontal)
|
|
displayNameLabel.setContentHuggingPriority(.required, for: .vertical)
|
|
displayNameLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
nameAccountTimeStackView.addArrangedSubview(displayNameLabel)
|
|
|
|
accountLabel.font = .preferredFont(forTextStyle: .subheadline)
|
|
accountLabel.adjustsFontForContentSizeCategory = true
|
|
accountLabel.textColor = .secondaryLabel
|
|
accountLabel.setContentHuggingPriority(.required, for: .horizontal)
|
|
accountLabel.setContentHuggingPriority(.required, for: .vertical)
|
|
nameAccountTimeStackView.addArrangedSubview(accountLabel)
|
|
|
|
timeLabel.font = .preferredFont(forTextStyle: .subheadline)
|
|
timeLabel.adjustsFontForContentSizeCategory = true
|
|
timeLabel.textColor = .secondaryLabel
|
|
timeLabel.textAlignment = .right
|
|
timeLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
timeLabel.setContentHuggingPriority(.required, for: .vertical)
|
|
nameAccountTimeStackView.addArrangedSubview(timeLabel)
|
|
|
|
nameAccountContainerStackView.spacing = .defaultSpacing
|
|
nameAccountContainerStackView.addArrangedSubview(nameAccountTimeStackView)
|
|
mainStackView.addArrangedSubview(nameAccountContainerStackView)
|
|
|
|
nameButton.translatesAutoresizingMaskIntoConstraints = false
|
|
nameButton.addAction(
|
|
UIAction { [weak self] _ in
|
|
self?.displayNameLabel.alpha = 1
|
|
self?.accountLabel.alpha = 1
|
|
self?.statusConfiguration.viewModel.accountSelected()
|
|
},
|
|
for: .touchUpInside)
|
|
nameButton.addAction(
|
|
UIAction { [weak self] _ in
|
|
self?.displayNameLabel.alpha = 0.5
|
|
self?.accountLabel.alpha = 0.5
|
|
},
|
|
for: .touchDown)
|
|
|
|
let unhighlightAction = UIAction { [weak self] _ in
|
|
self?.displayNameLabel.alpha = 1
|
|
self?.accountLabel.alpha = 1
|
|
}
|
|
|
|
nameButton.addAction(unhighlightAction, for: .touchUpOutside)
|
|
nameButton.addAction(unhighlightAction, for: .touchCancel)
|
|
nameButton.addAction(unhighlightAction, for: .touchDragOutside)
|
|
nameAccountContainerStackView.addSubview(nameButton)
|
|
|
|
mainStackView.addArrangedSubview(bodyView)
|
|
|
|
mainStackView.addArrangedSubview(showThreadIndicator)
|
|
showThreadIndicator.isHidden = true
|
|
showThreadIndicator.setTitle(NSLocalizedString("status.show-thread", comment: ""), for: .normal)
|
|
showThreadIndicator.titleLabel?.adjustsFontForContentSizeCategory = true
|
|
showThreadIndicator.titleLabel?.font = .preferredFont(forTextStyle: .callout)
|
|
showThreadIndicator.isUserInteractionEnabled = false
|
|
|
|
contextParentTimeLabel.font = .preferredFont(forTextStyle: .footnote)
|
|
contextParentTimeLabel.adjustsFontForContentSizeCategory = true
|
|
contextParentTimeLabel.textColor = .secondaryLabel
|
|
contextParentTimeLabel.setContentHuggingPriority(.required, for: .horizontal)
|
|
contextParentTimeApplicationStackView.addArrangedSubview(contextParentTimeLabel)
|
|
|
|
for label in [timeVisibilityDividerLabel, visibilityApplicationDividerLabel] {
|
|
label.font = .preferredFont(forTextStyle: .footnote)
|
|
label.adjustsFontForContentSizeCategory = true
|
|
label.textColor = .secondaryLabel
|
|
label.text = "•"
|
|
label.setContentHuggingPriority(.required, for: .horizontal)
|
|
label.isAccessibilityElement = false
|
|
}
|
|
|
|
contextParentTimeApplicationStackView.addArrangedSubview(timeVisibilityDividerLabel)
|
|
|
|
contextParentTimeApplicationStackView.addArrangedSubview(visibilityImageView)
|
|
visibilityImageView.contentMode = .scaleAspectFit
|
|
visibilityImageView.tintColor = .secondaryLabel
|
|
visibilityImageView.isAccessibilityElement = true
|
|
|
|
contextParentTimeApplicationStackView.addArrangedSubview(visibilityApplicationDividerLabel)
|
|
|
|
applicationButton.titleLabel?.font = .preferredFont(forTextStyle: .footnote)
|
|
applicationButton.titleLabel?.adjustsFontForContentSizeCategory = true
|
|
applicationButton.setTitleColor(.secondaryLabel, for: .disabled)
|
|
applicationButton.setContentHuggingPriority(.required, for: .horizontal)
|
|
applicationButton.addAction(
|
|
UIAction { [weak self] _ in
|
|
guard
|
|
let viewModel = self?.statusConfiguration.viewModel,
|
|
let url = viewModel.applicationURL
|
|
else { return }
|
|
|
|
viewModel.urlSelected(url)
|
|
},
|
|
for: .touchUpInside)
|
|
contextParentTimeApplicationStackView.addArrangedSubview(applicationButton)
|
|
contextParentTimeApplicationStackView.addArrangedSubview(UIView())
|
|
|
|
contextParentTimeApplicationStackView.spacing = .compactSpacing
|
|
mainStackView.addArrangedSubview(contextParentTimeApplicationStackView)
|
|
|
|
for view in [interactionsDividerView, buttonsDividerView] {
|
|
view.backgroundColor = .opaqueSeparator
|
|
view.heightAnchor.constraint(equalToConstant: .hairline).isActive = true
|
|
}
|
|
|
|
mainStackView.addArrangedSubview(interactionsDividerView)
|
|
mainStackView.addArrangedSubview(interactionsStackView)
|
|
mainStackView.addArrangedSubview(buttonsDividerView)
|
|
|
|
rebloggedByButton.contentHorizontalAlignment = .leading
|
|
rebloggedByButton.addAction(
|
|
UIAction { [weak self] _ in self?.statusConfiguration.viewModel.rebloggedBySelected() },
|
|
for: .touchUpInside)
|
|
interactionsStackView.addArrangedSubview(rebloggedByButton)
|
|
|
|
favoritedByButton.contentHorizontalAlignment = .leading
|
|
favoritedByButton.addAction(
|
|
UIAction { [weak self] _ in self?.statusConfiguration.viewModel.favoritedBySelected() },
|
|
for: .touchUpInside)
|
|
interactionsStackView.addArrangedSubview(favoritedByButton)
|
|
interactionsStackView.distribution = .fillEqually
|
|
|
|
replyButton.addAction(
|
|
UIAction { [weak self] _ in self?.statusConfiguration.viewModel.reply() },
|
|
for: .touchUpInside)
|
|
replyButton.accessibilityLabel = NSLocalizedString("status.reply-button.accessibility-label", comment: "")
|
|
|
|
reblogButton.addAction(
|
|
UIAction { [weak self] _ in
|
|
guard let self = self,
|
|
!self.statusConfiguration.viewModel.identityContext.appPreferences.requireDoubleTapToReblog
|
|
else { return }
|
|
|
|
self.reblog()
|
|
},
|
|
for: .touchUpInside)
|
|
reblogButton.addTarget(self, action: #selector(reblogButtonDoubleTap(sender:event:)), for: .touchDownRepeat)
|
|
|
|
favoriteButton.addAction(
|
|
UIAction { [weak self] _ in
|
|
guard let self = self,
|
|
!self.statusConfiguration.viewModel.identityContext.appPreferences.requireDoubleTapToFavorite
|
|
else { return }
|
|
|
|
self.favorite()
|
|
},
|
|
for: .touchUpInside)
|
|
favoriteButton.accessibilityLabel = NSLocalizedString("status.favorite-button.accessibility-label", comment: "")
|
|
favoriteButton.addTarget(self, action: #selector(favoriteButtonDoubleTap(sender:event:)), for: .touchDownRepeat)
|
|
|
|
shareButton.addAction(
|
|
UIAction { [weak self] _ in self?.statusConfiguration.viewModel.shareStatus() },
|
|
for: .touchUpInside)
|
|
|
|
menuButton.showsMenuAsPrimaryAction = true
|
|
|
|
for button in actionButtons {
|
|
button.titleLabel?.font = .preferredFont(forTextStyle: .footnote)
|
|
button.titleLabel?.adjustsFontSizeToFitWidth = true
|
|
button.tintColor = .secondaryLabel
|
|
button.setTitleColor(.secondaryLabel, for: .normal)
|
|
button.titleEdgeInsets = Self.actionButtonTitleEdgeInsets
|
|
buttonsStackView.addArrangedSubview(button)
|
|
button.widthAnchor.constraint(greaterThanOrEqualToConstant: .minimumButtonDimension).isActive = true
|
|
}
|
|
|
|
buttonsStackView.distribution = .equalSpacing
|
|
mainStackView.addArrangedSubview(buttonsStackView)
|
|
|
|
avatarContainerView.addSubview(avatarImageView)
|
|
avatarImageView.translatesAutoresizingMaskIntoConstraints = false
|
|
avatarImageView.layer.cornerRadius = .avatarDimension / 2
|
|
avatarImageView.clipsToBounds = true
|
|
|
|
avatarButton.translatesAutoresizingMaskIntoConstraints = false
|
|
avatarImageView.addSubview(avatarButton)
|
|
avatarImageView.isUserInteractionEnabled = true
|
|
avatarButton.setBackgroundImage(.highlightedButtonBackground, for: .highlighted)
|
|
|
|
avatarButton.addAction(
|
|
UIAction { [weak self] _ in self?.statusConfiguration.viewModel.accountSelected() },
|
|
for: .touchUpInside)
|
|
|
|
avatarContainerView.addSubview(rebloggerAvatarImageView)
|
|
rebloggerAvatarImageView.translatesAutoresizingMaskIntoConstraints = false
|
|
rebloggerAvatarImageView.layer.cornerRadius = .avatarDimension / 4
|
|
rebloggerAvatarImageView.clipsToBounds = true
|
|
rebloggerAvatarImageView.isHidden = true
|
|
|
|
for view in [inReplyToView, hasReplyFollowingView] {
|
|
addSubview(view)
|
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
view.backgroundColor = .opaqueSeparator
|
|
view.widthAnchor.constraint(equalToConstant: .hairline).isActive = true
|
|
}
|
|
|
|
containerStackView.addArrangedSubview(reportSelectionSwitch)
|
|
reportSelectionSwitch.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
reportSelectionSwitch.setContentHuggingPriority(.required, for: .horizontal)
|
|
reportSelectionSwitch.setContentHuggingPriority(.defaultLow, for: .vertical)
|
|
reportSelectionSwitch.isHidden = true
|
|
|
|
NSLayoutConstraint.activate([
|
|
containerStackView.topAnchor.constraint(equalTo: readableContentGuide.topAnchor),
|
|
containerStackView.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor),
|
|
containerStackView.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor),
|
|
containerStackView.bottomAnchor.constraint(equalTo: readableContentGuide.bottomAnchor),
|
|
avatarContainerView.widthAnchor.constraint(equalToConstant: .avatarDimension),
|
|
avatarContainerView.heightAnchor.constraint(equalToConstant: .avatarDimension),
|
|
avatarWidthConstraint,
|
|
avatarHeightConstraint,
|
|
avatarImageView.topAnchor.constraint(equalTo: avatarContainerView.topAnchor),
|
|
avatarImageView.leadingAnchor.constraint(equalTo: avatarContainerView.leadingAnchor),
|
|
rebloggerAvatarImageView.widthAnchor.constraint(equalToConstant: .avatarDimension / 2),
|
|
rebloggerAvatarImageView.heightAnchor.constraint(equalToConstant: .avatarDimension / 2),
|
|
rebloggerAvatarImageView.trailingAnchor.constraint(equalTo: avatarContainerView.trailingAnchor),
|
|
rebloggerAvatarImageView.bottomAnchor.constraint(equalTo: avatarContainerView.bottomAnchor),
|
|
sideStackView.widthAnchor.constraint(equalToConstant: .avatarDimension),
|
|
avatarButton.leadingAnchor.constraint(equalTo: avatarImageView.leadingAnchor),
|
|
avatarButton.topAnchor.constraint(equalTo: avatarImageView.topAnchor),
|
|
avatarButton.bottomAnchor.constraint(equalTo: avatarImageView.bottomAnchor),
|
|
avatarButton.trailingAnchor.constraint(equalTo: avatarImageView.trailingAnchor),
|
|
infoIcon.centerYAnchor.constraint(equalTo: infoLabel.centerYAnchor),
|
|
nameButton.leadingAnchor.constraint(equalTo: displayNameLabel.leadingAnchor),
|
|
nameButton.topAnchor.constraint(equalTo: displayNameLabel.topAnchor),
|
|
nameButton.trailingAnchor.constraint(equalTo: accountLabel.trailingAnchor),
|
|
nameButton.bottomAnchor.constraint(equalTo: accountLabel.bottomAnchor),
|
|
contextParentTimeApplicationStackView.heightAnchor.constraint(
|
|
greaterThanOrEqualToConstant: .minimumButtonDimension / 2),
|
|
interactionsStackView.heightAnchor.constraint(greaterThanOrEqualToConstant: .minimumButtonDimension),
|
|
rebloggerButton.leadingAnchor.constraint(equalTo: infoLabel.leadingAnchor),
|
|
rebloggerButton.topAnchor.constraint(equalTo: infoLabel.topAnchor),
|
|
rebloggerButton.trailingAnchor.constraint(equalTo: infoLabel.trailingAnchor),
|
|
rebloggerButton.bottomAnchor.constraint(equalTo: infoLabel.bottomAnchor)
|
|
])
|
|
|
|
NotificationCenter.default.publisher(for: UIAccessibility.voiceOverStatusDidChangeNotification)
|
|
.sink { [weak self] _ in self?.configureUserInteractionEnabledForAccessibility() }
|
|
.store(in: &cancellables)
|
|
}
|
|
|
|
func applyStatusConfiguration() {
|
|
let viewModel = statusConfiguration.viewModel
|
|
let isContextParent = viewModel.configuration.isContextParent
|
|
let mutableDisplayName = NSMutableAttributedString(string: viewModel.accountViewModel.displayName)
|
|
let isAuthenticated = viewModel.identityContext.identity.authenticated
|
|
&& !viewModel.identityContext.identity.pending
|
|
|
|
menuButton.menu = menu(viewModel: viewModel)
|
|
|
|
avatarImageView.sd_setImage(with: viewModel.avatarURL)
|
|
avatarButton.accessibilityLabel = String.localizedStringWithFormat(
|
|
NSLocalizedString("account.avatar.accessibility-label-%@", comment: ""),
|
|
viewModel.accountViewModel.displayName)
|
|
|
|
sideStackView.isHidden = isContextParent
|
|
|
|
let avatarDimension = viewModel.isReblog ? Self.reblogAvatarDimension : .avatarDimension
|
|
|
|
avatarWidthConstraint.constant = avatarDimension
|
|
avatarHeightConstraint.constant = avatarDimension
|
|
avatarImageView.layer.cornerRadius = avatarDimension / 2
|
|
rebloggerAvatarImageView.isHidden = !viewModel.isReblog
|
|
rebloggerAvatarImageView.sd_setImage(with: viewModel.isReblog ? viewModel.rebloggerAvatarURL : nil)
|
|
|
|
if isContextParent, avatarContainerView.superview !== nameAccountContainerStackView {
|
|
nameAccountContainerStackView.insertArrangedSubview(avatarContainerView, at: 0)
|
|
} else if avatarContainerView.superview !== sideStackView {
|
|
sideStackView.insertArrangedSubview(avatarContainerView, at: 1)
|
|
}
|
|
|
|
NSLayoutConstraint.activate([
|
|
inReplyToView.centerXAnchor.constraint(equalTo: avatarImageView.centerXAnchor),
|
|
inReplyToView.topAnchor.constraint(equalTo: topAnchor),
|
|
inReplyToView.bottomAnchor.constraint(equalTo: avatarImageView.topAnchor),
|
|
hasReplyFollowingView.centerXAnchor.constraint(equalTo: avatarImageView.centerXAnchor),
|
|
hasReplyFollowingView.topAnchor.constraint(equalTo: avatarImageView.bottomAnchor),
|
|
hasReplyFollowingView.bottomAnchor.constraint(equalTo: bottomAnchor)
|
|
])
|
|
|
|
inReplyToView.isHidden = !viewModel.configuration.isReplyInContext
|
|
hasReplyFollowingView.isHidden = !viewModel.configuration.hasReplyFollowing
|
|
|
|
if viewModel.isReblog {
|
|
let attributedTitle = "status.reblogged-by-%@".localizedBolding(
|
|
displayName: viewModel.rebloggedByDisplayName,
|
|
emojis: viewModel.rebloggedByDisplayNameEmojis,
|
|
label: infoLabel,
|
|
identityContext: viewModel.identityContext)
|
|
let highlightedAttributedTitle = NSMutableAttributedString(attributedString: attributedTitle)
|
|
|
|
highlightedAttributedTitle.addAttribute(
|
|
.foregroundColor,
|
|
value: UIColor.tertiaryLabel,
|
|
range: .init(location: 0, length: highlightedAttributedTitle.length))
|
|
|
|
infoLabel.attributedText = attributedTitle
|
|
infoIcon.image = UIImage(
|
|
systemName: "arrow.2.squarepath",
|
|
withConfiguration: UIImage.SymbolConfiguration(scale: .small))
|
|
infoLabel.isHidden = false
|
|
infoIcon.isHidden = false
|
|
rebloggerButton.isHidden = false
|
|
} else if viewModel.configuration.isPinned {
|
|
let pinnedText: String
|
|
|
|
switch viewModel.identityContext.appPreferences.statusWord {
|
|
case .toot:
|
|
pinnedText = NSLocalizedString("status.pinned.toot", comment: "")
|
|
case .post:
|
|
pinnedText = NSLocalizedString("status.pinned.post", comment: "")
|
|
}
|
|
|
|
infoLabel.text = pinnedText
|
|
infoIcon.centerYAnchor.constraint(equalTo: infoLabel.centerYAnchor).isActive = true
|
|
infoIcon.image = UIImage(
|
|
systemName: "pin",
|
|
withConfiguration: UIImage.SymbolConfiguration(scale: .small))
|
|
infoLabel.isHidden = false
|
|
infoIcon.isHidden = false
|
|
rebloggerButton.isHidden = true
|
|
} else {
|
|
infoLabel.text = nil
|
|
infoIcon.image = nil
|
|
infoLabel.isHidden = true
|
|
infoIcon.isHidden = true
|
|
rebloggerButton.setTitle(nil, for: .normal)
|
|
rebloggerButton.setImage(nil, for: .normal)
|
|
rebloggerButton.isHidden = true
|
|
}
|
|
|
|
mutableDisplayName.insert(emojis: viewModel.accountViewModel.emojis,
|
|
view: displayNameLabel,
|
|
identityContext: viewModel.identityContext)
|
|
mutableDisplayName.resizeAttachments(toLineHeight: displayNameLabel.font.lineHeight)
|
|
displayNameLabel.attributedText = mutableDisplayName
|
|
accountLabel.text = viewModel.accountName
|
|
|
|
let nameButtonAccessibilityAttributedLabel = NSMutableAttributedString(attributedString: mutableDisplayName)
|
|
|
|
nameButtonAccessibilityAttributedLabel.appendWithSeparator(viewModel.accountName)
|
|
nameButton.accessibilityAttributedLabel = nameButtonAccessibilityAttributedLabel
|
|
|
|
nameAccountTimeStackView.axis = isContextParent ? .vertical : .horizontal
|
|
nameAccountTimeStackView.alignment = isContextParent ? .leading : .fill
|
|
nameAccountTimeStackView.spacing = isContextParent ? 0 : .compactSpacing
|
|
|
|
contextParentTopNameAccountSpacingView.removeFromSuperview()
|
|
contextParentBottomNameAccountSpacingView.removeFromSuperview()
|
|
|
|
if isContextParent {
|
|
nameAccountTimeStackView.insertArrangedSubview(contextParentTopNameAccountSpacingView, at: 0)
|
|
nameAccountTimeStackView.addArrangedSubview(contextParentBottomNameAccountSpacingView)
|
|
contextParentTopNameAccountSpacingView.heightAnchor
|
|
.constraint(equalTo: contextParentBottomNameAccountSpacingView.heightAnchor).isActive = true
|
|
}
|
|
|
|
timeLabel.text = viewModel.time
|
|
timeLabel.accessibilityLabel = viewModel.accessibilityTime
|
|
timeLabel.isHidden = isContextParent
|
|
|
|
bodyView.viewModel = viewModel
|
|
|
|
showThreadIndicator.isHidden = !viewModel.configuration.isReplyOutOfContext
|
|
|
|
contextParentTimeLabel.text = viewModel.contextParentTime
|
|
contextParentTimeLabel.accessibilityLabel = viewModel.accessibilityContextParentTime
|
|
visibilityImageView.image = UIImage(systemName: viewModel.visibility.systemImageName)
|
|
visibilityImageView.accessibilityLabel = viewModel.visibility.title
|
|
visibilityApplicationDividerLabel.isHidden = viewModel.applicationName == nil
|
|
applicationButton.isHidden = viewModel.applicationName == nil
|
|
applicationButton.setTitle(viewModel.applicationName, for: .normal)
|
|
applicationButton.isEnabled = viewModel.applicationURL != nil
|
|
contextParentTimeApplicationStackView.isHidden = !isContextParent
|
|
|
|
let noReblogs = viewModel.reblogsCount == 0
|
|
let noFavorites = viewModel.favoritesCount == 0
|
|
let noInteractions = !isContextParent || (noReblogs && noFavorites)
|
|
|
|
rebloggedByButton.setAttributedLocalizedTitle(
|
|
localizationKey: "status.reblogs-count-%ld",
|
|
count: viewModel.reblogsCount)
|
|
rebloggedByButton.isHidden = noReblogs
|
|
favoritedByButton.setAttributedLocalizedTitle(
|
|
localizationKey: "status.favorites-count-%ld",
|
|
count: viewModel.favoritesCount)
|
|
favoritedByButton.isHidden = noFavorites
|
|
|
|
interactionsDividerView.isHidden = noInteractions
|
|
interactionsStackView.isHidden = noInteractions
|
|
buttonsDividerView.isHidden = !isContextParent
|
|
|
|
for button in actionButtons {
|
|
button.contentHorizontalAlignment = isContextParent ? .center : .leading
|
|
|
|
if isContextParent {
|
|
button.heightAnchor.constraint(equalToConstant: .minimumButtonDimension).isActive = true
|
|
} else {
|
|
button.heightAnchor.constraint(
|
|
greaterThanOrEqualToConstant: .minimumButtonDimension * 2 / 3).isActive = true
|
|
}
|
|
}
|
|
|
|
setButtonImages(font: isContextParent
|
|
? .preferredFont(forTextStyle: .title3)
|
|
: .preferredFont(forTextStyle: .subheadline))
|
|
|
|
replyButton.setCountTitle(count: viewModel.repliesCount, isContextParent: isContextParent)
|
|
replyButton.isEnabled = isAuthenticated
|
|
replyButton.menu = authenticatedIdentitiesMenu { viewModel.reply(identity: $0) }
|
|
|
|
if viewModel.identityContext.appPreferences.showReblogAndFavoriteCounts || isContextParent {
|
|
reblogButton.setCountTitle(count: viewModel.reblogsCount, isContextParent: isContextParent)
|
|
favoriteButton.setCountTitle(count: viewModel.favoritesCount, isContextParent: isContextParent)
|
|
} else {
|
|
reblogButton.setTitle(nil, for: .normal)
|
|
favoriteButton.setTitle(nil, for: .normal)
|
|
}
|
|
|
|
setReblogButtonColor(reblogged: viewModel.reblogged)
|
|
reblogButton.isEnabled = viewModel.canBeReblogged && isAuthenticated
|
|
reblogButton.menu = authenticatedIdentitiesMenu { viewModel.toggleReblogged(identityId: $0.id) }
|
|
|
|
setFavoriteButtonColor(favorited: viewModel.favorited)
|
|
favoriteButton.isEnabled = isAuthenticated
|
|
favoriteButton.menu = authenticatedIdentitiesMenu { viewModel.toggleFavorited(identityId: $0.id) }
|
|
|
|
shareButton.tag = viewModel.sharingURL?.hashValue ?? 0
|
|
|
|
menuButton.isEnabled = isAuthenticated
|
|
|
|
reportSelectionSwitch.isOn = viewModel.selectedForReport
|
|
|
|
isAccessibilityElement = !viewModel.configuration.isContextParent
|
|
|
|
accessibilityAttributedLabel = accessibilityAttributedLabel(forceShowContent: false)
|
|
|
|
configureUserInteractionEnabledForAccessibility()
|
|
|
|
accessibilityCustomActions = accessibilityCustomActions(viewModel: viewModel)
|
|
}
|
|
|
|
func menu(viewModel: StatusViewModel) -> UIMenu {
|
|
var sections = [UIMenu]()
|
|
|
|
var firstSectionItems = [
|
|
UIAction(
|
|
title: viewModel.bookmarked
|
|
? NSLocalizedString("status.unbookmark", comment: "")
|
|
: NSLocalizedString("status.bookmark", comment: ""),
|
|
image: UIImage(systemName: "bookmark")) { _ in
|
|
viewModel.toggleBookmarked()
|
|
}
|
|
]
|
|
|
|
if let pinned = viewModel.pinned {
|
|
firstSectionItems.append(UIAction(
|
|
title: pinned
|
|
? NSLocalizedString("status.unpin", comment: "")
|
|
: NSLocalizedString("status.pin", comment: ""),
|
|
image: UIImage(systemName: "pin")) { _ in
|
|
viewModel.togglePinned()
|
|
})
|
|
}
|
|
|
|
sections.append(UIMenu(options: .displayInline, children: firstSectionItems))
|
|
|
|
var secondSectionItems = [UIAction]()
|
|
|
|
if viewModel.isMine {
|
|
secondSectionItems += [
|
|
UIAction(
|
|
title: viewModel.muted
|
|
? NSLocalizedString("status.unmute", comment: "")
|
|
: NSLocalizedString("status.mute", comment: ""),
|
|
image: UIImage(systemName: viewModel.muted ? "speaker" : "speaker.slash")) { _ in
|
|
viewModel.toggleMuted()
|
|
},
|
|
UIAction(
|
|
title: NSLocalizedString("status.delete", comment: ""),
|
|
image: UIImage(systemName: "trash"),
|
|
attributes: .destructive) { _ in
|
|
viewModel.confirmDelete(redraft: false)
|
|
},
|
|
UIAction(
|
|
title: NSLocalizedString("status.delete-and-redraft", comment: ""),
|
|
image: UIImage(systemName: "trash.circle"),
|
|
attributes: .destructive) { _ in
|
|
viewModel.confirmDelete(redraft: true)
|
|
}
|
|
]
|
|
|
|
sections.append(UIMenu(options: .displayInline, children: secondSectionItems))
|
|
} else {
|
|
if let relationship = viewModel.accountViewModel.relationship {
|
|
if relationship.muting {
|
|
secondSectionItems.append(UIAction(
|
|
title: NSLocalizedString("account.unmute", comment: ""),
|
|
image: UIImage(systemName: "speaker")) { _ in
|
|
viewModel.accountViewModel.confirmUnmute()
|
|
})
|
|
} else {
|
|
secondSectionItems.append(UIAction(
|
|
title: NSLocalizedString("account.mute", comment: ""),
|
|
image: UIImage(systemName: "speaker.slash")) { _ in
|
|
viewModel.accountViewModel.confirmMute()
|
|
})
|
|
}
|
|
|
|
if relationship.blocking {
|
|
secondSectionItems.append(UIAction(
|
|
title: NSLocalizedString("account.unblock", comment: ""),
|
|
image: UIImage(systemName: "slash.circle"),
|
|
attributes: .destructive) { _ in
|
|
viewModel.accountViewModel.confirmUnblock()
|
|
})
|
|
} else {
|
|
secondSectionItems.append(UIAction(
|
|
title: NSLocalizedString("account.block", comment: ""),
|
|
image: UIImage(systemName: "slash.circle"),
|
|
attributes: .destructive) { _ in
|
|
viewModel.accountViewModel.confirmBlock()
|
|
})
|
|
}
|
|
}
|
|
|
|
secondSectionItems.append(UIAction(
|
|
title: NSLocalizedString("report", comment: ""),
|
|
image: UIImage(systemName: "flag"),
|
|
attributes: .destructive) { _ in
|
|
viewModel.reportStatus()
|
|
})
|
|
|
|
sections.append(UIMenu(options: .displayInline, children: secondSectionItems))
|
|
|
|
if !viewModel.accountViewModel.isLocal,
|
|
let domain = viewModel.accountViewModel.domain,
|
|
let relationship = viewModel.accountViewModel.relationship {
|
|
let domainBlockAction: UIAction
|
|
|
|
if relationship.domainBlocking {
|
|
domainBlockAction = UIAction(
|
|
title: String.localizedStringWithFormat(
|
|
NSLocalizedString("account.domain-unblock-%@", comment: ""),
|
|
domain),
|
|
image: UIImage(systemName: "slash.circle"),
|
|
attributes: .destructive) { _ in
|
|
viewModel.accountViewModel.confirmDomainUnblock(domain: domain)
|
|
}
|
|
} else {
|
|
domainBlockAction = UIAction(
|
|
title: String.localizedStringWithFormat(
|
|
NSLocalizedString("account.domain-block-%@", comment: ""),
|
|
domain),
|
|
image: UIImage(systemName: "slash.circle"),
|
|
attributes: .destructive) { _ in
|
|
viewModel.accountViewModel.confirmDomainBlock(domain: domain)
|
|
}
|
|
}
|
|
|
|
sections.append(UIMenu(options: .displayInline, children: [domainBlockAction]))
|
|
}
|
|
}
|
|
|
|
return UIMenu(children: sections)
|
|
}
|
|
// swiftlint:enable function_body_length
|
|
|
|
func accessibilityAttributedLabel(forceShowContent: Bool) -> NSAttributedString {
|
|
let accessibilityAttributedLabel = NSMutableAttributedString(string: "")
|
|
|
|
if !reportSelectionSwitch.isHidden, reportSelectionSwitch.isOn {
|
|
accessibilityAttributedLabel.appendWithSeparator(NSLocalizedString("selected", comment: ""))
|
|
}
|
|
|
|
if !infoLabel.isHidden, let infoText = infoLabel.attributedText {
|
|
accessibilityAttributedLabel.appendWithSeparator(infoText)
|
|
}
|
|
|
|
if let displayName = displayNameLabel.attributedText {
|
|
if accessibilityAttributedLabel.string.isEmpty {
|
|
accessibilityAttributedLabel.append(displayName)
|
|
} else {
|
|
accessibilityAttributedLabel.appendWithSeparator(displayName)
|
|
}
|
|
}
|
|
|
|
accessibilityAttributedLabel.appendWithSeparator(
|
|
bodyView.accessibilityAttributedLabel(forceShowContent: forceShowContent))
|
|
|
|
if let accessibilityTime = statusConfiguration.viewModel.accessibilityTime {
|
|
accessibilityAttributedLabel.appendWithSeparator(accessibilityTime)
|
|
}
|
|
|
|
if statusConfiguration.viewModel.repliesCount > 0 {
|
|
accessibilityAttributedLabel.appendWithSeparator(
|
|
String.localizedStringWithFormat(
|
|
NSLocalizedString("status.replies-count-%ld", comment: ""),
|
|
statusConfiguration.viewModel.repliesCount))
|
|
}
|
|
|
|
if statusConfiguration.viewModel.identityContext.appPreferences.showReblogAndFavoriteCounts {
|
|
if statusConfiguration.viewModel.reblogsCount > 0 {
|
|
accessibilityAttributedLabel.appendWithSeparator(
|
|
String.localizedStringWithFormat(
|
|
NSLocalizedString("status.reblogs-count-%ld", comment: ""),
|
|
statusConfiguration.viewModel.reblogsCount))
|
|
}
|
|
|
|
if statusConfiguration.viewModel.favoritesCount > 0 {
|
|
accessibilityAttributedLabel.appendWithSeparator(
|
|
String.localizedStringWithFormat(
|
|
NSLocalizedString("status.favorites-count-%ld", comment: ""),
|
|
statusConfiguration.viewModel.favoritesCount))
|
|
}
|
|
}
|
|
|
|
if statusConfiguration.viewModel.configuration.isReplyOutOfContext {
|
|
accessibilityAttributedLabel.appendWithSeparator(
|
|
NSLocalizedString("status.accessibility.part-of-a-thread", comment: ""))
|
|
}
|
|
|
|
return accessibilityAttributedLabel
|
|
}
|
|
|
|
func setButtonImages(font: UIFont) {
|
|
let visibility = statusConfiguration.viewModel.visibility
|
|
let reblogSystemImageName: String
|
|
|
|
if statusConfiguration.viewModel.configuration.isContextParent {
|
|
reblogSystemImageName = "arrow.2.squarepath"
|
|
} else {
|
|
switch visibility {
|
|
case .public, .unlisted:
|
|
reblogSystemImageName = "arrow.2.squarepath"
|
|
default:
|
|
reblogSystemImageName = visibility.systemImageName
|
|
}
|
|
}
|
|
|
|
replyButton.setImage(UIImage(systemName: "bubble.right",
|
|
withConfiguration: UIImage.SymbolConfiguration(pointSize: font.pointSize)),
|
|
for: .normal)
|
|
reblogButton.setImage(UIImage(systemName: reblogSystemImageName,
|
|
withConfiguration: UIImage.SymbolConfiguration(
|
|
pointSize: font.pointSize,
|
|
weight: statusConfiguration.viewModel.reblogged ? .bold : .regular)),
|
|
for: .normal)
|
|
favoriteButton.setImage(UIImage(systemName: statusConfiguration.viewModel.favorited ? "star.fill" : "star",
|
|
withConfiguration: UIImage.SymbolConfiguration(pointSize: font.pointSize)),
|
|
for: .normal)
|
|
shareButton.setImage(UIImage(systemName: "square.and.arrow.up",
|
|
withConfiguration: UIImage.SymbolConfiguration(pointSize: font.pointSize)),
|
|
for: .normal)
|
|
menuButton.setImage(UIImage(systemName: "ellipsis",
|
|
withConfiguration: UIImage.SymbolConfiguration(pointSize: font.pointSize)),
|
|
for: .normal)
|
|
}
|
|
|
|
@objc func reblogButtonDoubleTap(sender: UIButton, event: UIEvent) {
|
|
guard
|
|
statusConfiguration.viewModel.identityContext.appPreferences.requireDoubleTapToReblog,
|
|
event.allTouches?.first?.tapCount == 2 else {
|
|
return
|
|
}
|
|
|
|
reblog()
|
|
}
|
|
|
|
@objc func favoriteButtonDoubleTap(sender: UIButton, event: UIEvent) {
|
|
guard
|
|
statusConfiguration.viewModel.identityContext.appPreferences.requireDoubleTapToFavorite,
|
|
event.allTouches?.first?.tapCount == 2 else {
|
|
return
|
|
}
|
|
|
|
favorite()
|
|
}
|
|
|
|
func setReblogButtonColor(reblogged: Bool) {
|
|
let reblogColor: UIColor = reblogged ? .systemGreen : .secondaryLabel
|
|
|
|
reblogButton.tintColor = reblogColor
|
|
reblogButton.setTitleColor(reblogColor, for: .normal)
|
|
|
|
if reblogged {
|
|
reblogButton.accessibilityLabel =
|
|
NSLocalizedString("status.reblog-button.undo.accessibility-label", comment: "")
|
|
} else {
|
|
reblogButton.accessibilityLabel =
|
|
NSLocalizedString("status.reblog-button.accessibility-label", comment: "")
|
|
}
|
|
}
|
|
|
|
func setFavoriteButtonColor(favorited: Bool) {
|
|
let favoriteColor: UIColor = favorited ? .systemYellow : .secondaryLabel
|
|
|
|
favoriteButton.tintColor = favoriteColor
|
|
favoriteButton.setTitleColor(favoriteColor, for: .normal)
|
|
|
|
if favorited {
|
|
favoriteButton.accessibilityLabel =
|
|
NSLocalizedString("status.favorite-button.undo.accessibility-label", comment: "")
|
|
} else {
|
|
favoriteButton.accessibilityLabel =
|
|
NSLocalizedString("status.favorite-button.accessibility-label", comment: "")
|
|
}
|
|
}
|
|
|
|
func reblog() {
|
|
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
|
|
|
if !UIAccessibility.isReduceMotionEnabled {
|
|
UIViewPropertyAnimator.runningPropertyAnimator(
|
|
withDuration: .defaultAnimationDuration,
|
|
delay: 0,
|
|
options: .curveLinear) {
|
|
self.setReblogButtonColor(reblogged: !self.statusConfiguration.viewModel.reblogged)
|
|
self.reblogButton.imageView?.transform =
|
|
self.reblogButton.imageView?.transform.rotated(by: .pi) ?? .identity
|
|
} completion: { _ in
|
|
self.reblogButton.imageView?.transform = .identity
|
|
}
|
|
}
|
|
|
|
statusConfiguration.viewModel.toggleReblogged()
|
|
}
|
|
|
|
func favorite() {
|
|
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
|
|
|
if !UIAccessibility.isReduceMotionEnabled {
|
|
UIViewPropertyAnimator.runningPropertyAnimator(
|
|
withDuration: .defaultAnimationDuration,
|
|
delay: 0,
|
|
options: .curveLinear) {
|
|
self.setFavoriteButtonColor(favorited: !self.statusConfiguration.viewModel.favorited)
|
|
self.favoriteButton.imageView?.transform =
|
|
self.favoriteButton.imageView?.transform.rotated(by: .pi) ?? .identity
|
|
} completion: { _ in
|
|
self.favoriteButton.imageView?.transform = .identity
|
|
}
|
|
}
|
|
|
|
statusConfiguration.viewModel.toggleFavorited()
|
|
}
|
|
|
|
func configureUserInteractionEnabledForAccessibility() {
|
|
isUserInteractionEnabled = !UIAccessibility.isVoiceOverRunning
|
|
|| statusConfiguration.viewModel.configuration.isContextParent
|
|
}
|
|
|
|
// swiftlint:disable:next function_body_length cyclomatic_complexity
|
|
func accessibilityCustomActions(viewModel: StatusViewModel) -> [UIAccessibilityCustomAction] {
|
|
guard !viewModel.configuration.isContextParent, reportSelectionSwitch.isHidden else {
|
|
return []
|
|
}
|
|
|
|
var actions = bodyView.accessibilityCustomActions ?? []
|
|
|
|
if replyButton.isEnabled {
|
|
actions.append(UIAccessibilityCustomAction(
|
|
name: replyButton.accessibilityLabel ?? "") { _ in
|
|
viewModel.reply()
|
|
|
|
return true
|
|
})
|
|
}
|
|
|
|
if viewModel.canBeReblogged, reblogButton.isEnabled {
|
|
actions.append(UIAccessibilityCustomAction(
|
|
name: reblogButton.accessibilityLabel ?? "") { [weak self] _ in
|
|
self?.reblog()
|
|
|
|
return true
|
|
})
|
|
}
|
|
|
|
if favoriteButton.isEnabled {
|
|
actions.append(UIAccessibilityCustomAction(
|
|
name: favoriteButton.accessibilityLabel ?? "") { [weak self] _ in
|
|
self?.favorite()
|
|
|
|
return true
|
|
})
|
|
}
|
|
|
|
if shareButton.isEnabled {
|
|
actions.append(UIAccessibilityCustomAction(
|
|
name: shareButton.accessibilityLabel ?? "") { _ in
|
|
viewModel.shareStatus()
|
|
|
|
return true
|
|
})
|
|
}
|
|
|
|
actions.append(
|
|
UIAccessibilityCustomAction(
|
|
name: NSLocalizedString("status.accessibility.view-author-profile",
|
|
comment: "")) { [weak self] _ in
|
|
self?.statusConfiguration.viewModel.accountSelected()
|
|
|
|
return true
|
|
})
|
|
|
|
if viewModel.isReblog {
|
|
actions.append(
|
|
UIAccessibilityCustomAction(
|
|
name: NSLocalizedString("status.accessibility.view-reblogger-profile",
|
|
comment: "")) { [weak self] _ in
|
|
self?.statusConfiguration.viewModel.rebloggerAccountSelected()
|
|
|
|
return true
|
|
})
|
|
}
|
|
|
|
actions.append(
|
|
UIAccessibilityCustomAction(
|
|
name: NSLocalizedString("accessibility.copy-text",
|
|
comment: "")) { [weak self] _ in
|
|
UIPasteboard.general.string = self?.bodyView.contentTextView.text
|
|
|
|
return true
|
|
})
|
|
|
|
if menuButton.isEnabled {
|
|
actions.append(UIAccessibilityCustomAction(
|
|
name: viewModel.bookmarked
|
|
? NSLocalizedString("status.unbookmark", comment: "")
|
|
: NSLocalizedString("status.bookmark", comment: "")) { _ in
|
|
viewModel.toggleBookmarked()
|
|
|
|
return true
|
|
})
|
|
|
|
if let pinned = viewModel.pinned {
|
|
actions.append(UIAccessibilityCustomAction(
|
|
name: pinned
|
|
? NSLocalizedString("status.unpin", comment: "")
|
|
: NSLocalizedString("status.pin", comment: "")) { _ in
|
|
viewModel.togglePinned()
|
|
|
|
return true
|
|
})
|
|
}
|
|
|
|
if viewModel.isMine {
|
|
actions += [
|
|
UIAccessibilityCustomAction(
|
|
name: viewModel.muted
|
|
? NSLocalizedString("status.unmute", comment: "")
|
|
: NSLocalizedString("status.mute", comment: "")) { _ in
|
|
viewModel.toggleMuted()
|
|
|
|
return true
|
|
},
|
|
UIAccessibilityCustomAction(
|
|
name: NSLocalizedString("status.delete", comment: "")) { _ in
|
|
viewModel.confirmDelete(redraft: false)
|
|
|
|
return true
|
|
},
|
|
UIAccessibilityCustomAction(
|
|
name: NSLocalizedString("status.delete-and-redraft", comment: "")) { _ in
|
|
viewModel.confirmDelete(redraft: true)
|
|
|
|
return true
|
|
}
|
|
]
|
|
} else {
|
|
if let relationship = viewModel.accountViewModel.relationship {
|
|
if relationship.muting {
|
|
actions.append(UIAccessibilityCustomAction(
|
|
name: NSLocalizedString("account.unmute", comment: "")) { _ in
|
|
viewModel.accountViewModel.confirmUnmute()
|
|
|
|
return true
|
|
})
|
|
} else {
|
|
actions.append(UIAccessibilityCustomAction(
|
|
name: NSLocalizedString("account.mute", comment: "")) { _ in
|
|
viewModel.accountViewModel.confirmMute()
|
|
|
|
return true
|
|
})
|
|
}
|
|
|
|
if relationship.blocking {
|
|
actions.append(UIAccessibilityCustomAction(
|
|
name: NSLocalizedString("account.unblock", comment: "")) { _ in
|
|
viewModel.accountViewModel.confirmUnblock()
|
|
|
|
return true
|
|
})
|
|
} else {
|
|
actions.append(UIAccessibilityCustomAction(
|
|
name: NSLocalizedString("account.block", comment: "")) { _ in
|
|
viewModel.accountViewModel.confirmBlock()
|
|
|
|
return true
|
|
})
|
|
}
|
|
}
|
|
actions.append(UIAccessibilityCustomAction(
|
|
name: NSLocalizedString("report", comment: "")) { _ in
|
|
viewModel.reportStatus()
|
|
|
|
return true
|
|
})
|
|
|
|
if !viewModel.accountViewModel.isLocal,
|
|
let domain = viewModel.accountViewModel.domain,
|
|
let relationship = viewModel.accountViewModel.relationship {
|
|
|
|
if relationship.domainBlocking {
|
|
actions.append(UIAccessibilityCustomAction(
|
|
name: String.localizedStringWithFormat(
|
|
NSLocalizedString("account.domain-unblock-%@", comment: ""),
|
|
domain)) { _ in
|
|
viewModel.accountViewModel.confirmDomainUnblock(domain: domain)
|
|
|
|
return true
|
|
})
|
|
} else {
|
|
actions.append(UIAccessibilityCustomAction(
|
|
name: String.localizedStringWithFormat(
|
|
NSLocalizedString("account.domain-block-%@", comment: ""),
|
|
domain)) { _ in
|
|
viewModel.accountViewModel.confirmDomainBlock(domain: domain)
|
|
|
|
return true
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return actions
|
|
}
|
|
|
|
func authenticatedIdentitiesMenu(action: @escaping (Identity) -> Void) -> UIMenu {
|
|
let imageTransformer = SDImageRoundCornerTransformer(
|
|
radius: .greatestFiniteMagnitude,
|
|
corners: .allCorners,
|
|
borderWidth: 0,
|
|
borderColor: nil)
|
|
|
|
return UIMenu(children: statusConfiguration.viewModel
|
|
.identityContext
|
|
.authenticatedOtherIdentities.map { identity in
|
|
UIDeferredMenuElement { completion in
|
|
let menuItemAction = UIAction(title: identity.handle) { _ in
|
|
action(identity)
|
|
}
|
|
|
|
if let image = identity.image {
|
|
SDWebImageManager.shared.loadImage(
|
|
with: image,
|
|
options: [.transformAnimatedImage],
|
|
context: [.imageTransformer: imageTransformer],
|
|
progress: nil) { (image, _, _, _, _, _) in
|
|
menuItemAction.image = image
|
|
|
|
completion([menuItemAction])
|
|
}
|
|
} else {
|
|
completion([menuItemAction])
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
private extension UIButton {
|
|
func setCountTitle(count: Int, isContextParent: Bool) {
|
|
setTitle((isContextParent || count == 0) ? nil : String(count), for: .normal)
|
|
}
|
|
}
|
|
// swiftlint:enable file_length
|