Begin implementing verified link in UserView (IOS-140)
This commit is contained in:
parent
c638d86cb7
commit
645542c581
|
@ -46,5 +46,18 @@ extension UserView {
|
||||||
.map { $0 as String? }
|
.map { $0 as String? }
|
||||||
.assign(to: \.authorUsername, on: viewModel)
|
.assign(to: \.authorUsername, on: viewModel)
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
|
user.publisher(for: \.followersCount)
|
||||||
|
.map { Int($0) }
|
||||||
|
.assign(to: \.authorFollowers, on: viewModel)
|
||||||
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
|
user.publisher(for: \.fields)
|
||||||
|
.map { fields in
|
||||||
|
let firstVerified = fields.first(where: { $0.verifiedAt != nil })
|
||||||
|
return firstVerified?.value
|
||||||
|
}
|
||||||
|
.assign(to: \.authorVerifiedLink, on: viewModel)
|
||||||
|
.store(in: &disposeBag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public extension NSAttributedString {
|
||||||
|
convenience init(format: NSAttributedString, args: NSAttributedString...) {
|
||||||
|
let mutableNSAttributedString = NSMutableAttributedString(attributedString: format)
|
||||||
|
|
||||||
|
args.forEach { attributedString in
|
||||||
|
let range = NSString(string: mutableNSAttributedString.string).range(of: "%@")
|
||||||
|
mutableNSAttributedString.replaceCharacters(in: range, with: attributedString)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.init(attributedString: mutableNSAttributedString)
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,8 @@ import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import MetaTextKit
|
import MetaTextKit
|
||||||
import MastodonCore
|
import MastodonCore
|
||||||
|
import MastodonMeta
|
||||||
|
import MastodonAsset
|
||||||
|
|
||||||
extension UserView {
|
extension UserView {
|
||||||
public final class ViewModel: ObservableObject {
|
public final class ViewModel: ObservableObject {
|
||||||
|
@ -22,6 +24,8 @@ extension UserView {
|
||||||
@Published public var authorAvatarImageURL: URL?
|
@Published public var authorAvatarImageURL: URL?
|
||||||
@Published public var authorName: MetaContent?
|
@Published public var authorName: MetaContent?
|
||||||
@Published public var authorUsername: String?
|
@Published public var authorUsername: String?
|
||||||
|
@Published public var authorFollowers: Int?
|
||||||
|
@Published public var authorVerifiedLink: String?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,5 +78,43 @@ extension UserView.ViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
|
$authorFollowers
|
||||||
|
.sink { count in
|
||||||
|
guard let count = count else {
|
||||||
|
userView.authorFollowersLabel.text = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userView.authorFollowersLabel.attributedText = NSAttributedString(
|
||||||
|
format: NSAttributedString(string: "%@ followers", attributes: [.font: Font.systemFont(ofSize: 15, weight: .regular)]),
|
||||||
|
args: NSAttributedString(string: count.formatted(), attributes: [.font: Font.systemFont(ofSize: 15, weight: .bold)])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
|
$authorVerifiedLink
|
||||||
|
.sink { link in
|
||||||
|
userView.authorVerifiedImageView.image = link == nil ? UIImage(systemName: "questionmark.circle") : UIImage(systemName: "checkmark")
|
||||||
|
|
||||||
|
switch link {
|
||||||
|
case let .some(link):
|
||||||
|
userView.authorVerifiedImageView.tintColor = Asset.Colors.brand.color
|
||||||
|
userView.authorVerifiedLabel.textColor = Asset.Colors.brand.color
|
||||||
|
do {
|
||||||
|
let mastodonContent = MastodonContent(content: link, emojis: [:])
|
||||||
|
let content = try MastodonMetaContent.convert(document: mastodonContent)
|
||||||
|
userView.authorVerifiedLabel.configure(content: content)
|
||||||
|
} catch {
|
||||||
|
let content = PlaintextMetaContent(string: link)
|
||||||
|
userView.authorVerifiedLabel.configure(content: content)
|
||||||
|
}
|
||||||
|
case .none:
|
||||||
|
userView.authorVerifiedImageView.tintColor = .secondaryLabel
|
||||||
|
userView.authorVerifiedLabel.configure(content: PlaintextMetaContent(string: "No verified link"))
|
||||||
|
userView.authorVerifiedLabel.textColor = .secondaryLabel
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import MetaTextKit
|
import MetaTextKit
|
||||||
|
import MastodonAsset
|
||||||
|
import os
|
||||||
|
|
||||||
public final class UserView: UIView {
|
public final class UserView: UIView {
|
||||||
|
|
||||||
|
@ -38,6 +40,44 @@ public final class UserView: UIView {
|
||||||
// author username
|
// author username
|
||||||
public let authorUsernameLabel = MetaLabel(style: .statusUsername)
|
public let authorUsernameLabel = MetaLabel(style: .statusUsername)
|
||||||
|
|
||||||
|
public let authorFollowersLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
label.textColor = .secondaryLabel
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
public let authorVerifiedLabel: MetaLabel = {
|
||||||
|
let label = MetaLabel(style: .profileFieldValue)
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
label.textAttributes = [
|
||||||
|
.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular)),
|
||||||
|
.foregroundColor: UIColor.secondaryLabel
|
||||||
|
]
|
||||||
|
label.linkAttributes = [
|
||||||
|
.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold)),
|
||||||
|
.foregroundColor: Asset.Colors.brand.color
|
||||||
|
]
|
||||||
|
label.isUserInteractionEnabled = false
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
public let authorVerifiedImageView: UIImageView = {
|
||||||
|
let imageView = UIImageView()
|
||||||
|
imageView.setContentHuggingPriority(.required, for: .horizontal)
|
||||||
|
imageView.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
|
||||||
|
imageView.setContentHuggingPriority(.required, for: .vertical)
|
||||||
|
imageView.contentMode = .scaleAspectFit
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
public let verifiedStackView: UIStackView = {
|
||||||
|
let stackView = UIStackView()
|
||||||
|
stackView.axis = .horizontal
|
||||||
|
stackView.alignment = .center
|
||||||
|
return stackView
|
||||||
|
}()
|
||||||
|
|
||||||
public func prepareForReuse() {
|
public func prepareForReuse() {
|
||||||
disposeBag.removeAll()
|
disposeBag.removeAll()
|
||||||
|
|
||||||
|
@ -82,10 +122,37 @@ extension UserView {
|
||||||
labelStackView.axis = .vertical
|
labelStackView.axis = .vertical
|
||||||
containerStackView.addArrangedSubview(labelStackView)
|
containerStackView.addArrangedSubview(labelStackView)
|
||||||
|
|
||||||
labelStackView.addArrangedSubview(authorNameLabel)
|
let nameStackView = UIStackView()
|
||||||
labelStackView.addArrangedSubview(authorUsernameLabel)
|
nameStackView.axis = .horizontal
|
||||||
|
|
||||||
|
let nameSpacer = UIView()
|
||||||
|
nameSpacer.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||||
|
|
||||||
|
nameStackView.addArrangedSubview(authorNameLabel)
|
||||||
|
nameStackView.addArrangedSubview(authorUsernameLabel)
|
||||||
|
nameStackView.addArrangedSubview(nameSpacer)
|
||||||
|
|
||||||
authorNameLabel.setContentCompressionResistancePriority(.required - 1, for: .vertical)
|
authorNameLabel.setContentCompressionResistancePriority(.required - 1, for: .vertical)
|
||||||
|
authorNameLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
||||||
|
|
||||||
authorUsernameLabel.setContentCompressionResistancePriority(.required - 1, for: .vertical)
|
authorUsernameLabel.setContentCompressionResistancePriority(.required - 1, for: .vertical)
|
||||||
|
authorUsernameLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
||||||
|
|
||||||
|
labelStackView.addArrangedSubview(nameStackView)
|
||||||
|
labelStackView.addArrangedSubview(authorFollowersLabel)
|
||||||
|
|
||||||
|
let verifiedSpacerView = UIView()
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
authorVerifiedImageView.widthAnchor.constraint(equalToConstant: 15),
|
||||||
|
verifiedSpacerView.widthAnchor.constraint(equalToConstant: 2)
|
||||||
|
])
|
||||||
|
|
||||||
|
verifiedStackView.addArrangedSubview(authorVerifiedImageView)
|
||||||
|
verifiedStackView.addArrangedSubview(verifiedSpacerView)
|
||||||
|
verifiedStackView.addArrangedSubview(authorVerifiedLabel)
|
||||||
|
|
||||||
|
labelStackView.addArrangedSubview(verifiedStackView)
|
||||||
|
|
||||||
avatarButton.isUserInteractionEnabled = false
|
avatarButton.isUserInteractionEnabled = false
|
||||||
authorNameLabel.isUserInteractionEnabled = false
|
authorNameLabel.isUserInteractionEnabled = false
|
||||||
|
|
Loading…
Reference in New Issue