// // ProfileHeaderTableViewCell.swift // Tusker // // Created by Shadowfacts on 8/27/18. // Copyright © 2018 Shadowfacts. All rights reserved. // import UIKit import Pachyderm import Combine protocol ProfileHeaderTableViewCellDelegate: TuskerNavigationDelegate { func showMoreOptions(cell: ProfileHeaderTableViewCell) } class ProfileHeaderTableViewCell: UITableViewCell { weak var delegate: ProfileHeaderTableViewCellDelegate? var mastodonController: MastodonController! { delegate?.apiController } @IBOutlet weak var headerImageView: UIImageView! @IBOutlet weak var avatarContainerView: UIView! @IBOutlet weak var avatarImageView: UIImageView! @IBOutlet weak var displayNameLabel: EmojiLabel! @IBOutlet weak var usernameLabel: UILabel! @IBOutlet weak var followsYouLabel: UILabel! @IBOutlet weak var noteTextView: StatusContentTextView! @IBOutlet weak var fieldsStackView: UIStackView! @IBOutlet weak var fieldNamesStackView: UIStackView! @IBOutlet weak var fieldValuesStackView: UIStackView! @IBOutlet weak var moreButton: VisualEffectImageButton! var accountID: String! var avatarRequest: ImageCache.Request? var headerRequest: ImageCache.Request? private var accountUpdater: Cancellable? override func awakeFromNib() { avatarContainerView.layer.masksToBounds = true avatarImageView.layer.masksToBounds = true avatarImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(avatarPressed))) avatarImageView.isUserInteractionEnabled = true headerImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(headerPressed))) headerImageView.isUserInteractionEnabled = true moreButton.layer.cornerRadius = 16 moreButton.layer.masksToBounds = true if #available(iOS 13.4, *) { moreButton.addInteraction(UIPointerInteraction(delegate: self)) } if #available(iOS 14.0, *) { moreButton.showsMenuAsPrimaryAction = true moreButton.isContextMenuInteractionEnabled = true } NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil) } func updateUI(for accountID: String) { guard accountID != self.accountID else { return } self.accountID = accountID guard let account = mastodonController.persistentContainer.account(for: accountID) else { fatalError("Missing cached account \(accountID)") } updateUIForPreferences() usernameLabel.text = "@\(account.acct)" avatarRequest = ImageCache.avatars.get(account.avatar) { [weak self] (data) in guard let self = self, let data = data, self.accountID == accountID else { return } self.avatarRequest = nil DispatchQueue.main.async { self.avatarImageView.image = UIImage(data: data) } } headerRequest = ImageCache.headers.get(account.header) { [weak self] (data) in guard let self = self, let data = data, self.accountID == accountID else { return } self.headerRequest = nil DispatchQueue.main.async { self.headerImageView.image = UIImage(data: data) } } if #available(iOS 14.0, *) { moreButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: actionsForProfile(accountID: accountID, sourceView: moreButton)) } noteTextView.navigationDelegate = delegate noteTextView.setTextFromHtml(account.note) noteTextView.setEmojis(account.emojis) // don't show relationship label for the user's own account if accountID != mastodonController.account.id { let request = Client.getRelationships(accounts: [accountID]) mastodonController.run(request) { (response) in if case let .success(results, _) = response, let relationship = results.first { DispatchQueue.main.async { self.followsYouLabel.isHidden = !relationship.followedBy } } } } fieldsStackView.isHidden = account.fields.isEmpty fieldNamesStackView.arrangedSubviews.forEach { $0.removeFromSuperview() } fieldValuesStackView.arrangedSubviews.forEach { $0.removeFromSuperview() } for field in account.fields { let nameLabel = UILabel() nameLabel.text = field.name nameLabel.font = .boldSystemFont(ofSize: 17) nameLabel.textAlignment = .right nameLabel.numberOfLines = 0 nameLabel.lineBreakMode = .byWordWrapping nameLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) fieldNamesStackView.addArrangedSubview(nameLabel) let valueTextView = ContentTextView() valueTextView.isSelectable = false valueTextView.font = .systemFont(ofSize: 17) valueTextView.setTextFromHtml(field.value) valueTextView.setEmojis(account.emojis) valueTextView.textAlignment = .left valueTextView.awakeFromNib() valueTextView.navigationDelegate = delegate valueTextView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) fieldValuesStackView.addArrangedSubview(valueTextView) nameLabel.heightAnchor.constraint(equalTo: valueTextView.heightAnchor).isActive = true } if accountUpdater == nil { accountUpdater = mastodonController.persistentContainer.accountSubject .filter { [unowned self] in $0 == self.accountID } .receive(on: DispatchQueue.main) .sink { [unowned self] in self.updateUI(for: $0) } } } @objc func updateUIForPreferences() { guard let account = mastodonController.persistentContainer.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") } avatarContainerView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarContainerView) avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView) displayNameLabel.updateForAccountDisplayName(account: account) } override func prepareForReuse() { avatarRequest?.cancel() headerRequest?.cancel() } @IBAction func morePressed(_ sender: Any) { delegate?.showMoreOptions(cell: self) } @objc func avatarPressed() { guard let account = mastodonController.persistentContainer.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") } delegate?.showLoadingLargeImage(url: account.avatar, cache: .avatars, description: nil, animatingFrom: avatarImageView) } @objc func headerPressed() { guard let account = mastodonController.persistentContainer.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") } delegate?.showLoadingLargeImage(url: account.header, cache: .headers, description: nil, animatingFrom: headerImageView) } } @available(iOS 13.4, *) extension ProfileHeaderTableViewCell: UIPointerInteractionDelegate { func pointerInteraction(_ interaction: UIPointerInteraction, styleFor region: UIPointerRegion) -> UIPointerStyle? { let preview = UITargetedPreview(view: moreButton) return UIPointerStyle(effect: .lift(preview), shape: .none) } } extension ProfileHeaderTableViewCell: MenuPreviewProvider { var navigationDelegate: TuskerNavigationDelegate? { delegate } }