2020-09-23 03:00:56 +02:00
|
|
|
// Copyright © 2020 Metabolist. All rights reserved.
|
|
|
|
|
|
|
|
import Kingfisher
|
2021-01-20 03:47:21 +01:00
|
|
|
import Mastodon
|
2020-09-23 03:00:56 +02:00
|
|
|
import UIKit
|
2021-01-26 07:57:44 +01:00
|
|
|
import ViewModels
|
2020-09-23 03:00:56 +02:00
|
|
|
|
2020-11-09 07:22:20 +01:00
|
|
|
final class AccountView: UIView {
|
2020-09-23 03:00:56 +02:00
|
|
|
let avatarImageView = AnimatedImageView()
|
2020-09-26 02:52:59 +02:00
|
|
|
let displayNameLabel = UILabel()
|
|
|
|
let accountLabel = UILabel()
|
2020-09-23 03:00:56 +02:00
|
|
|
let noteTextView = TouchFallthroughTextView()
|
2021-01-26 07:57:44 +01:00
|
|
|
let acceptFollowRequestButton = UIButton()
|
|
|
|
let rejectFollowRequestButton = UIButton()
|
2020-09-23 03:00:56 +02:00
|
|
|
|
|
|
|
private var accountConfiguration: AccountContentConfiguration
|
|
|
|
|
|
|
|
init(configuration: AccountContentConfiguration) {
|
|
|
|
self.accountConfiguration = configuration
|
|
|
|
|
|
|
|
super.init(frame: .zero)
|
|
|
|
|
|
|
|
initialSetup()
|
2020-10-13 22:11:27 +02:00
|
|
|
applyAccountConfiguration()
|
2020-09-23 03:00:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@available(*, unavailable)
|
|
|
|
required init?(coder: NSCoder) {
|
|
|
|
fatalError("init(coder:) has not been implemented")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-20 03:47:21 +01:00
|
|
|
extension AccountView {
|
2021-01-26 07:57:44 +01:00
|
|
|
static func estimatedHeight(width: CGFloat,
|
|
|
|
account: Account,
|
|
|
|
configuration: CollectionItem.AccountConfiguration) -> CGFloat {
|
|
|
|
var height = CGFloat.defaultSpacing * 2
|
|
|
|
+ .compactSpacing
|
2021-01-20 03:47:21 +01:00
|
|
|
+ account.displayName.height(width: width, font: .preferredFont(forTextStyle: .headline))
|
|
|
|
+ account.acct.height(width: width, font: .preferredFont(forTextStyle: .subheadline))
|
2021-01-26 07:57:44 +01:00
|
|
|
|
|
|
|
if configuration == .withNote {
|
|
|
|
height += .compactSpacing + account.note.attributed.string.height(
|
|
|
|
width: width,
|
|
|
|
font: .preferredFont(forTextStyle: .callout))
|
|
|
|
}
|
|
|
|
|
|
|
|
return max(height, .avatarDimension + .defaultSpacing * 2)
|
2021-01-20 03:47:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-23 03:00:56 +02:00
|
|
|
extension AccountView: UIContentView {
|
|
|
|
var configuration: UIContentConfiguration {
|
|
|
|
get { accountConfiguration }
|
|
|
|
set {
|
|
|
|
guard let accountConfiguration = newValue as? AccountContentConfiguration else { return }
|
|
|
|
|
|
|
|
self.accountConfiguration = accountConfiguration
|
|
|
|
|
|
|
|
applyAccountConfiguration()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-26 02:57:35 +02:00
|
|
|
extension AccountView: UITextViewDelegate {
|
|
|
|
func textView(
|
|
|
|
_ textView: UITextView,
|
|
|
|
shouldInteractWith URL: URL,
|
|
|
|
in characterRange: NSRange,
|
|
|
|
interaction: UITextItemInteraction) -> Bool {
|
|
|
|
switch interaction {
|
|
|
|
case .invokeDefaultAction:
|
|
|
|
accountConfiguration.viewModel.urlSelected(URL)
|
|
|
|
return false
|
|
|
|
case .preview: return false
|
|
|
|
case .presentActions: return false
|
|
|
|
@unknown default: return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-23 03:00:56 +02:00
|
|
|
private extension AccountView {
|
2021-01-27 01:15:52 +01:00
|
|
|
// swiftlint:disable:next function_body_length
|
2020-09-23 03:00:56 +02:00
|
|
|
func initialSetup() {
|
2020-09-26 02:52:59 +02:00
|
|
|
let stackView = UIStackView()
|
2020-09-23 03:00:56 +02:00
|
|
|
|
2020-09-26 02:52:59 +02:00
|
|
|
addSubview(stackView)
|
2021-01-26 07:57:44 +01:00
|
|
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
stackView.spacing = .defaultSpacing
|
|
|
|
stackView.alignment = .top
|
|
|
|
|
|
|
|
stackView.addArrangedSubview(avatarImageView)
|
2020-10-12 07:37:34 +02:00
|
|
|
avatarImageView.layer.cornerRadius = .avatarDimension / 2
|
2020-09-26 02:52:59 +02:00
|
|
|
avatarImageView.clipsToBounds = true
|
2021-01-26 07:57:44 +01:00
|
|
|
|
|
|
|
let verticalStackView = UIStackView()
|
|
|
|
|
|
|
|
stackView.addArrangedSubview(verticalStackView)
|
|
|
|
verticalStackView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
verticalStackView.axis = .vertical
|
|
|
|
verticalStackView.spacing = .compactSpacing
|
|
|
|
verticalStackView.addArrangedSubview(displayNameLabel)
|
|
|
|
verticalStackView.addArrangedSubview(accountLabel)
|
|
|
|
verticalStackView.addArrangedSubview(noteTextView)
|
2020-09-26 02:52:59 +02:00
|
|
|
displayNameLabel.numberOfLines = 0
|
|
|
|
displayNameLabel.font = .preferredFont(forTextStyle: .headline)
|
|
|
|
displayNameLabel.adjustsFontForContentSizeCategory = true
|
|
|
|
accountLabel.numberOfLines = 0
|
|
|
|
accountLabel.font = .preferredFont(forTextStyle: .subheadline)
|
|
|
|
accountLabel.adjustsFontForContentSizeCategory = true
|
|
|
|
accountLabel.textColor = .secondaryLabel
|
|
|
|
noteTextView.backgroundColor = .clear
|
2020-09-26 02:57:35 +02:00
|
|
|
noteTextView.delegate = self
|
2020-09-23 03:00:56 +02:00
|
|
|
|
2021-01-26 07:57:44 +01:00
|
|
|
let largeTitlePointSize = UIFont.preferredFont(forTextStyle: .largeTitle).pointSize
|
|
|
|
|
|
|
|
stackView.addArrangedSubview(acceptFollowRequestButton)
|
|
|
|
acceptFollowRequestButton.setImage(
|
|
|
|
UIImage(systemName: "checkmark.circle",
|
|
|
|
withConfiguration: UIImage.SymbolConfiguration(pointSize: largeTitlePointSize)),
|
|
|
|
for: .normal)
|
|
|
|
acceptFollowRequestButton.setContentHuggingPriority(.required, for: .horizontal)
|
|
|
|
acceptFollowRequestButton.addAction(
|
|
|
|
UIAction { [weak self] _ in self?.accountConfiguration.viewModel.acceptFollowRequest() },
|
|
|
|
for: .touchUpInside)
|
|
|
|
|
|
|
|
stackView.addArrangedSubview(rejectFollowRequestButton)
|
|
|
|
rejectFollowRequestButton.setImage(
|
|
|
|
UIImage(systemName: "xmark.circle",
|
|
|
|
withConfiguration: UIImage.SymbolConfiguration(pointSize: largeTitlePointSize)),
|
|
|
|
for: .normal)
|
|
|
|
rejectFollowRequestButton.tintColor = .systemRed
|
|
|
|
rejectFollowRequestButton.setContentHuggingPriority(.required, for: .horizontal)
|
|
|
|
rejectFollowRequestButton.addAction(
|
|
|
|
UIAction { [weak self] _ in self?.accountConfiguration.viewModel.rejectFollowRequest() },
|
|
|
|
for: .touchUpInside)
|
|
|
|
|
2020-09-23 03:00:56 +02:00
|
|
|
NSLayoutConstraint.activate([
|
2020-10-12 07:37:34 +02:00
|
|
|
avatarImageView.widthAnchor.constraint(equalToConstant: .avatarDimension),
|
|
|
|
avatarImageView.heightAnchor.constraint(equalToConstant: .avatarDimension),
|
2021-01-26 07:57:44 +01:00
|
|
|
acceptFollowRequestButton.widthAnchor.constraint(greaterThanOrEqualToConstant: .avatarDimension),
|
|
|
|
acceptFollowRequestButton.heightAnchor.constraint(greaterThanOrEqualToConstant: .avatarDimension),
|
|
|
|
rejectFollowRequestButton.widthAnchor.constraint(greaterThanOrEqualToConstant: .avatarDimension),
|
|
|
|
rejectFollowRequestButton.heightAnchor.constraint(greaterThanOrEqualToConstant: .avatarDimension),
|
|
|
|
stackView.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor),
|
2020-09-26 02:52:59 +02:00
|
|
|
stackView.topAnchor.constraint(equalTo: readableContentGuide.topAnchor),
|
2021-01-26 07:57:44 +01:00
|
|
|
stackView.bottomAnchor.constraint(equalTo: readableContentGuide.bottomAnchor),
|
|
|
|
stackView.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor)
|
2020-09-23 03:00:56 +02:00
|
|
|
])
|
2021-02-02 22:49:29 +01:00
|
|
|
|
|
|
|
isAccessibilityElement = true
|
2020-09-23 03:00:56 +02:00
|
|
|
}
|
|
|
|
|
2021-02-02 22:49:29 +01:00
|
|
|
// swiftlint:disable:next function_body_length
|
2020-09-23 03:00:56 +02:00
|
|
|
func applyAccountConfiguration() {
|
2021-01-26 07:57:44 +01:00
|
|
|
let viewModel = accountConfiguration.viewModel
|
2020-09-26 02:52:59 +02:00
|
|
|
|
2021-01-26 07:57:44 +01:00
|
|
|
avatarImageView.kf.setImage(with: viewModel.avatarURL(profile: false))
|
|
|
|
|
2021-02-02 22:49:29 +01:00
|
|
|
let mutableDisplayName = NSMutableAttributedString(string: viewModel.displayName)
|
2020-09-26 02:52:59 +02:00
|
|
|
|
2021-02-02 22:49:29 +01:00
|
|
|
mutableDisplayName.insert(emojis: viewModel.emojis, view: displayNameLabel)
|
|
|
|
mutableDisplayName.resizeAttachments(toLineHeight: displayNameLabel.font.lineHeight)
|
|
|
|
displayNameLabel.attributedText = mutableDisplayName
|
|
|
|
|
|
|
|
displayNameLabel.isHidden = viewModel.displayName.isEmpty
|
2020-09-26 02:52:59 +02:00
|
|
|
|
2021-01-26 07:57:44 +01:00
|
|
|
accountLabel.text = viewModel.accountName
|
|
|
|
|
|
|
|
if viewModel.configuration == .withNote {
|
|
|
|
let noteFont = UIFont.preferredFont(forTextStyle: .callout)
|
|
|
|
let mutableNote = NSMutableAttributedString(attributedString: viewModel.note)
|
|
|
|
let noteRange = NSRange(location: 0, length: mutableNote.length)
|
2020-09-26 02:52:59 +02:00
|
|
|
|
2021-01-26 07:57:44 +01:00
|
|
|
mutableNote.removeAttribute(.font, range: noteRange)
|
|
|
|
mutableNote.addAttributes(
|
|
|
|
[.font: noteFont as Any,
|
|
|
|
.foregroundColor: UIColor.label],
|
|
|
|
range: noteRange)
|
|
|
|
mutableNote.insert(emojis: viewModel.emojis, view: noteTextView)
|
|
|
|
mutableNote.resizeAttachments(toLineHeight: noteFont.lineHeight)
|
|
|
|
|
|
|
|
noteTextView.attributedText = mutableNote
|
|
|
|
noteTextView.isHidden = false
|
|
|
|
} else {
|
|
|
|
noteTextView.isHidden = true
|
|
|
|
}
|
2020-09-26 02:52:59 +02:00
|
|
|
|
2021-01-26 07:57:44 +01:00
|
|
|
let isFollowRequest = viewModel.configuration == .followRequest
|
2020-09-26 02:52:59 +02:00
|
|
|
|
2021-01-26 07:57:44 +01:00
|
|
|
acceptFollowRequestButton.isHidden = !isFollowRequest
|
|
|
|
rejectFollowRequestButton.isHidden = !isFollowRequest
|
2021-02-02 22:49:29 +01:00
|
|
|
|
|
|
|
let accessibilityAttributedLabel = NSMutableAttributedString(string: "")
|
|
|
|
|
|
|
|
if !displayNameLabel.isHidden, let displayName = displayNameLabel.attributedText {
|
|
|
|
accessibilityAttributedLabel.append(displayName)
|
|
|
|
accessibilityAttributedLabel.appendWithSeparator(viewModel.accountName)
|
|
|
|
} else {
|
|
|
|
accessibilityAttributedLabel.appendWithSeparator(viewModel.accountName)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !noteTextView.isHidden, let note = noteTextView.attributedText {
|
|
|
|
accessibilityAttributedLabel.appendWithSeparator(note)
|
|
|
|
}
|
|
|
|
|
|
|
|
self.accessibilityAttributedLabel = accessibilityAttributedLabel
|
|
|
|
|
|
|
|
if isFollowRequest {
|
|
|
|
accessibilityCustomActions = [
|
|
|
|
UIAccessibilityCustomAction(
|
|
|
|
name: NSLocalizedString(
|
|
|
|
"account.accept-follow-request-button.accessibility-label",
|
|
|
|
comment: "")) { [weak self] _ in
|
|
|
|
self?.accountConfiguration.viewModel.acceptFollowRequest()
|
|
|
|
|
|
|
|
return true
|
|
|
|
},
|
|
|
|
UIAccessibilityCustomAction(
|
|
|
|
name: NSLocalizedString(
|
|
|
|
"account.reject-follow-request-button.accessibility-label",
|
|
|
|
comment: "")) { [weak self] _ in
|
|
|
|
self?.accountConfiguration.viewModel.rejectFollowRequest()
|
|
|
|
|
|
|
|
return true
|
|
|
|
}]
|
|
|
|
} else {
|
|
|
|
accessibilityCustomActions = []
|
|
|
|
}
|
2020-09-23 03:00:56 +02:00
|
|
|
}
|
|
|
|
}
|