Working pretty well

This commit is contained in:
Kyle Bashour 2022-11-23 21:51:39 -08:00
parent 595b46e96e
commit f8d1afc7e4
6 changed files with 119 additions and 93 deletions

View File

@ -9,17 +9,17 @@
</entity>
<entity name="Card" representedClassName="CoreDataStack.Card" syncable="YES">
<attribute name="authorName" optional="YES" attributeType="String"/>
<attribute name="authorURL" optional="YES" attributeType="String"/>
<attribute name="authorURLRaw" optional="YES" attributeType="String"/>
<attribute name="blurhash" optional="YES" attributeType="String"/>
<attribute name="desc" attributeType="String"/>
<attribute name="embedURL" optional="YES" attributeType="String"/>
<attribute name="embedURLRaw" optional="YES" attributeType="String"/>
<attribute name="height" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="image" optional="YES" attributeType="String"/>
<attribute name="providerName" optional="YES" attributeType="String"/>
<attribute name="providerURL" optional="YES" attributeType="String"/>
<attribute name="providerURLRaw" optional="YES" attributeType="String"/>
<attribute name="title" attributeType="String"/>
<attribute name="typeRaw" attributeType="String"/>
<attribute name="url" attributeType="String"/>
<attribute name="urlRaw" attributeType="String"/>
<attribute name="width" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="card" inverseEntity="Status"/>
</entity>

View File

@ -10,7 +10,11 @@ import CoreData
public final class Card: NSManagedObject {
// sourcery: autoGenerateProperty
@NSManaged public private(set) var url: String
@NSManaged public private(set) var urlRaw: String
public var url: URL? {
URL(string: urlRaw)
}
// sourcery: autoGenerateProperty
@NSManaged public private(set) var title: String
// sourcery: autoGenerateProperty
@ -26,19 +30,23 @@ public final class Card: NSManagedObject {
// sourcery: autoGenerateProperty
@NSManaged public private(set) var authorName: String?
// sourcery: autoGenerateProperty
@NSManaged public private(set) var authorURL: String?
@NSManaged public private(set) var authorURLRaw: String?
// sourcery: autoGenerateProperty
@NSManaged public private(set) var providerName: String?
// sourcery: autoGenerateProperty
@NSManaged public private(set) var providerURL: String?
@NSManaged public private(set) var providerURLRaw: String?
// sourcery: autoGenerateProperty
@NSManaged public private(set) var width: Int64
// sourcery: autoGenerateProperty
@NSManaged public private(set) var height: Int64
// sourcery: autoGenerateProperty
@NSManaged public private(set) var image: String?
public var imageURL: URL? {
image.flatMap(URL.init)
}
// sourcery: autoGenerateProperty
@NSManaged public private(set) var embedURL: String?
@NSManaged public private(set) var embedURLRaw: String?
// sourcery: autoGenerateProperty
@NSManaged public private(set) var blurhash: String?
@ -75,64 +83,64 @@ extension Card: AutoGenerateProperty {
// Generated using Sourcery
// DO NOT EDIT
public struct Property {
public let url: String
public let urlRaw: String
public let title: String
public let desc: String
public let type: MastodonCardType
public let authorName: String?
public let authorURL: String?
public let authorURLRaw: String?
public let providerName: String?
public let providerURL: String?
public let providerURLRaw: String?
public let width: Int64
public let height: Int64
public let image: String?
public let embedURL: String?
public let embedURLRaw: String?
public let blurhash: String?
public init(
url: String,
urlRaw: String,
title: String,
desc: String,
type: MastodonCardType,
authorName: String?,
authorURL: String?,
authorURLRaw: String?,
providerName: String?,
providerURL: String?,
providerURLRaw: String?,
width: Int64,
height: Int64,
image: String?,
embedURL: String?,
embedURLRaw: String?,
blurhash: String?
) {
self.url = url
self.urlRaw = urlRaw
self.title = title
self.desc = desc
self.type = type
self.authorName = authorName
self.authorURL = authorURL
self.authorURLRaw = authorURLRaw
self.providerName = providerName
self.providerURL = providerURL
self.providerURLRaw = providerURLRaw
self.width = width
self.height = height
self.image = image
self.embedURL = embedURL
self.embedURLRaw = embedURLRaw
self.blurhash = blurhash
}
}
public func configure(property: Property) {
self.url = property.url
self.urlRaw = property.urlRaw
self.title = property.title
self.desc = property.desc
self.type = property.type
self.authorName = property.authorName
self.authorURL = property.authorURL
self.authorURLRaw = property.authorURLRaw
self.providerName = property.providerName
self.providerURL = property.providerURL
self.providerURLRaw = property.providerURLRaw
self.width = property.width
self.height = property.height
self.image = property.image
self.embedURL = property.embedURL
self.embedURLRaw = property.embedURLRaw
self.blurhash = property.blurhash
}

View File

@ -65,18 +65,18 @@ extension Persistence.Card {
}
let property = Card.Property(
url: context.entity.url,
urlRaw: context.entity.url,
title: context.entity.title,
desc: context.entity.description,
type: type,
authorName: context.entity.authorName,
authorURL: context.entity.authorURL,
authorURLRaw: context.entity.authorURL,
providerName: context.entity.providerName,
providerURL: context.entity.providerURL,
providerURLRaw: context.entity.providerURL,
width: Int64(context.entity.width ?? 0),
height: Int64(context.entity.height ?? 0),
image: context.entity.image,
embedURL: context.entity.embedURL,
embedURLRaw: context.entity.embedURL,
blurhash: context.entity.blurhash
)

View File

@ -9,18 +9,26 @@ import AlamofireImage
import LinkPresentation
import MastodonAsset
import MastodonCore
import CoreDataStack
import UIKit
public final class LinkPreviewButton: UIControl {
private var linkPresentationTask: Task<Void, Error>?
private var url: URL?
private let containerStackView = UIStackView()
private let labelStackView = UIStackView()
private let imageView = UIImageView()
private let titleLabel = UILabel()
private let subtitleLabel = UILabel()
private let linkLabel = UILabel()
private lazy var compactImageConstraints = [
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor),
imageView.heightAnchor.constraint(equalTo: heightAnchor),
containerStackView.heightAnchor.constraint(equalToConstant: 85),
]
private lazy var largeImageConstraints = [
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 21 / 40),
]
public override init(frame: CGRect) {
super.init(frame: frame)
@ -29,25 +37,24 @@ public final class LinkPreviewButton: UIControl {
layer.cornerCurve = .continuous
layer.cornerRadius = 10
layer.borderColor = ThemeService.shared.currentTheme.value.separator.cgColor
backgroundColor = ThemeService.shared.currentTheme.value.systemElevatedBackgroundColor
titleLabel.numberOfLines = 2
titleLabel.setContentCompressionResistancePriority(.defaultLow - 1, for: .horizontal)
titleLabel.text = "This is where I'd put a title... if I had one"
titleLabel.textColor = Asset.Colors.Label.primary.color
subtitleLabel.text = "Subtitle"
subtitleLabel.numberOfLines = 1
subtitleLabel.setContentCompressionResistancePriority(.defaultLow - 1, for: .horizontal)
subtitleLabel.textColor = Asset.Colors.Label.secondary.color
subtitleLabel.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 15, weight: .regular), maximumPointSize: 20)
linkLabel.text = "Subtitle"
linkLabel.numberOfLines = 1
linkLabel.setContentCompressionResistancePriority(.defaultLow - 1, for: .horizontal)
linkLabel.textColor = Asset.Colors.Label.secondary.color
imageView.backgroundColor = UIColor.black.withAlphaComponent(0.15)
imageView.tintColor = Asset.Colors.Label.secondary.color
imageView.backgroundColor = ThemeService.shared.currentTheme.value.systemElevatedBackgroundColor
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
labelStackView.addArrangedSubview(linkLabel)
labelStackView.addArrangedSubview(titleLabel)
labelStackView.addArrangedSubview(subtitleLabel)
labelStackView.layoutMargins = .init(top: 8, left: 10, bottom: 8, right: 10)
labelStackView.isLayoutMarginsRelativeArrangement = true
labelStackView.axis = .vertical
@ -55,20 +62,16 @@ public final class LinkPreviewButton: UIControl {
containerStackView.addArrangedSubview(imageView)
containerStackView.addArrangedSubview(labelStackView)
containerStackView.distribution = .fill
containerStackView.alignment = .center
addSubview(containerStackView)
containerStackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
containerStackView.heightAnchor.constraint(equalToConstant: 85),
containerStackView.topAnchor.constraint(equalTo: topAnchor),
containerStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
containerStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
containerStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor),
imageView.heightAnchor.constraint(equalTo: heightAnchor),
])
}
@ -76,36 +79,32 @@ public final class LinkPreviewButton: UIControl {
fatalError("init(coder:) has not been implemented")
}
public func configure(url: URL, trimmed: String) {
guard url != self.url else {
return
public func configure(card: Card) {
let isCompact = card.width == card.height
titleLabel.text = card.title
linkLabel.text = card.url?.host
imageView.contentMode = .center
imageView.sd_setImage(
with: card.imageURL,
placeholderImage: isCompact ? newsIcon : photoIcon
) { [weak imageView] image, _, _, _ in
if image != nil {
imageView?.contentMode = .scaleAspectFill
}
}
reset()
subtitleLabel.text = trimmed
self.url = url
NSLayoutConstraint.deactivate(compactImageConstraints + largeImageConstraints)
linkPresentationTask = Task {
do {
let metadata = try await LPMetadataProvider().startFetchingMetadata(for: url)
guard !Task.isCancelled else {
return
}
self.titleLabel.text = metadata.title
if let result = try await metadata.imageProvider?.loadImageData() {
let image = UIImage(data: result.data)
guard !Task.isCancelled else {
return
}
self.imageView.image = image
}
} catch {
self.subtitleLabel.text = "Error loading link preview"
}
if isCompact {
containerStackView.alignment = .center
containerStackView.axis = .horizontal
NSLayoutConstraint.activate(compactImageConstraints)
} else {
containerStackView.alignment = .fill
containerStackView.axis = .vertical
NSLayoutConstraint.activate(largeImageConstraints)
}
}
@ -117,11 +116,12 @@ public final class LinkPreviewButton: UIControl {
}
}
private func reset() {
linkPresentationTask?.cancel()
url = nil
imageView.image = nil
titleLabel.text = nil
subtitleLabel.text = nil
private var newsIcon: UIImage? {
UIImage(systemName: "newspaper.fill")
}
private var photoIcon: UIImage? {
let configuration = UIImage.SymbolConfiguration(pointSize: 40)
return UIImage(systemName: "photo", withConfiguration: configuration)
}
}

View File

@ -40,6 +40,14 @@ extension StatusView {
extension StatusView {
public func configure(status: Status) {
if let card = status.card {
print("---- \(card.title)")
print("---- \(card.url)")
print("---- \(card.image)")
print("---- \(card.width)")
print("---- \(card.height)")
}
viewModel.objects.insert(status)
if let reblog = status.reblog {
viewModel.objects.insert(reblog)
@ -53,6 +61,7 @@ extension StatusView {
configureContent(status: status)
configureMedia(status: status)
configurePoll(status: status)
configureCard(status: status)
configureToolbar(status: status)
configureFilter(status: status)
}
@ -349,6 +358,17 @@ extension StatusView {
.assign(to: \.isVoting, on: viewModel)
.store(in: &disposeBag)
}
private func configureCard(status: Status) {
let status = status.reblog ?? status
if viewModel.mediaViewConfigurations.isEmpty {
status.publisher(for: \.card)
.assign(to: \.card, on: viewModel)
.store(in: &disposeBag)
} else {
viewModel.card = nil
}
}
private func configureToolbar(status: Status) {
let status = status.reblog ?? status

View File

@ -69,7 +69,10 @@ extension StatusView {
@Published public var voteCount = 0
@Published public var expireAt: Date?
@Published public var expired: Bool = false
// Card
@Published public var card: Card?
// Visibility
@Published public var visibility: MastodonVisibility = .public
@ -185,6 +188,7 @@ extension StatusView.ViewModel {
bindContent(statusView: statusView)
bindMedia(statusView: statusView)
bindPoll(statusView: statusView)
bindCard(statusView: statusView)
bindToolbar(statusView: statusView)
bindMetric(statusView: statusView)
bindMenu(statusView: statusView)
@ -306,21 +310,6 @@ extension StatusView.ViewModel {
statusView.contentMetaText.textView.accessibilityTraits = [.staticText]
statusView.contentMetaText.textView.accessibilityElementsHidden = false
if let url = content.entities.first(where: {
switch $0.meta {
case .url:
return true
default:
return false
}
}) {
guard case .url(let text, let trimmed, let url, _) = url.meta, let url = URL(string: url) else {
fatalError()
}
statusView.linkPreviewButton.configure(url: url, trimmed: trimmed)
statusView.setLinkPreviewButtonDisplay()
}
} else {
statusView.contentMetaText.reset()
statusView.contentMetaText.textView.accessibilityLabel = ""
@ -496,6 +485,15 @@ extension StatusView.ViewModel {
.assign(to: \.isEnabled, on: statusView.pollVoteButton)
.store(in: &disposeBag)
}
private func bindCard(statusView: StatusView) {
$card.sink { card in
guard let card = card else { return }
statusView.linkPreviewButton.configure(card: card)
statusView.setLinkPreviewButtonDisplay()
}
.store(in: &disposeBag)
}
private func bindToolbar(statusView: StatusView) {
$replyCount