// // 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 let contentContainer = StatusContentContainer(useTopSpacer: false).configure { $0.contentTextView.defaultFont = TimelineStatusCollectionViewCell.contentFont $0.contentTextView.monospaceFont = TimelineStatusCollectionViewCell.monospaceFont $0.contentTextView.paragraphStyle = TimelineStatusCollectionViewCell.contentParagraphStyle $0.setContentHuggingPriority(.defaultLow, for: .vertical) } 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") } // todo: accessibility // 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) contentContainer.contentTextView.setTextFrom(edit: edit, index: index) contentContainer.contentTextView.navigationDelegate = delegate contentContainer.attachmentsView.delegate = self contentContainer.attachmentsView.updateUI(attachments: edit.attachments) contentContainer.pollView.isHidden = edit.poll == nil contentContainer.pollView.updateUI(poll: edit.poll, emojis: edit.emojis) contentContainer.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 = contentContainer.attachmentsView.attachments! let sourceViews = attachments.map { contentContainer.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) } }