Revert "Use text view for profile field value view"

This reverts commit c88076eec0.

Closes #521
This commit is contained in:
Shadowfacts 2024-12-07 12:56:26 -05:00
parent 670047af6f
commit 9990d50e3e
1 changed files with 108 additions and 58 deletions

View File

@ -12,11 +12,7 @@ import SwiftUI
import SafariServices import SafariServices
class ProfileFieldValueView: UIView { class ProfileFieldValueView: UIView {
weak var navigationDelegate: TuskerNavigationDelegate? { weak var navigationDelegate: TuskerNavigationDelegate?
didSet {
textView.navigationDelegate = navigationDelegate
}
}
private static let converter = HTMLConverter( private static let converter = HTMLConverter(
font: .preferredFont(forTextStyle: .body), font: .preferredFont(forTextStyle: .body),
@ -28,8 +24,9 @@ class ProfileFieldValueView: UIView {
private let account: AccountMO private let account: AccountMO
private let field: Account.Field private let field: Account.Field
private var link: (String, URL)?
private let textView = ContentTextView() private let label = EmojiLabel()
private var iconView: UIView? private var iconView: UIView?
private var currentTargetedPreview: UITargetedPreview? private var currentTargetedPreview: UITargetedPreview?
@ -42,28 +39,34 @@ class ProfileFieldValueView: UIView {
let converted = NSMutableAttributedString(attributedString: ProfileFieldValueView.converter.convert(field.value)) let converted = NSMutableAttributedString(attributedString: ProfileFieldValueView.converter.convert(field.value))
#if os(visionOS) var range = NSRange(location: 0, length: 0)
textView.linkTextAttributes = [ if converted.length != 0,
.foregroundColor: UIColor.link let url = converted.attribute(.link, at: 0, longestEffectiveRange: &range, in: converted.fullRange) as? URL {
] link = (converted.attributedSubstring(from: range).string, url)
#else label.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(linkTapped)))
textView.linkTextAttributes = [ label.addInteraction(UIContextMenuInteraction(delegate: self))
.foregroundColor: UIColor.tintColor label.isUserInteractionEnabled = true
]
#endif converted.enumerateAttribute(.link, in: converted.fullRange) { value, range, stop in
textView.backgroundColor = nil guard value != nil else { return }
textView.isScrollEnabled = false #if os(visionOS)
textView.isSelectable = false converted.addAttribute(.foregroundColor, value: UIColor.link, range: range)
textView.isEditable = false #else
textView.font = .preferredFont(forTextStyle: .body) converted.addAttribute(.foregroundColor, value: UIColor.tintColor, range: range)
updateTextContainerInset() #endif
textView.adjustsFontForContentSizeCategory = true // the .link attribute in a UILabel always makes the color blue >.>
textView.attributedText = converted converted.removeAttribute(.link, range: range)
textView.setEmojis(account.emojis, identifier: account.id) }
textView.isUserInteractionEnabled = true }
textView.setContentCompressionResistancePriority(.required, for: .vertical)
textView.translatesAutoresizingMaskIntoConstraints = false label.numberOfLines = 0
addSubview(textView) label.font = .preferredFont(forTextStyle: .body)
label.adjustsFontForContentSizeCategory = true
label.attributedText = converted
label.setEmojis(account.emojis, identifier: account.id)
label.setContentCompressionResistancePriority(.required, for: .vertical)
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
let labelTrailingConstraint: NSLayoutConstraint let labelTrailingConstraint: NSLayoutConstraint
@ -80,20 +83,20 @@ class ProfileFieldValueView: UIView {
icon.isPointerInteractionEnabled = true icon.isPointerInteractionEnabled = true
icon.accessibilityLabel = "Verified link" icon.accessibilityLabel = "Verified link"
addSubview(icon) addSubview(icon)
labelTrailingConstraint = textView.trailingAnchor.constraint(equalTo: icon.leadingAnchor) labelTrailingConstraint = label.trailingAnchor.constraint(equalTo: icon.leadingAnchor)
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
icon.centerYAnchor.constraint(equalTo: textView.centerYAnchor), icon.centerYAnchor.constraint(equalTo: label.centerYAnchor),
icon.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor), icon.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor),
]) ])
} else { } else {
labelTrailingConstraint = textView.trailingAnchor.constraint(equalTo: trailingAnchor) labelTrailingConstraint = label.trailingAnchor.constraint(equalTo: trailingAnchor)
} }
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
textView.leadingAnchor.constraint(equalTo: leadingAnchor), label.leadingAnchor.constraint(equalTo: leadingAnchor),
labelTrailingConstraint, labelTrailingConstraint,
textView.topAnchor.constraint(equalTo: topAnchor), label.topAnchor.constraint(equalTo: topAnchor),
textView.bottomAnchor.constraint(equalTo: bottomAnchor), label.bottomAnchor.constraint(equalTo: bottomAnchor),
]) ])
} }
@ -102,36 +105,37 @@ class ProfileFieldValueView: UIView {
} }
override func sizeThatFits(_ size: CGSize) -> CGSize { override func sizeThatFits(_ size: CGSize) -> CGSize {
var size = textView.sizeThatFits(size) var size = label.sizeThatFits(size)
if let iconView { if let iconView {
size.width += iconView.sizeThatFits(UIView.layoutFittingCompressedSize).width size.width += iconView.sizeThatFits(UIView.layoutFittingCompressedSize).width
} }
return size return size
} }
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if traitCollection.preferredContentSizeCategory != previousTraitCollection?.preferredContentSizeCategory {
updateTextContainerInset()
}
}
private func updateTextContainerInset() {
// blergh
switch traitCollection.preferredContentSizeCategory {
case .extraSmall:
textView.textContainerInset = UIEdgeInsets(top: 4, left: 0, bottom: 0, right: 0)
case .small:
textView.textContainerInset = UIEdgeInsets(top: 3, left: 0, bottom: 0, right: 0)
case .medium, .large:
textView.textContainerInset = UIEdgeInsets(top: 2, left: 0, bottom: 0, right: 0)
default:
textView.textContainerInset = .zero
}
}
func setTextAlignment(_ alignment: NSTextAlignment) { func setTextAlignment(_ alignment: NSTextAlignment) {
textView.textAlignment = alignment label.textAlignment = alignment
}
func getHashtagOrURL() -> (Hashtag?, URL)? {
guard let (text, url) = link else {
return nil
}
if text.starts(with: "#") {
return (Hashtag(name: String(text.dropFirst()), url: url), url)
} else {
return (nil, url)
}
}
@objc private func linkTapped() {
guard let (hashtag, url) = getHashtagOrURL() else {
return
}
if let hashtag {
navigationDelegate?.selected(tag: hashtag)
} else {
navigationDelegate?.selected(url: url)
}
} }
@objc private func verifiedIconTapped() { @objc private func verifiedIconTapped() {
@ -141,7 +145,7 @@ class ProfileFieldValueView: UIView {
let view = ProfileFieldVerificationView( let view = ProfileFieldVerificationView(
acct: account.acct, acct: account.acct,
verifiedAt: field.verifiedAt!, verifiedAt: field.verifiedAt!,
linkText: textView.text ?? "", linkText: label.text ?? "",
navigationDelegate: navigationDelegate navigationDelegate: navigationDelegate
) )
let host = UIHostingController(rootView: view) let host = UIHostingController(rootView: view)
@ -165,3 +169,49 @@ class ProfileFieldValueView: UIView {
navigationDelegate.present(toPresent, animated: true) navigationDelegate.present(toPresent, animated: true)
} }
} }
extension ProfileFieldValueView: UIContextMenuInteractionDelegate, MenuActionProvider {
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
guard let (hashtag, url) = getHashtagOrURL(),
let navigationDelegate else {
return nil
}
if let hashtag {
return UIContextMenuConfiguration {
HashtagTimelineViewController(for: hashtag, mastodonController: navigationDelegate.apiController)
} actionProvider: { _ in
UIMenu(children: self.actionsForHashtag(hashtag, source: .view(self)))
}
} else {
return UIContextMenuConfiguration {
let vc = SFSafariViewController(url: url)
#if !os(visionOS)
vc.preferredControlTintColor = Preferences.shared.accentColor.color
#endif
return vc
} actionProvider: { _ in
UIMenu(children: self.actionsForURL(url, source: .view(self)))
}
}
}
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configuration: UIContextMenuConfiguration, highlightPreviewForItemWithIdentifier identifier: NSCopying) -> UITargetedPreview? {
var rect = label.textRect(forBounds: label.bounds, limitedToNumberOfLines: 0)
// the rect should be vertically centered, but textRect doesn't seem to take the label's vertical alignment into account
rect.origin.x = 0
rect.origin.y = (bounds.height - rect.height) / 2
let parameters = UIPreviewParameters(textLineRects: [rect as NSValue])
let preview = UITargetedPreview(view: label, parameters: parameters)
currentTargetedPreview = preview
return preview
}
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configuration: UIContextMenuConfiguration, dismissalPreviewForItemWithIdentifier identifier: NSCopying) -> UITargetedPreview? {
return currentTargetedPreview
}
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
MenuPreviewHelper.willPerformPreviewAction(animator: animator, presenter: navigationDelegate!)
}
}