// Copyright © 2023 Mastodon gGmbH. All rights reserved. import UIKit import CoreDataStack import MetaTextKit import MastodonAsset import MastodonLocalization import MastodonMeta import MastodonCore import MastodonSDK public class CondensedUserView: UIView { private static var metricFormatter = MastodonMetricFormatter() private let avatarImageWrapperView: UIView let avatarImageView: AvatarImageView private let metaInformationStackView: UIStackView private let upperLineStackView: UIStackView let displayNameLabel: MetaLabel let acctLabel: UILabel private let lowerLineStackView: UIStackView let followersLabel: UILabel let verifiedLinkImageView: UIImageView let verifiedLinkLabel: MetaLabel private let contentStackView: UIStackView public override init(frame: CGRect) { avatarImageView = AvatarImageView() avatarImageView.cornerConfiguration = AvatarImageView.CornerConfiguration(corner: .fixed(radius: 8)) avatarImageView.translatesAutoresizingMaskIntoConstraints = false avatarImageWrapperView = UIView() avatarImageWrapperView.translatesAutoresizingMaskIntoConstraints = false avatarImageWrapperView.addSubview(avatarImageView) displayNameLabel = MetaLabel(style: .statusName) displayNameLabel.setContentCompressionResistancePriority(.required, for: .horizontal) displayNameLabel.setContentHuggingPriority(.required, for: .horizontal) acctLabel = UILabel() acctLabel.textColor = .secondaryLabel acctLabel.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular)) acctLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) upperLineStackView = UIStackView(arrangedSubviews: [displayNameLabel, acctLabel]) upperLineStackView.distribution = .fill upperLineStackView.alignment = .center followersLabel = UILabel() followersLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) followersLabel.textColor = .secondaryLabel followersLabel.setContentHuggingPriority(.required, for: .horizontal) verifiedLinkImageView = UIImageView() verifiedLinkImageView.setContentCompressionResistancePriority(.defaultHigh - 1, for: .vertical) verifiedLinkImageView.setContentHuggingPriority(.required, for: .horizontal) verifiedLinkImageView.contentMode = .scaleAspectFit verifiedLinkLabel = MetaLabel(style: .profileFieldValue) verifiedLinkLabel.setContentCompressionResistancePriority(.defaultHigh - 2, for: .horizontal) verifiedLinkLabel.translatesAutoresizingMaskIntoConstraints = false verifiedLinkLabel.textAttributes = [ .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular)), .foregroundColor: UIColor.secondaryLabel ] verifiedLinkLabel.linkAttributes = [ .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular)), .foregroundColor: Asset.Colors.Brand.blurple.color ] verifiedLinkLabel.isUserInteractionEnabled = false lowerLineStackView = UIStackView(arrangedSubviews: [followersLabel, verifiedLinkImageView, verifiedLinkLabel]) lowerLineStackView.distribution = .fill lowerLineStackView.alignment = .center lowerLineStackView.spacing = 4 lowerLineStackView.setCustomSpacing(2, after: verifiedLinkImageView) metaInformationStackView = UIStackView(arrangedSubviews: [upperLineStackView, lowerLineStackView]) metaInformationStackView.axis = .vertical metaInformationStackView.alignment = .leading contentStackView = UIStackView(arrangedSubviews: [avatarImageWrapperView, metaInformationStackView]) contentStackView.translatesAutoresizingMaskIntoConstraints = false contentStackView.axis = .horizontal contentStackView.alignment = .center contentStackView.spacing = 16 super.init(frame: .zero) addSubview(contentStackView) setupConstraints() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupConstraints() { let constraints = [ contentStackView.topAnchor.constraint(equalTo: topAnchor, constant: 8), contentStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16), trailingAnchor.constraint(greaterThanOrEqualTo: contentStackView.trailingAnchor, constant: 16), bottomAnchor.constraint(equalTo: contentStackView.bottomAnchor, constant: 8), upperLineStackView.trailingAnchor.constraint(greaterThanOrEqualTo: metaInformationStackView.trailingAnchor), lowerLineStackView.trailingAnchor.constraint(greaterThanOrEqualTo: metaInformationStackView.trailingAnchor), metaInformationStackView.trailingAnchor.constraint(greaterThanOrEqualTo: contentStackView.trailingAnchor), avatarImageView.widthAnchor.constraint(equalToConstant: 30), avatarImageView.heightAnchor.constraint(equalTo: avatarImageView.widthAnchor), avatarImageView.topAnchor.constraint(greaterThanOrEqualTo: avatarImageWrapperView.topAnchor), avatarImageView.leadingAnchor.constraint(equalTo: avatarImageWrapperView.leadingAnchor), avatarImageWrapperView.trailingAnchor.constraint(equalTo: avatarImageView.trailingAnchor), avatarImageWrapperView.bottomAnchor.constraint(greaterThanOrEqualTo: avatarImageView.bottomAnchor), avatarImageView.centerYAnchor.constraint(equalTo: avatarImageWrapperView.centerYAnchor), ] NSLayoutConstraint.activate(constraints) } public func prepareForReuse() { avatarImageView.prepareForReuse() } public func configure(with user: MastodonUser) { let displayNameMetaContent: MetaContent do { let content = MastodonContent(content: user.displayNameWithFallback, emojis: user.emojis.asDictionary) displayNameMetaContent = try MastodonMetaContent.convert(document: content) } catch { displayNameMetaContent = PlaintextMetaContent(string: user.displayNameWithFallback) } displayNameLabel.configure(content: displayNameMetaContent) acctLabel.text = user.acct followersLabel.attributedText = NSAttributedString( format: NSAttributedString(string: L10n.Common.UserList.followersCount("%@"), attributes: [.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular))]), args: NSAttributedString(string: Self.metricFormatter.string(from: Int(user.followersCount)) ?? user.followersCount.formatted(), attributes: [.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .bold))]) ) avatarImageView.setImage(url: user.avatarImageURL()) if let verifiedLink = user.verifiedLink?.value { verifiedLinkImageView.image = UIImage(systemName: "checkmark") verifiedLinkImageView.tintColor = Asset.Colors.Brand.blurple.color let verifiedLinkMetaContent: MetaContent do { let mastodonContent = MastodonContent(content: verifiedLink, emojis: [:]) verifiedLinkMetaContent = try MastodonMetaContent.convert(document: mastodonContent) } catch { verifiedLinkMetaContent = PlaintextMetaContent(string: verifiedLink) } verifiedLinkLabel.configure(content: verifiedLinkMetaContent) } else { verifiedLinkImageView.image = UIImage(systemName: "questionmark.circle") verifiedLinkImageView.tintColor = .secondaryLabel verifiedLinkLabel.configure(content: PlaintextMetaContent(string: L10n.Common.UserList.noVerifiedLink)) } } public func configure(with account: Mastodon.Entity.Account, showFollowers: Bool = true) { let displayNameMetaContent: MetaContent do { let content = MastodonContent(content: account.displayNameWithFallback, emojis: account.emojis?.asDictionary ?? [:]) displayNameMetaContent = try MastodonMetaContent.convert(document: content) } catch { displayNameMetaContent = PlaintextMetaContent(string: account.displayNameWithFallback) } displayNameLabel.configure(content: displayNameMetaContent) acctLabel.text = account.acct if showFollowers { followersLabel.attributedText = NSAttributedString( format: NSAttributedString(string: L10n.Common.UserList.followersCount("%@"), attributes: [.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular))]), args: NSAttributedString(string: Self.metricFormatter.string(from: account.followersCount) ?? account.followersCount.formatted(), attributes: [.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .bold))]) ) followersLabel.isHidden = false } else { followersLabel.isHidden = false } avatarImageView.setImage(url: account.avatarImageURL()) if let verifiedLink = account.verifiedLink?.value { verifiedLinkImageView.image = UIImage(systemName: "checkmark") verifiedLinkImageView.tintColor = Asset.Colors.Brand.blurple.color let verifiedLinkMetaContent: MetaContent do { let mastodonContent = MastodonContent(content: verifiedLink, emojis: [:]) verifiedLinkMetaContent = try MastodonMetaContent.convert(document: mastodonContent) } catch { verifiedLinkMetaContent = PlaintextMetaContent(string: verifiedLink) } verifiedLinkLabel.configure(content: verifiedLinkMetaContent) } else { verifiedLinkImageView.image = UIImage(systemName: "questionmark.circle") verifiedLinkImageView.tintColor = .secondaryLabel verifiedLinkLabel.configure(content: PlaintextMetaContent(string: L10n.Common.UserList.noVerifiedLink)) } } }