Tusker/Tusker/Views/Status/StatusCardView.swift

244 lines
8.6 KiB
Swift

//
// StatusCardView.swift
// Tusker
//
// Created by Shadowfacts on 10/25/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
import SafariServices
class StatusCardView: UIView {
weak var navigationDelegate: TuskerNavigationDelegate?
var card: Card? {
didSet {
if let card = card {
self.updateUI(card: card)
}
}
}
private let activeBackgroundColor = UIColor.secondarySystemFill
private let inactiveBackgroundColor = UIColor.secondarySystemBackground
private var imageRequest: ImageCache.Request?
private var isGrayscale = false
private var titleLabel: UILabel!
private var descriptionLabel: UILabel!
private var imageView: UIImageView!
private var placeholderImageView: UIImageView!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
self.clipsToBounds = true
self.layer.cornerRadius = 6.5
self.layer.borderWidth = 1
self.layer.borderColor = UIColor.lightGray.cgColor
self.backgroundColor = inactiveBackgroundColor
self.addInteraction(UIContextMenuInteraction(delegate: self))
titleLabel = UILabel()
titleLabel.font = UIFont(descriptor: UIFontDescriptor.preferredFontDescriptor(withTextStyle: .subheadline).withSymbolicTraits(.traitBold)!, size: 0)
titleLabel.numberOfLines = 2
descriptionLabel = UILabel()
descriptionLabel.font = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .caption1), size: 0)
descriptionLabel.numberOfLines = 2
descriptionLabel.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
let vStack = UIStackView(arrangedSubviews: [
titleLabel,
descriptionLabel
])
vStack.axis = .vertical
vStack.alignment = .leading
vStack.distribution = .fill
vStack.spacing = 0
imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
let hStack = UIStackView(arrangedSubviews: [
imageView,
vStack
])
hStack.translatesAutoresizingMaskIntoConstraints = false
hStack.axis = .horizontal
hStack.alignment = .center
hStack.distribution = .fill
hStack.spacing = 4
addSubview(hStack)
placeholderImageView = UIImageView(image: UIImage(systemName: "doc.text"))
placeholderImageView.translatesAutoresizingMaskIntoConstraints = false
placeholderImageView.contentMode = .scaleAspectFit
placeholderImageView.tintColor = .gray
addSubview(placeholderImageView)
NSLayoutConstraint.activate([
imageView.heightAnchor.constraint(equalTo: heightAnchor),
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor),
vStack.heightAnchor.constraint(equalTo: heightAnchor, constant: -8),
hStack.leadingAnchor.constraint(equalTo: leadingAnchor),
hStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -4),
hStack.topAnchor.constraint(equalTo: topAnchor),
hStack.bottomAnchor.constraint(equalTo: bottomAnchor),
placeholderImageView.widthAnchor.constraint(equalToConstant: 30),
placeholderImageView.heightAnchor.constraint(equalToConstant: 30),
placeholderImageView.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
placeholderImageView.centerYAnchor.constraint(equalTo: imageView.centerYAnchor),
])
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
}
private func updateUI(card: Card) {
self.imageView.image = nil
updateGrayscaleableUI(card: card)
updateUIForPreferences()
let title = card.title.trimmingCharacters(in: .whitespacesAndNewlines)
titleLabel.text = title
titleLabel.isHidden = title.isEmpty
let description = card.description.trimmingCharacters(in: .whitespacesAndNewlines)
descriptionLabel.text = description
descriptionLabel.isHidden = description.isEmpty
}
@objc private func updateUIForPreferences() {
if isGrayscale != Preferences.shared.grayscaleImages,
let card = card {
updateGrayscaleableUI(card: card)
}
}
private func updateGrayscaleableUI(card: Card) {
isGrayscale = Preferences.shared.grayscaleImages
if let imageURL = card.image {
placeholderImageView.isHidden = true
imageRequest = ImageCache.attachments.get(imageURL, completion: { (_, image) in
guard let image = image,
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: imageURL, image: image) else {
return
}
DispatchQueue.main.async {
self.imageView.image = transformedImage
}
})
if imageRequest != nil {
loadBlurHash()
}
} else {
placeholderImageView.isHidden = false
}
}
private func loadBlurHash() {
guard let card = card, let hash = card.blurhash else { return }
let imageViewSize = self.imageView.bounds.size
// todo: merge this code with AttachmentView, use a single DispatchQueue
DispatchQueue.global(qos: .default).async { [weak self] in
guard let self = self else { return }
let size: CGSize
if let width = card.width, let height = card.height {
size = CGSize(width: width, height: height)
} else {
size = imageViewSize
}
guard let preview = UIImage(blurHash: hash, size: size) else {
return
}
DispatchQueue.main.async { [weak self] in
guard let self = self,
self.card?.url == card.url,
self.imageView.image == nil else { return }
self.imageView.image = preview
}
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
backgroundColor = activeBackgroundColor
setNeedsDisplay()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
backgroundColor = inactiveBackgroundColor
setNeedsDisplay()
if let card = card, let delegate = navigationDelegate {
delegate.selected(url: card.url)
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
backgroundColor = inactiveBackgroundColor
setNeedsDisplay()
}
}
extension StatusCardView: MenuPreviewProvider {
}
extension StatusCardView: UIContextMenuInteractionDelegate {
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
guard let card = card else { return nil }
return UIContextMenuConfiguration(identifier: nil) {
return SFSafariViewController(url: card.url)
} actionProvider: { (_) in
let actions = self.actionsForURL(card.url, sourceView: self)
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: actions)
}
}
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
if let viewController = animator.previewViewController,
let delegate = navigationDelegate {
animator.preferredCommitStyle = .pop
animator.addCompletion {
if let customPresenting = viewController as? CustomPreviewPresenting {
customPresenting.presentFromPreview(presenter: delegate)
} else {
delegate.show(viewController)
}
}
}
}
}