Working pretty well
This commit is contained in:
parent
595b46e96e
commit
f8d1afc7e4
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue