forked from shadowfacts/Tusker
Revert "Use text view for profile field value view"
This reverts commit c88076eec0
.
Closes #521
This commit is contained in:
parent
670047af6f
commit
9990d50e3e
|
@ -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))
|
||||||
|
|
||||||
|
var range = NSRange(location: 0, length: 0)
|
||||||
|
if converted.length != 0,
|
||||||
|
let url = converted.attribute(.link, at: 0, longestEffectiveRange: &range, in: converted.fullRange) as? URL {
|
||||||
|
link = (converted.attributedSubstring(from: range).string, url)
|
||||||
|
label.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(linkTapped)))
|
||||||
|
label.addInteraction(UIContextMenuInteraction(delegate: self))
|
||||||
|
label.isUserInteractionEnabled = true
|
||||||
|
|
||||||
|
converted.enumerateAttribute(.link, in: converted.fullRange) { value, range, stop in
|
||||||
|
guard value != nil else { return }
|
||||||
#if os(visionOS)
|
#if os(visionOS)
|
||||||
textView.linkTextAttributes = [
|
converted.addAttribute(.foregroundColor, value: UIColor.link, range: range)
|
||||||
.foregroundColor: UIColor.link
|
|
||||||
]
|
|
||||||
#else
|
#else
|
||||||
textView.linkTextAttributes = [
|
converted.addAttribute(.foregroundColor, value: UIColor.tintColor, range: range)
|
||||||
.foregroundColor: UIColor.tintColor
|
|
||||||
]
|
|
||||||
#endif
|
#endif
|
||||||
textView.backgroundColor = nil
|
// the .link attribute in a UILabel always makes the color blue >.>
|
||||||
textView.isScrollEnabled = false
|
converted.removeAttribute(.link, range: range)
|
||||||
textView.isSelectable = false
|
}
|
||||||
textView.isEditable = false
|
}
|
||||||
textView.font = .preferredFont(forTextStyle: .body)
|
|
||||||
updateTextContainerInset()
|
label.numberOfLines = 0
|
||||||
textView.adjustsFontForContentSizeCategory = true
|
label.font = .preferredFont(forTextStyle: .body)
|
||||||
textView.attributedText = converted
|
label.adjustsFontForContentSizeCategory = true
|
||||||
textView.setEmojis(account.emojis, identifier: account.id)
|
label.attributedText = converted
|
||||||
textView.isUserInteractionEnabled = true
|
label.setEmojis(account.emojis, identifier: account.id)
|
||||||
textView.setContentCompressionResistancePriority(.required, for: .vertical)
|
label.setContentCompressionResistancePriority(.required, for: .vertical)
|
||||||
textView.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
addSubview(textView)
|
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!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue