2022-11-12 03:35:18 +01:00
|
|
|
//
|
|
|
|
// OpenGraphView.swift
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// Created by Kyle Bashour on 11/11/22.
|
|
|
|
//
|
|
|
|
|
2022-11-14 22:26:25 +01:00
|
|
|
import AlamofireImage
|
2022-11-26 05:16:42 +01:00
|
|
|
import Combine
|
2022-11-12 03:35:18 +01:00
|
|
|
import MastodonAsset
|
|
|
|
import MastodonCore
|
2022-11-24 06:51:39 +01:00
|
|
|
import CoreDataStack
|
2022-11-12 03:35:18 +01:00
|
|
|
import UIKit
|
|
|
|
|
2022-11-27 06:47:49 +01:00
|
|
|
public final class StatusCardControl: UIControl {
|
2022-11-26 05:16:42 +01:00
|
|
|
private var disposeBag = Set<AnyCancellable>()
|
2022-11-12 03:35:18 +01:00
|
|
|
|
2022-11-27 04:21:47 +01:00
|
|
|
private let containerStackView = UIStackView()
|
|
|
|
private let labelStackView = UIStackView()
|
|
|
|
|
2022-11-24 16:48:07 +01:00
|
|
|
private let highlightView = UIView()
|
2022-11-12 03:35:18 +01:00
|
|
|
private let imageView = UIImageView()
|
|
|
|
private let titleLabel = UILabel()
|
2022-11-24 06:51:39 +01:00
|
|
|
private let linkLabel = UILabel()
|
|
|
|
|
2022-11-28 06:00:03 +01:00
|
|
|
private var layout: Layout?
|
|
|
|
private var layoutConstraints: [NSLayoutConstraint] = []
|
2022-11-12 03:35:18 +01:00
|
|
|
|
2022-11-24 16:48:07 +01:00
|
|
|
public override var isHighlighted: Bool {
|
2022-11-27 06:47:49 +01:00
|
|
|
didSet { highlightView.isHidden = !isHighlighted }
|
2022-11-24 16:48:07 +01:00
|
|
|
}
|
|
|
|
|
2022-11-12 03:35:18 +01:00
|
|
|
public override init(frame: CGRect) {
|
|
|
|
super.init(frame: frame)
|
|
|
|
|
2022-11-26 05:16:42 +01:00
|
|
|
apply(theme: ThemeService.shared.currentTheme.value)
|
|
|
|
|
|
|
|
ThemeService.shared.currentTheme.sink { [weak self] theme in
|
|
|
|
self?.apply(theme: theme)
|
|
|
|
}.store(in: &disposeBag)
|
|
|
|
|
2022-11-12 03:35:18 +01:00
|
|
|
clipsToBounds = true
|
|
|
|
layer.cornerCurve = .continuous
|
|
|
|
layer.cornerRadius = 10
|
|
|
|
|
2022-11-27 06:47:49 +01:00
|
|
|
if #available(iOS 15, *) {
|
|
|
|
maximumContentSizeCategory = .accessibilityLarge
|
|
|
|
}
|
|
|
|
|
2022-11-24 16:48:07 +01:00
|
|
|
highlightView.backgroundColor = UIColor.black.withAlphaComponent(0.1)
|
|
|
|
highlightView.isHidden = true
|
|
|
|
|
2022-11-14 22:26:25 +01:00
|
|
|
titleLabel.numberOfLines = 2
|
2022-11-12 03:35:18 +01:00
|
|
|
titleLabel.textColor = Asset.Colors.Label.primary.color
|
2022-11-26 05:16:42 +01:00
|
|
|
titleLabel.font = .preferredFont(forTextStyle: .body)
|
2022-11-12 03:35:18 +01:00
|
|
|
|
2022-11-24 06:51:39 +01:00
|
|
|
linkLabel.numberOfLines = 1
|
|
|
|
linkLabel.textColor = Asset.Colors.Label.secondary.color
|
2022-11-26 05:16:42 +01:00
|
|
|
linkLabel.font = .preferredFont(forTextStyle: .subheadline)
|
2022-11-12 03:35:18 +01:00
|
|
|
|
2022-11-24 06:51:39 +01:00
|
|
|
imageView.tintColor = Asset.Colors.Label.secondary.color
|
2022-11-14 22:26:25 +01:00
|
|
|
imageView.contentMode = .scaleAspectFill
|
|
|
|
imageView.clipsToBounds = true
|
2022-11-27 04:21:47 +01:00
|
|
|
imageView.setContentHuggingPriority(.zero, for: .horizontal)
|
|
|
|
imageView.setContentHuggingPriority(.zero, for: .vertical)
|
|
|
|
imageView.setContentCompressionResistancePriority(.zero, for: .horizontal)
|
|
|
|
imageView.setContentCompressionResistancePriority(.zero, for: .vertical)
|
|
|
|
|
|
|
|
labelStackView.addArrangedSubview(titleLabel)
|
|
|
|
labelStackView.addArrangedSubview(linkLabel)
|
|
|
|
labelStackView.layoutMargins = .init(top: 10, left: 10, bottom: 10, right: 10)
|
|
|
|
labelStackView.isLayoutMarginsRelativeArrangement = true
|
|
|
|
labelStackView.axis = .vertical
|
2022-11-28 06:00:03 +01:00
|
|
|
labelStackView.spacing = 2
|
2022-11-27 04:21:47 +01:00
|
|
|
|
|
|
|
containerStackView.addArrangedSubview(imageView)
|
|
|
|
containerStackView.addArrangedSubview(labelStackView)
|
|
|
|
containerStackView.isUserInteractionEnabled = false
|
2022-11-27 07:05:43 +01:00
|
|
|
containerStackView.distribution = .fill
|
2022-11-27 04:21:47 +01:00
|
|
|
|
|
|
|
addSubview(containerStackView)
|
2022-11-24 16:48:07 +01:00
|
|
|
addSubview(highlightView)
|
2022-11-12 03:35:18 +01:00
|
|
|
|
2022-11-27 04:21:47 +01:00
|
|
|
containerStackView.translatesAutoresizingMaskIntoConstraints = false
|
2022-11-24 16:48:07 +01:00
|
|
|
highlightView.translatesAutoresizingMaskIntoConstraints = false
|
2022-11-12 03:35:18 +01:00
|
|
|
|
2022-11-27 04:21:47 +01:00
|
|
|
containerStackView.pinToParent()
|
|
|
|
highlightView.pinToParent()
|
2022-11-12 03:35:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
required init?(coder: NSCoder) {
|
|
|
|
fatalError("init(coder:) has not been implemented")
|
|
|
|
}
|
|
|
|
|
2022-11-24 06:51:39 +01:00
|
|
|
public func configure(card: Card) {
|
2022-11-27 06:47:49 +01:00
|
|
|
if let host = card.url?.host {
|
|
|
|
accessibilityLabel = "\(card.title) \(host)"
|
|
|
|
} else {
|
|
|
|
accessibilityLabel = card.title
|
|
|
|
}
|
|
|
|
|
2022-11-24 06:51:39 +01:00
|
|
|
titleLabel.text = card.title
|
|
|
|
linkLabel.text = card.url?.host
|
|
|
|
imageView.contentMode = .center
|
2022-11-14 22:26:25 +01:00
|
|
|
|
2022-11-24 06:51:39 +01:00
|
|
|
imageView.sd_setImage(
|
|
|
|
with: card.imageURL,
|
2022-11-28 06:00:03 +01:00
|
|
|
placeholderImage: icon(for: card.layout)
|
|
|
|
) { [weak self] image, _, _, _ in
|
2022-11-24 06:51:39 +01:00
|
|
|
if image != nil {
|
2022-11-28 06:00:03 +01:00
|
|
|
self?.imageView.contentMode = .scaleAspectFill
|
2022-11-24 06:51:39 +01:00
|
|
|
}
|
2022-11-14 22:26:25 +01:00
|
|
|
|
2022-11-28 06:00:03 +01:00
|
|
|
self?.containerStackView.setNeedsLayout()
|
|
|
|
self?.containerStackView.layoutIfNeeded()
|
2022-11-14 22:26:25 +01:00
|
|
|
}
|
2022-11-28 06:00:03 +01:00
|
|
|
|
|
|
|
updateConstraints(for: card.layout)
|
2022-11-12 03:35:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public override func didMoveToWindow() {
|
|
|
|
super.didMoveToWindow()
|
|
|
|
|
|
|
|
if let window = window {
|
|
|
|
layer.borderWidth = 1 / window.screen.scale
|
|
|
|
}
|
|
|
|
}
|
2022-11-14 22:26:25 +01:00
|
|
|
|
2022-11-28 06:00:03 +01:00
|
|
|
private func updateConstraints(for layout: Layout) {
|
|
|
|
guard layout != self.layout else { return }
|
|
|
|
self.layout = layout
|
|
|
|
|
|
|
|
NSLayoutConstraint.deactivate(layoutConstraints)
|
|
|
|
|
|
|
|
switch layout {
|
|
|
|
case .large(let aspectRatio):
|
|
|
|
containerStackView.alignment = .fill
|
|
|
|
containerStackView.axis = .vertical
|
|
|
|
layoutConstraints = [
|
|
|
|
imageView.widthAnchor.constraint(
|
|
|
|
equalTo: imageView.heightAnchor,
|
|
|
|
multiplier: aspectRatio
|
|
|
|
)
|
|
|
|
// This priority is important or constraints break;
|
|
|
|
// it still renders the card correctly.
|
|
|
|
.priority(.defaultLow - 1),
|
2022-12-02 22:02:05 +01:00
|
|
|
// set a reasonable max height for very tall images
|
|
|
|
imageView.heightAnchor
|
|
|
|
.constraint(lessThanOrEqualToConstant: 400)
|
2022-11-28 06:00:03 +01:00
|
|
|
]
|
|
|
|
case .compact:
|
|
|
|
containerStackView.alignment = .center
|
|
|
|
containerStackView.axis = .horizontal
|
|
|
|
layoutConstraints = [
|
|
|
|
imageView.heightAnchor.constraint(equalTo: heightAnchor),
|
|
|
|
imageView.widthAnchor.constraint(equalToConstant: 85),
|
|
|
|
heightAnchor.constraint(equalToConstant: 85).priority(.defaultLow - 1),
|
|
|
|
heightAnchor.constraint(greaterThanOrEqualToConstant: 85)
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
NSLayoutConstraint.activate(layoutConstraints)
|
2022-11-24 06:51:39 +01:00
|
|
|
}
|
|
|
|
|
2022-11-28 06:00:03 +01:00
|
|
|
private func icon(for layout: Layout) -> UIImage? {
|
|
|
|
switch layout {
|
|
|
|
case .compact:
|
|
|
|
return UIImage(systemName: "newspaper.fill")
|
|
|
|
case .large:
|
|
|
|
let configuration = UIImage.SymbolConfiguration(pointSize: 32)
|
|
|
|
return UIImage(systemName: "photo", withConfiguration: configuration)
|
|
|
|
}
|
2022-11-14 22:26:25 +01:00
|
|
|
}
|
2022-11-26 05:16:42 +01:00
|
|
|
|
|
|
|
private func apply(theme: Theme) {
|
|
|
|
layer.borderColor = theme.separator.cgColor
|
|
|
|
imageView.backgroundColor = theme.systemElevatedBackgroundColor
|
|
|
|
}
|
2022-11-12 03:35:18 +01:00
|
|
|
}
|
2022-11-27 04:21:47 +01:00
|
|
|
|
2022-11-28 06:00:03 +01:00
|
|
|
private extension StatusCardControl {
|
|
|
|
enum Layout: Equatable {
|
|
|
|
case compact
|
|
|
|
case large(aspectRatio: CGFloat)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private extension Card {
|
|
|
|
var layout: StatusCardControl.Layout {
|
2022-12-02 21:54:02 +01:00
|
|
|
let aspectRatio = CGFloat(width) / CGFloat(height)
|
|
|
|
return abs(aspectRatio - 1) < 0.05 || image == nil
|
2022-11-28 06:00:03 +01:00
|
|
|
? .compact
|
2022-12-02 21:54:02 +01:00
|
|
|
: .large(aspectRatio: aspectRatio)
|
2022-11-28 06:00:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-27 04:21:47 +01:00
|
|
|
private extension UILayoutPriority {
|
|
|
|
static let zero = UILayoutPriority(rawValue: 0)
|
|
|
|
}
|