Tusker/Tusker/Screens/Status Edit History/StatusEditCollectionViewCel...

253 lines
9.6 KiB
Swift

//
// StatusEditCollectionViewCell.swift
// Tusker
//
// Created by Shadowfacts on 5/11/23.
// Copyright © 2023 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
@MainActor
protocol StatusEditCollectionViewCellDelegate: AnyObject, TuskerNavigationDelegate {
func statusEditCellNeedsReconfigure(_ cell: StatusEditCollectionViewCell, animated: Bool, completion: (() -> Void)?)
}
class StatusEditCollectionViewCell: UICollectionViewListCell {
private lazy var contentVStack = UIStackView(arrangedSubviews: [
timestampLabel,
contentWarningLabel,
collapseButton,
contentContainer,
]).configure {
$0.axis = .vertical
$0.spacing = 4
$0.alignment = .fill
}
private lazy var timestampLabel = UILabel().configure {
$0.textColor = .secondaryLabel
$0.font = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .body).addingAttributes([
.traits: [
UIFontDescriptor.TraitKey.weight: UIFont.Weight.light.rawValue,
]
]), size: 0)
$0.adjustsFontForContentSizeCategory = true
}
private lazy var contentWarningLabel = EmojiLabel().configure {
$0.numberOfLines = 0
$0.textColor = .secondaryLabel
$0.font = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .body).addingAttributes([
.traits: [
UIFontDescriptor.TraitKey.weight: UIFont.Weight.bold.rawValue
]
]), size: 0)
$0.adjustsFontForContentSizeCategory = true
$0.setContentHuggingPriority(.defaultHigh, for: .vertical)
$0.isUserInteractionEnabled = true
$0.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(collapseButtonPressed)))
}
private lazy var collapseButton = StatusCollapseButton(configuration: {
var config = UIButton.Configuration.filled()
config.image = UIImage(systemName: "chevron.down")
return config
}()).configure {
$0.tintAdjustmentMode = .normal
$0.setContentHuggingPriority(.defaultHigh, for: .vertical)
$0.addTarget(self, action: #selector(collapseButtonPressed), for: .touchUpInside)
}
private lazy var contentContainer = StatusContentContainer(arrangedSubviews: [
contentTextView,
cardView,
attachmentsView,
pollView,
] as! [any StatusContentView], useTopSpacer: false).configure {
$0.setContentHuggingPriority(.defaultLow, for: .vertical)
}
private let contentTextView = ContentTextView().configure {
$0.adjustsFontForContentSizeCategory = true
$0.isScrollEnabled = false
$0.backgroundColor = nil
$0.isEditable = false
$0.isSelectable = false
}
private let cardView = StatusCardView().configure {
$0.heightAnchor.constraint(equalToConstant: StatusContentContainer.cardViewHeight).isActive = true
}
private let attachmentsView = AttachmentsContainerView()
private let pollView = StatusEditPollView()
weak var delegate: StatusEditCollectionViewCellDelegate?
private var mastodonController: MastodonController! { delegate?.apiController }
private var edit: StatusEdit!
private var statusState: CollapseState!
override init(frame: CGRect) {
super.init(frame: frame)
contentVStack.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(contentVStack)
NSLayoutConstraint.activate([
contentVStack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
contentVStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -6),
contentVStack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
contentVStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Accessibility
override var isAccessibilityElement: Bool {
get { true }
set {}
}
override var accessibilityAttributedLabel: NSAttributedString? {
get {
var str: AttributedString = ""
if statusState.collapsed ?? false {
if !edit.spoilerText.isEmpty {
str += AttributedString(edit.spoilerText)
str += ", "
}
str += "collapsed"
} else {
str += AttributedString(contentTextView.attributedText)
if edit.attachments.count > 0 {
let includeDescriptions: Bool
switch Preferences.shared.attachmentBlurMode {
case .useStatusSetting:
includeDescriptions = !Preferences.shared.blurMediaBehindContentWarning || edit.spoilerText.isEmpty
case .always:
includeDescriptions = true
case .never:
includeDescriptions = false
}
if includeDescriptions {
if edit.attachments.count == 1 {
let attachment = edit.attachments[0]
let desc = attachment.description?.isEmpty == false ? attachment.description! : "no description"
str += AttributedString(", attachment: \(desc)")
} else {
for (index, attachment) in edit.attachments.enumerated() {
let desc = attachment.description?.isEmpty == false ? attachment.description! : "no description"
str += AttributedString(", attachment \(index + 1): \(desc)")
}
}
} else {
str += AttributedString(", \(edit.attachments.count) attachment\(edit.attachments.count == 1 ? "" : "s")")
}
}
if edit.poll != nil {
str += ", poll"
}
}
return NSAttributedString(str)
}
set {}
}
override var accessibilityHint: String? {
get {
if statusState.collapsed ?? false {
return "Double tap to expand the post."
} else {
return nil
}
}
set {}
}
override func accessibilityActivate() -> Bool {
if statusState.collapsed ?? false {
collapseButtonPressed()
}
return true
}
// MARK: Configure UI
func updateUI(edit: StatusEdit, state: CollapseState, index: Int) {
self.edit = edit
self.statusState = state
timestampLabel.text = ConversationMainStatusCollectionViewCell.dateFormatter.string(from: edit.createdAt)
contentTextView.attributedText = TimelineStatusCollectionViewCell.htmlConverter.convert(edit.content)
contentTextView.setEmojis(edit.emojis, identifier: index)
contentTextView.navigationDelegate = delegate
attachmentsView.delegate = self
attachmentsView.updateUI(attachments: edit.attachments)
pollView.isHidden = edit.poll == nil
pollView.updateUI(poll: edit.poll, emojis: edit.emojis)
cardView.isHidden = true
contentWarningLabel.text = edit.spoilerText
contentWarningLabel.isHidden = edit.spoilerText.isEmpty
if !contentWarningLabel.isHidden {
contentWarningLabel.setEmojis(edit.emojis, identifier: index)
}
_ = state.resolveFor(status: edit, height: {
let width = self.bounds.width - 2*16
return contentContainer.estimateVisibleSubviewHeight(effectiveWidth: width)
})
collapseButton.isHidden = !state.collapsible!
contentContainer.setCollapsed(state.collapsed!)
if state.collapsed! {
contentContainer.alpha = 0
// TODO: is this accessing the image view before the button's been laid out?
collapseButton.imageView!.transform = CGAffineTransform(rotationAngle: 0)
collapseButton.accessibilityLabel = NSLocalizedString("Expand Status", comment: "expand status button accessibility label")
} else {
contentContainer.alpha = 1
collapseButton.imageView!.transform = CGAffineTransform(rotationAngle: .pi)
collapseButton.accessibilityLabel = NSLocalizedString("Collapse Status", comment: "collapse status button accessibility label")
}
}
// MARK: Interaction
@objc private func collapseButtonPressed() {
statusState.collapsed!.toggle()
contentContainer.layer.masksToBounds = true
delegate?.statusEditCellNeedsReconfigure(self, animated: true) {
self.contentContainer.layer.masksToBounds = false
}
}
}
extension StatusEditCollectionViewCell: AttachmentViewDelegate {
func attachmentViewGallery(startingAt index: Int) -> GalleryViewController? {
guard let delegate else {
return nil
}
let attachments = attachmentsView.attachments!
let sourceViews = attachments.map {
attachmentsView.getAttachmentView(for: $0)
}
let gallery = delegate.gallery(attachments: attachments, sourceViews: sourceViews, startIndex: index)
return gallery
}
func attachmentViewPresent(_ vc: UIViewController, animated: Bool) {
delegate?.present(vc, animated: animated)
}
}