// // TrendingLinkCardCollectionViewCell.swift // Tusker // // Created by Shadowfacts on 6/29/22. // Copyright © 2022 Shadowfacts. All rights reserved. // import UIKit import Pachyderm class TrendingLinkCardCollectionViewCell: UICollectionViewCell { private var card: Card? private var isGrayscale = false private var thumbnailRequest: ImageCache.Request? @IBOutlet weak var thumbnailView: UIImageView! @IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var descriptionLabel: UILabel! @IBOutlet weak var providerLabel: UILabel! @IBOutlet weak var activityLabel: UILabel! @IBOutlet weak var historyView: TrendHistoryView! private var hoverGestureAnimator: UIViewPropertyAnimator? override func awakeFromNib() { super.awakeFromNib() layer.shadowOpacity = 0.2 layer.shadowRadius = 8 layer.shadowOffset = .zero layer.masksToBounds = false updateLayerColors() NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil) addGestureRecognizer(UIHoverGestureRecognizer(target: self, action: #selector(hoverRecognized))) } override func layoutSubviews() { super.layoutSubviews() contentView.layer.cornerRadius = 0.05 * bounds.width } func updateUI(card: Card) { self.card = card self.thumbnailView.image = nil updateGrayscaleableUI(card: card) updateUIForPreferences() let title = card.title.trimmingCharacters(in: .whitespacesAndNewlines) titleLabel.text = title let provider = card.providerName!.trimmingCharacters(in: .whitespacesAndNewlines) providerLabel.text = provider let description = card.description.trimmingCharacters(in: .whitespacesAndNewlines) descriptionLabel.text = description descriptionLabel.isHidden = description.isEmpty let sorted = card.history!.sorted(by: { $0.day < $1.day }) let lastTwo = sorted[(sorted.count - 2)...] let accounts = lastTwo.map(\.accounts).reduce(0, +) let uses = lastTwo.map(\.uses).reduce(0, +) // U+2009 THIN SPACE let activityStr = NSMutableAttributedString(string: "\(accounts.formatted())\u{2009}") activityStr.append(NSAttributedString(attachment: NSTextAttachment(image: UIImage(systemName: "person")!))) activityStr.append(NSAttributedString(string: ", \(uses.formatted())\u{2009}")) activityStr.append(NSAttributedString(attachment: NSTextAttachment(image: UIImage(systemName: "square.text.square")!))) activityLabel.attributedText = activityStr historyView.setHistory(card.history) historyView.isHidden = card.history == nil || card.history!.count < 2 } @objc private func updateUIForPreferences() { if isGrayscale != Preferences.shared.grayscaleImages, let card { updateGrayscaleableUI(card: card) } } private func updateGrayscaleableUI(card: Card) { isGrayscale = Preferences.shared.grayscaleImages if let imageURL = card.image, let url = URL(imageURL) { thumbnailRequest = ImageCache.attachments.get(url, completion: { _, image in guard let image, let transformedImage = ImageGrayscalifier.convertIfNecessary(url: url, image: image) else { return } DispatchQueue.main.async { self.thumbnailView.image = transformedImage } }) if thumbnailRequest != nil { loadBlurHash(card: card) } } } private func loadBlurHash(card: Card) { guard let hash = card.blurhash else { return } AttachmentView.queue.async { [weak self] in let size: CGSize if let width = card.width, let height = card.height { let aspectRatio = CGFloat(width) / CGFloat(height) if aspectRatio > 1 { size = CGSize(width: 32, height: 32 / aspectRatio) } else { size = CGSize(width: 32 * aspectRatio, height: 32) } } else { size = CGSize(width: 32, height: 32) } guard let preview = UIImage(blurHash: hash, size: size) else { return } DispatchQueue.main.async { [weak self] in guard let self, self.card?.url == card.url, self.thumbnailView.image == nil else { return } self.thumbnailView.image = preview } } } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) updateLayerColors() } private func updateLayerColors() { if traitCollection.userInterfaceStyle == .dark { layer.shadowColor = UIColor.darkGray.cgColor } else { layer.shadowColor = UIColor.black.cgColor } } // MARK: Interaction @objc private func hoverRecognized(_ recognizer: UIHoverGestureRecognizer) { switch recognizer.state { case .began, .changed: hoverGestureAnimator = UIViewPropertyAnimator(duration: 0.2, curve: .easeInOut, animations: { self.transform = CGAffineTransform(scaleX: 1.05, y: 1.05) }) hoverGestureAnimator!.startAnimation() case .ended: hoverGestureAnimator?.stopAnimation(true) hoverGestureAnimator?.addAnimations { self.transform = .identity } hoverGestureAnimator?.startAnimation() default: break } } }