diff --git a/MastodonSDK/Sources/MastodonUI/Extension/CoreDataStack/MastodonEmoji.swift b/MastodonSDK/Sources/MastodonUI/Extension/CoreDataStack/MastodonEmoji.swift index 4f097759..8e2558bb 100644 --- a/MastodonSDK/Sources/MastodonUI/Extension/CoreDataStack/MastodonEmoji.swift +++ b/MastodonSDK/Sources/MastodonUI/Extension/CoreDataStack/MastodonEmoji.swift @@ -8,6 +8,7 @@ import Foundation import CoreDataStack import MastodonMeta +import MastodonSDK extension Collection where Element == MastodonEmoji { public var asDictionary: MastodonContent.Emojis { @@ -18,3 +19,13 @@ extension Collection where Element == MastodonEmoji { return dictionary } } + +extension Collection where Element == Mastodon.Entity.Emoji { + public var asDictionary: MastodonContent.Emojis { + var dictionary: MastodonContent.Emojis = [:] + for emoji in self { + dictionary[emoji.shortcode] = emoji.url + } + return dictionary + } +} diff --git a/MastodonSDK/Sources/MastodonUI/Extension/MetaLabel.swift b/MastodonSDK/Sources/MastodonUI/Extension/MetaLabel.swift index 24a4027f..41fbfe40 100644 --- a/MastodonSDK/Sources/MastodonUI/Extension/MetaLabel.swift +++ b/MastodonSDK/Sources/MastodonUI/Extension/MetaLabel.swift @@ -22,6 +22,7 @@ extension MetaLabel { case profileFieldValue case profileCardName case profileCardUsername + case profileCardFamiliarFollowerFooter case recommendAccountName case titleView case settingTableFooter @@ -90,6 +91,14 @@ extension MetaLabel { font = .systemFont(ofSize: 15, weight: .regular) textColor = Asset.Colors.Label.secondary.color + case .profileCardFamiliarFollowerFooter: + font = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: .systemFont(ofSize: 13, weight: .regular), maximumPointSize: 26) + textColor = Asset.Colors.Label.secondary.color + numberOfLines = 2 + textContainer.maximumNumberOfLines = 2 + paragraphStyle.lineSpacing = 0 + paragraphStyle.paragraphSpacing = 0 + case .titleView: font = .systemFont(ofSize: 17, weight: .semibold) textColor = Asset.Colors.Label.primary.color @@ -106,18 +115,23 @@ extension MetaLabel { numberOfLines = 0 textContainer.maximumNumberOfLines = 0 paragraphStyle.alignment = .center + case .autoCompletion: font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold), maximumPointSize: 22) textColor = Asset.Colors.brandBlue.color + case .accountListName: font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .regular), maximumPointSize: 22) textColor = Asset.Colors.Label.primary.color + case .accountListUsername: font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .regular), maximumPointSize: 20) textColor = Asset.Colors.Label.secondary.color + case .sidebarHeadline(let isSelected): font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 22, weight: .regular), maximumPointSize: 20) textColor = isSelected ? .white : Asset.Colors.Label.primary.color + case .sidebarSubheadline(let isSelected): font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 13, weight: .regular), maximumPointSize: 18) textColor = isSelected ? .white : Asset.Colors.Label.secondary.color diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView+Configuration.swift b/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView+Configuration.swift index 63c962b7..78207023 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView+Configuration.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView+Configuration.swift @@ -16,5 +16,12 @@ extension FamiliarFollowersDashboardView { viewModel.avatarURLs = accounts.map { $0.avatarImageURL() } viewModel.names = accounts.map { $0.displayNameWithFallback } + viewModel.emojis = { + var array: [Mastodon.Entity.Emoji] = [] + for account in accounts { + array.append(contentsOf: account.emojis ?? []) + } + return array.asDictionary + }() } } diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView+ViewModel.swift index 4ab9200c..cee71a45 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView+ViewModel.swift @@ -8,6 +8,8 @@ import os.log import UIKit import Combine +import CoreDataStack +import MastodonMeta extension FamiliarFollowersDashboardView { public final class ViewModel: ObservableObject { @@ -17,47 +19,86 @@ extension FamiliarFollowersDashboardView { @Published var avatarURLs: [URL?] = [] @Published var names: [String] = [] + @Published var emojis: MastodonContent.Emojis = [:] @Published var backgroundColor: UIColor? } } extension FamiliarFollowersDashboardView.ViewModel { func bind(view: FamiliarFollowersDashboardView) { - Publishers.CombineLatest( + Publishers.CombineLatest3( $avatarURLs, - $backgroundColor + $backgroundColor, + UIContentSizeCategory.publisher ) - .sink { avatarURLs, backgroundColor in + .sink { avatarURLs, backgroundColor, contentSizeCategory in view.avatarContainerView.subviews.forEach { $0.removeFromSuperview() } + + let initialOffset = min(12 * 1.5, UIFontMetrics(forTextStyle: .headline).scaledValue(for: 12)) // max 1.5x + let offset = min(20 * 1.5, UIFontMetrics(forTextStyle: .headline).scaledValue(for: 20)) + let dimension = min(32 * 1.5, UIFontMetrics(forTextStyle: .headline).scaledValue(for: 32)) + let borderWidth = min(1.5, UIFontMetrics.default.scaledValue(for: 1)) + for (i, avatarURL) in avatarURLs.enumerated() { let avatarButton = AvatarButton() - let origin = CGPoint(x: 20 * i, y: 0) - let size = CGSize(width: 32, height: 32) + let origin = CGPoint(x: offset * CGFloat(i), y: 0) + let size = CGSize(width: dimension, height: dimension) avatarButton.size = size avatarButton.frame = CGRect(origin: origin, size: size) view.avatarContainerView.addSubview(avatarButton) - avatarButton.avatarImageView.configure(configuration: .init(url: avatarURL)) + avatarButton.avatarImageView.configure( + configuration: .init( + url: avatarURL, + placeholder: .placeholder(color: .systemGray3) + ) + ) avatarButton.avatarImageView.configure( cornerConfiguration: .init( corner: .fixed(radius: 7), border: .init( color: backgroundColor ?? .clear, - width: 1 + width: borderWidth ) ) ) } - view.avatarContainerViewWidthLayoutConstraint.constant = CGFloat(12 + 20 * avatarURLs.count) + let avatarContainerViewWidth = initialOffset + offset * CGFloat(avatarURLs.count) + view.avatarContainerViewWidthLayoutConstraint.constant = avatarContainerViewWidth + view.avatarContainerViewHeightLayoutConstraint.constant = dimension } .store(in: &disposeBag) - $names - .sink { names in - // TODO: i18n - let description = "Followed by" + names.joined(separator: ", ") - view.descriptionLabel.text = description - } - .store(in: &disposeBag) + Publishers.CombineLatest( + $names, + $emojis + ) + .sink { names, emojis in + let content: String = { + guard names.count > 0 else { return " " } + + let count = names.count + let firstTwoNames = names.prefix(2).joined(separator: ", ") + + switch names.count { + case 1..<3: + return "Followed by \(firstTwoNames)" + case 3: + return "Followed by \(firstTwoNames), and another mutual" + default: + let remains = count - 2 + return "Followed by \(firstTwoNames), and \(remains) mutuals" + } + }() + let document = MastodonContent(content: content, emojis: emojis) + do { + let metaContent = try MastodonMetaContent.convert(document: document) + view.descriptionMetaLabel.configure(content: metaContent) + } catch { + assertionFailure() + view.descriptionMetaLabel.configure(content: PlaintextMetaContent(string: content)) + } + } + .store(in: &disposeBag) } } diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView.swift index 8bd14220..50bde02b 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView.swift @@ -7,20 +7,15 @@ import UIKit import MastodonAsset +import MetaTextKit public final class FamiliarFollowersDashboardView: UIView { let avatarContainerView = UIView() var avatarContainerViewWidthLayoutConstraint: NSLayoutConstraint! + var avatarContainerViewHeightLayoutConstraint: NSLayoutConstraint! - let descriptionLabel: UILabel = { - let label = UILabel() - label.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 13, weight: .regular)) - label.text = "Followed by Pixelflowers, Lee’s Food, and 4 other mutuals" - label.textColor = Asset.Colors.Label.secondary.color - label.numberOfLines = 0 - return label - }() + let descriptionMetaLabel = MetaLabel(style: .profileCardFamiliarFollowerFooter) public private(set) lazy var viewModel: ViewModel = { let viewModel = ViewModel() @@ -28,10 +23,6 @@ public final class FamiliarFollowersDashboardView: UIView { return viewModel }() - public func prepareForReuse() { - - } - override init(frame: CGRect) { super.init(frame: frame) _init() @@ -49,6 +40,7 @@ extension FamiliarFollowersDashboardView { private func _init() { let stackView = UIStackView() stackView.axis = .horizontal + stackView.alignment = .center stackView.spacing = 8 stackView.translatesAutoresizingMaskIntoConstraints = false @@ -63,11 +55,14 @@ extension FamiliarFollowersDashboardView { avatarContainerView.translatesAutoresizingMaskIntoConstraints = false stackView.addArrangedSubview(avatarContainerView) avatarContainerViewWidthLayoutConstraint = avatarContainerView.widthAnchor.constraint(equalToConstant: 32).priority(.required - 1) + avatarContainerViewHeightLayoutConstraint = avatarContainerView.heightAnchor.constraint(equalToConstant: 32).priority(.required - 1) NSLayoutConstraint.activate([ avatarContainerViewWidthLayoutConstraint, - avatarContainerView.heightAnchor.constraint(equalToConstant: 32).priority(.required - 1) + avatarContainerViewHeightLayoutConstraint ]) - stackView.addArrangedSubview(descriptionLabel) + stackView.addArrangedSubview(descriptionMetaLabel) + descriptionMetaLabel.setContentHuggingPriority(.required - 1, for: .vertical) + descriptionMetaLabel.setContentCompressionResistancePriority(.required - 1, for: .vertical) } } diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView.swift index c0d74320..9b5b06fe 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView.swift @@ -166,7 +166,8 @@ extension ProfileCardView { authorContainerAdaptiveMarginContainerView.contentView = authorContainer authorContainerAdaptiveMarginContainerView.margin = ProfileCardView.contentMargin container.addArrangedSubview(authorContainerAdaptiveMarginContainerView) - + container.setCustomSpacing(6, after: bannerContainer) + // avatarPlaceholder let avatarPlaceholder = UIView() avatarPlaceholder.translatesAutoresizingMaskIntoConstraints = false @@ -220,6 +221,7 @@ extension ProfileCardView { infoContainerAdaptiveMarginContainerView.contentView = infoContainer infoContainerAdaptiveMarginContainerView.margin = ProfileCardView.contentMargin container.addArrangedSubview(infoContainerAdaptiveMarginContainerView) + container.setCustomSpacing(16, after: infoContainerAdaptiveMarginContainerView) infoContainer.addArrangedSubview(statusDashboardView) let infoContainerSpacer = UIView() @@ -249,7 +251,7 @@ extension ProfileCardView { bottomPadding.translatesAutoresizingMaskIntoConstraints = false container.addArrangedSubview(bottomPadding) NSLayoutConstraint.activate([ - bottomPadding.heightAnchor.constraint(equalToConstant: 16).priority(.required - 10), + bottomPadding.heightAnchor.constraint(equalToConstant: 8).priority(.required - 10), ]) relationshipActionButton.addTarget(self, action: #selector(ProfileCardView.relationshipActionButtonDidPressed(_:)), for: .touchUpInside) diff --git a/MastodonSDK/Sources/MastodonUI/View/ImageView/AvatarImageView.swift b/MastodonSDK/Sources/MastodonUI/View/ImageView/AvatarImageView.swift index c2fdbc92..0406b17f 100644 --- a/MastodonSDK/Sources/MastodonUI/View/ImageView/AvatarImageView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/ImageView/AvatarImageView.swift @@ -103,7 +103,11 @@ extension AvatarImageView { return ScaledToSizeFilter(size: self.frame.size) }() - af.setImage(withURL: url, filter: filter) + af.setImage( + withURL: url, + placeholderImage: configuration.placeholder, + filter: filter + ) } }