From dd569fe0acb29001c262e4936bd29bda3cf8dcf0 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 18 Sep 2023 17:21:36 +0200 Subject: [PATCH] Put CondensedUserView into their own class (IOS-141) --- .../SearchResultsProfileTableViewCell.swift | 153 +------------ ...chResultsOverviewTableViewController.swift | 2 +- .../SearchHistoryUserCollectionViewCell.swift | 156 +------------ .../SearchHistory/SearchHistorySection.swift | 2 +- .../View/Content/CondensedUserView.swift | 205 ++++++++++++++++++ 5 files changed, 220 insertions(+), 298 deletions(-) create mode 100644 MastodonSDK/Sources/MastodonUI/View/Content/CondensedUserView.swift diff --git a/Mastodon/Scene/Search/SearchDetail/Search Results Overview/Cells/SearchResultsProfileTableViewCell.swift b/Mastodon/Scene/Search/SearchDetail/Search Results Overview/Cells/SearchResultsProfileTableViewCell.swift index 25af454b8..505e1fa80 100644 --- a/Mastodon/Scene/Search/SearchDetail/Search Results Overview/Cells/SearchResultsProfileTableViewCell.swift +++ b/Mastodon/Scene/Search/SearchDetail/Search Results Overview/Cells/SearchResultsProfileTableViewCell.swift @@ -1,170 +1,29 @@ // Copyright © 2023 Mastodon gGmbH. All rights reserved. import UIKit -import MastodonSDK import MastodonUI -import MetaTextKit -import MastodonLocalization -import MastodonMeta -import MastodonCore -import MastodonAsset class SearchResultsProfileTableViewCell: UITableViewCell { static let reuseIdentifier = "SearchResultsProfileTableViewCell" - 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 + let condensedUserView: CondensedUserView override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - 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 + condensedUserView = CondensedUserView(frame: .zero) + condensedUserView.translatesAutoresizingMaskIntoConstraints = false super.init(style: style, reuseIdentifier: reuseIdentifier) - contentView.addSubview(contentStackView) - setupConstraints() + contentView.addSubview(condensedUserView) + condensedUserView.pinToParent() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - private func setupConstraints() { - let constraints = [ - contentStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8), - contentStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), - contentView.trailingAnchor.constraint(greaterThanOrEqualTo: contentStackView.trailingAnchor, constant: 16), - contentView.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) - } - override func prepareForReuse() { super.prepareForReuse() - avatarImageView.prepareForReuse() - } - - func configure(with account: Mastodon.Entity.Account) { - 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 - 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))]) - ) - - 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)) - } + condensedUserView.prepareForReuse() } } diff --git a/Mastodon/Scene/Search/SearchDetail/Search Results Overview/SearchResultsOverviewTableViewController.swift b/Mastodon/Scene/Search/SearchDetail/Search Results Overview/SearchResultsOverviewTableViewController.swift index c25bea693..5687562bb 100644 --- a/Mastodon/Scene/Search/SearchDetail/Search Results Overview/SearchResultsOverviewTableViewController.swift +++ b/Mastodon/Scene/Search/SearchDetail/Search Results Overview/SearchResultsOverviewTableViewController.swift @@ -55,7 +55,7 @@ class SearchResultsOverviewTableViewController: UIViewController, NeedsDependenc case .profile(let profile): guard let cell = tableView.dequeueReusableCell(withIdentifier: SearchResultsProfileTableViewCell.reuseIdentifier, for: indexPath) as? SearchResultsProfileTableViewCell else { fatalError() } - cell.configure(with: profile) + cell.condensedUserView.configure(with: profile) return cell } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchHistory/Cell/SearchHistoryUserCollectionViewCell.swift b/Mastodon/Scene/Search/SearchDetail/SearchHistory/Cell/SearchHistoryUserCollectionViewCell.swift index 3e655417c..c17492b2e 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchHistory/Cell/SearchHistoryUserCollectionViewCell.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchHistory/Cell/SearchHistoryUserCollectionViewCell.swift @@ -1,171 +1,29 @@ // Copyright © 2023 Mastodon gGmbH. All rights reserved. import UIKit -import MastodonSDK import MastodonUI -import MetaTextKit -import MastodonLocalization -import MastodonMeta import MastodonCore -import MastodonAsset -import CoreDataStack class SearchHistoryUserCollectionViewCell: UICollectionViewCell { static let reuseIdentifier = "SearchHistoryUserCollectionViewCell" - 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 + let condensedUserView: CondensedUserView override init(frame: CGRect) { - avatarImageView = AvatarImageView() - avatarImageView.cornerConfiguration = AvatarImageView.CornerConfiguration(corner: .fixed(radius: 8)) - avatarImageView.translatesAutoresizingMaskIntoConstraints = false + condensedUserView = CondensedUserView(frame: .zero) + condensedUserView.translatesAutoresizingMaskIntoConstraints = false + super.init(frame: frame) - 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) - - contentView.addSubview(contentStackView) - setupConstraints() + contentView.addSubview(condensedUserView) + condensedUserView.pinToParent() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - private func setupConstraints() { - let constraints = [ - contentStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8), - contentStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), - contentView.trailingAnchor.constraint(greaterThanOrEqualTo: contentStackView.trailingAnchor, constant: 16), - contentView.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) - } - override func prepareForReuse() { super.prepareForReuse() - avatarImageView.prepareForReuse() - } - - 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)) - } + condensedUserView.prepareForReuse() } override func updateConfiguration(using state: UICellConfigurationState) { diff --git a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistorySection.swift b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistorySection.swift index aa43f2467..082e1224f 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistorySection.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistorySection.swift @@ -31,7 +31,7 @@ extension SearchHistorySection { let userCellRegister = UICollectionView.CellRegistration> { cell, indexPath, item in context.managedObjectContext.performAndWait { guard let user = item.object(in: context.managedObjectContext) else { return } - cell.configure(with: user) + cell.condensedUserView.configure(with: user) } } diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/CondensedUserView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/CondensedUserView.swift new file mode 100644 index 000000000..be766f5e6 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/View/Content/CondensedUserView.swift @@ -0,0 +1,205 @@ +// 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) { + 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 + 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))]) + ) + + 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)) + } + } + +}