// // ItemCollectionViewCell.swift // Reader // // Created by Shadowfacts on 1/9/22. // import UIKit import SwiftSoup protocol ItemCollectionViewCellDelegate: AnyObject { func itemCellSelected(item: Item) } class ItemCollectionViewCell: UICollectionViewListCell { weak var delegate: ItemCollectionViewCellDelegate? private let titleLabel = UILabel() private let feedTitleLabel = UILabel() private let contentLabel = UILabel() private var shouldHighlight = true private var item: Item! override init(frame: CGRect) { super.init(frame: frame) backgroundConfiguration = .clear() let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .title3).withSymbolicTraits(.traitBold)!.withDesign(.serif)! titleLabel.font = UIFont(descriptor: descriptor, size: 0) titleLabel.numberOfLines = 0 feedTitleLabel.font = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .subheadline), size: 0) contentLabel.font = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .body).withDesign(.serif)!, size: 0) contentLabel.numberOfLines = 0 let stack = UIStackView(arrangedSubviews: [ titleLabel, feedTitleLabel, contentLabel, ]) stack.translatesAutoresizingMaskIntoConstraints = false stack.spacing = 8 stack.axis = .vertical addSubview(stack) NSLayoutConstraint.activate([ stack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), stack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20), stack.topAnchor.constraint(equalTo: topAnchor, constant: 8), stack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8), separatorLayoutGuide.leadingAnchor.constraint(equalTo: stack.leadingAnchor), ]) let doubleTap = DoubleTapRecognizer(target: self, action: #selector(cellDoubleTapped)) doubleTap.onTouchesBegan = highlightCell addGestureRecognizer(doubleTap) let singleTap = UITapGestureRecognizer(target: self, action: #selector(cellSingleTapped)) singleTap.require(toFail: doubleTap) addGestureRecognizer(singleTap) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func updateUI(item: Item) { self.item = item titleLabel.text = item.title feedTitleLabel.text = item.feed!.title ?? item.feed!.url?.host if let content = item.content { let doc = try! SwiftSoup.parse(content) contentLabel.text = try! doc.select("p").first()?.text() } else { contentLabel.text = "" } contentLabel.isHidden = contentLabel.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ?? true setColors() } private func setColors() { if item.read { titleLabel.textColor = .secondaryLabel feedTitleLabel.textColor = .secondaryLabel contentLabel.textColor = .secondaryLabel } else { titleLabel.textColor = .label feedTitleLabel.textColor = .tintColor contentLabel.textColor = .appContentPreviewLabel } } override func touchesBegan(_ touches: Set, with event: UIEvent?) { highlightCell() } @objc private func highlightCell() { guard shouldHighlight else { return } shouldHighlight = false UIView.animateKeyframes(withDuration: 0.4, delay: 0, options: .allowUserInteraction) { UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) { self.backgroundColor = .appCellHighlightBackground } UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) { self.backgroundColor = nil } } } @objc private func cellDoubleTapped() { self.item.read = !self.item.read // i don't know why .transition works but .animate doesn't UIView.transition(with: self, duration: 0.2, options: .transitionCrossDissolve) { self.setColors() } shouldHighlight = true } @objc private func cellSingleTapped() { delegate?.itemCellSelected(item: item) shouldHighlight = true } } private class DoubleTapRecognizer: UITapGestureRecognizer { var onTouchesBegan: (() -> Void)! override init(target: Any?, action: Selector?) { super.init(target: target, action: action) numberOfTapsRequired = 2 delaysTouchesBegan = true } override func touchesBegan(_ touches: Set, with event: UIEvent) { super.touchesBegan(touches, with: event) onTouchesBegan() } }