diff --git a/Mastodon/Diffable/RecommandAccount/RecommendAccountSection.swift b/Mastodon/Diffable/RecommandAccount/RecommendAccountSection.swift index 7d14aa4af..8a913609d 100644 --- a/Mastodon/Diffable/RecommandAccount/RecommendAccountSection.swift +++ b/Mastodon/Diffable/RecommandAccount/RecommendAccountSection.swift @@ -40,7 +40,6 @@ extension RecommendAccountSection { cell.configure(user: user) } - cell.viewModel.userIdentifier = configuration.authContext.mastodonAuthenticationBox cell.delegate = configuration.suggestionAccountTableViewCellDelegate } return cell diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift index 6d37a3f88..6d520360b 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift @@ -31,7 +31,6 @@ class SuggestionAccountViewController: UIViewController, NeedsDependency { }() //TODO: Add "follow all"-footer-cell - override func viewDidLoad() { super.viewDidLoad() @@ -106,7 +105,6 @@ extension SuggestionAccountViewController: SuggestionAccountTableViewCellDelegat switch item { case .account(let user): Task { @MainActor in - cell.startAnimating() do { try await DataSourceFacade.responseToUserFollowAction( dependency: self, @@ -115,8 +113,7 @@ extension SuggestionAccountViewController: SuggestionAccountTableViewCellDelegat } catch { // do noting } - cell.stopAnimating() - } // end Task + } } } } diff --git a/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell+ViewModel.swift b/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell+ViewModel.swift index 828449c6d..05ed2c13d 100644 --- a/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell+ViewModel.swift +++ b/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell+ViewModel.swift @@ -17,119 +17,21 @@ import Meta extension SuggestionAccountTableViewCell { func configure(user: MastodonUser) { - // author avatar - Publishers.CombineLatest( - user.publisher(for: \.avatar), - UserDefaults.shared.publisher(for: \.preferredStaticAvatar) - ) - .map { _ in user.avatarImageURL() } - .assign(to: \.avatarImageURL, on: viewModel) - .store(in: &disposeBag) - // author name - Publishers.CombineLatest( - user.publisher(for: \.displayName), - user.publisher(for: \.emojis) - ) - .map { _, emojis in - do { - let content = MastodonContent(content: user.displayNameWithFallback, emojis: emojis.asDictionary) - let metaContent = try MastodonMetaContent.convert(document: content) - return metaContent - } catch { - assertionFailure(error.localizedDescription) - return PlaintextMetaContent(string: user.displayNameWithFallback) - } - } - .assign(to: \.authorName, on: viewModel) - .store(in: &disposeBag) - // author username - user.publisher(for: \.acct) - .map { $0 as String? } - .assign(to: \.authorUsername, on: viewModel) - .store(in: &disposeBag) - // isFollowing - Publishers.CombineLatest( - viewModel.$userIdentifier, - user.publisher(for: \.followingBy) - ) - .map { userIdentifier, followingBy in - guard let userIdentifier = userIdentifier else { return false } - return followingBy.contains(where: { - $0.id == userIdentifier.userID && $0.domain == userIdentifier.domain - }) - } - .assign(to: \.isFollowing, on: viewModel) - .store(in: &disposeBag) - // isPending - Publishers.CombineLatest( - viewModel.$userIdentifier, - user.publisher(for: \.followRequestedBy) - ) - .map { userIdentifier, followRequestedBy in - guard let userIdentifier = userIdentifier else { return false } - return followRequestedBy.contains(where: { - $0.id == userIdentifier.userID && $0.domain == userIdentifier.domain - }) - } - .assign(to: \.isPending, on: viewModel) - .store(in: &disposeBag) - } + //TODO: Set Delegate + userView.configure(user: user, delegate: nil) + //TODO: Fix Button State + userView.setButtonState(.follow) - class ViewModel { - var disposeBag = Set() - - @Published public var userIdentifier: UserIdentifier? // me - - @Published var avatarImageURL: URL? - @Published public var authorName: MetaContent? - @Published public var authorUsername: String? - - @Published var isFollowing = false - @Published var isPending = false - - func prepareForReuse() { - isFollowing = false - isPending = false - } - func bind(cell: SuggestionAccountTableViewCell) { - // avatar - $avatarImageURL.removeDuplicates() - .sink { url in - let configuration = AvatarImageView.Configuration(url: url) - cell.avatarButton.avatarImageView.configure(configuration: configuration) - cell.avatarButton.avatarImageView.configure(cornerConfiguration: .init(corner: .fixed(radius: 12))) - } - .store(in: &disposeBag) - // name - $authorName - .sink { metaContent in - let metaContent = metaContent ?? PlaintextMetaContent(string: " ") - cell.titleLabel.configure(content: metaContent) - } - .store(in: &disposeBag) - // username - $authorUsername - .map { text -> String in - guard let text = text else { return "" } - return "@\(text)" - } - .sink { username in - cell.subTitleLabel.text = username - } - .store(in: &disposeBag) - // button - Publishers.CombineLatest( - $isFollowing, - $isPending - ) - .sink { isFollowing, isPending in - let isFollowState = isFollowing || isPending - let imageName = isFollowState ? "minus.circle.fill" : "plus.circle" - let image = UIImage(systemName: imageName, withConfiguration: UIImage.SymbolConfiguration(pointSize: 22, weight: .regular)) - cell.button.setImage(image, for: .normal) - cell.button.tintColor = isFollowState ? Asset.Colors.danger.color : Asset.Colors.Label.secondary.color + let metaContent: MetaContent = { + do { + let mastodonContent = MastodonContent(content: user.note ?? "", emojis: [:]) + return try MastodonMetaContent.convert(document: mastodonContent) + } catch { + assertionFailure() + return PlaintextMetaContent(string: user.note ?? "") } - .store(in: &disposeBag) - } + } () + + bioMetaLabel.configure(content: metaContent) } } diff --git a/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift b/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift index b65cdd193..c721d3790 100644 --- a/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift +++ b/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift @@ -24,144 +24,63 @@ protocol SuggestionAccountTableViewCellDelegate: AnyObject { final class SuggestionAccountTableViewCell: UITableViewCell { var disposeBag = Set() - weak var delegate: SuggestionAccountTableViewCellDelegate? - public private(set) lazy var viewModel: ViewModel = { - let viewModel = ViewModel() - viewModel.bind(cell: self) - return viewModel - }() + let userView: UserView + let bioMetaLabel: MetaLabel + private let contentStackView: UIStackView - //TODO: Replace this with user view - let avatarButton = AvatarButton() + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + + userView = UserView() + bioMetaLabel = MetaLabel() + bioMetaLabel.numberOfLines = 0 + bioMetaLabel.textAttributes = [ + .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular)), + .foregroundColor: UIColor.secondaryLabel + ] + bioMetaLabel.linkAttributes = [ + .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold)), + .foregroundColor: Asset.Colors.brand.color + ] + bioMetaLabel.isUserInteractionEnabled = false + + contentStackView = UIStackView(arrangedSubviews: [userView, bioMetaLabel]) + contentStackView.translatesAutoresizingMaskIntoConstraints = false + contentStackView.alignment = .leading + contentStackView.axis = .vertical + + super.init(style: style, reuseIdentifier: reuseIdentifier) + + contentView.addSubview(contentStackView) + + backgroundColor = .systemBackground + + setupConstraints() + } - let titleLabel = MetaLabel(style: .statusName) - - let subTitleLabel: UILabel = { - let label = UILabel() - label.textColor = Asset.Colors.Label.secondary.color - label.font = .preferredFont(forTextStyle: .body) - return label - }() - - let buttonContainer: UIView = { - let view = UIView() - return view - }() - - let button: HighlightDimmableButton = { - let button = HighlightDimmableButton(type: .custom) - let image = UIImage(systemName: "plus.circle", withConfiguration: UIImage.SymbolConfiguration(pointSize: 22, weight: .regular)) - button.setImage(image, for: .normal) - return button - }() - - let activityIndicatorView: UIActivityIndicatorView = { - let activityIndicatorView = UIActivityIndicatorView(style: .medium) - activityIndicatorView.hidesWhenStopped = true - return activityIndicatorView - }() + required init?(coder: NSCoder) { fatalError("We don't support ancient technology like Storyboards") } + + private func setupConstraints() { + let constraints = [ + contentStackView.topAnchor.constraint(equalTo: contentView.topAnchor), + contentStackView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), + contentView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: contentStackView.trailingAnchor), + contentView.bottomAnchor.constraint(equalTo: contentStackView.bottomAnchor, constant: 16), + ] + + NSLayoutConstraint.activate(constraints) + } override func prepareForReuse() { super.prepareForReuse() - + disposeBag.removeAll() - avatarButton.avatarImageView.prepareForReuse() - viewModel.prepareForReuse() - } - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - configure() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - configure() } -} + //MARK: - Action -extension SuggestionAccountTableViewCell { - - private func configure() { - - backgroundColor = .systemBackground - let containerStackView = UIStackView() - containerStackView.axis = .horizontal - containerStackView.distribution = .fill - containerStackView.spacing = 12 - containerStackView.layoutMargins = UIEdgeInsets(top: 12, left: 21, bottom: 12, right: 12) - containerStackView.isLayoutMarginsRelativeArrangement = true - containerStackView.translatesAutoresizingMaskIntoConstraints = false - contentView.addSubview(containerStackView) - containerStackView.pinToParent() - - avatarButton.translatesAutoresizingMaskIntoConstraints = false - containerStackView.addArrangedSubview(avatarButton) - NSLayoutConstraint.activate([ - avatarButton.widthAnchor.constraint(equalToConstant: 42).priority(.required - 1), - avatarButton.heightAnchor.constraint(equalToConstant: 42).priority(.required - 1), - ]) - - let textStackView = UIStackView() - textStackView.axis = .vertical - textStackView.distribution = .fill - textStackView.alignment = .leading - textStackView.translatesAutoresizingMaskIntoConstraints = false - titleLabel.translatesAutoresizingMaskIntoConstraints = false - textStackView.addArrangedSubview(titleLabel) - subTitleLabel.translatesAutoresizingMaskIntoConstraints = false - textStackView.addArrangedSubview(subTitleLabel) - subTitleLabel.setContentHuggingPriority(.defaultLow - 1, for: .vertical) - - containerStackView.addArrangedSubview(textStackView) - textStackView.setContentHuggingPriority(.defaultLow - 1, for: .horizontal) - - buttonContainer.translatesAutoresizingMaskIntoConstraints = false - containerStackView.addArrangedSubview(buttonContainer) - NSLayoutConstraint.activate([ - buttonContainer.widthAnchor.constraint(equalToConstant: 24).priority(.required - 1), - buttonContainer.heightAnchor.constraint(equalToConstant: 42).priority(.required - 1), - ]) - buttonContainer.setContentHuggingPriority(.required - 1, for: .horizontal) - - activityIndicatorView.translatesAutoresizingMaskIntoConstraints = false - button.translatesAutoresizingMaskIntoConstraints = false - activityIndicatorView.translatesAutoresizingMaskIntoConstraints = false - buttonContainer.addSubview(button) - buttonContainer.addSubview(activityIndicatorView) - NSLayoutConstraint.activate([ - buttonContainer.centerXAnchor.constraint(equalTo: activityIndicatorView.centerXAnchor), - buttonContainer.centerYAnchor.constraint(equalTo: activityIndicatorView.centerYAnchor), - buttonContainer.centerXAnchor.constraint(equalTo: button.centerXAnchor), - buttonContainer.centerYAnchor.constraint(equalTo: button.centerYAnchor), - ]) - - button.addTarget(self, action: #selector(SuggestionAccountTableViewCell.buttonDidPressed(_:)), for: .touchUpInside) - } - -} - -extension SuggestionAccountTableViewCell { @objc private func buttonDidPressed(_ sender: UIButton) { delegate?.suggestionAccountTableViewCell(self, friendshipDidPressed: sender) } } - -extension SuggestionAccountTableViewCell { - - func startAnimating() { - activityIndicatorView.isHidden = false - activityIndicatorView.startAnimating() - button.isHidden = true - } - - func stopAnimating() { - activityIndicatorView.stopAnimating() - activityIndicatorView.isHidden = true - button.isHidden = false - } - -}