244 lines
8.6 KiB
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|