Tusker/Tusker/Screens/Explore/SuggestedProfileCardCollect...

217 lines
7.9 KiB
Swift

//
// SuggestedProfileCardCollectionViewCell.swift
// Tusker
//
// Created by Shadowfacts on 1/23/23.
// Copyright © 2023 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
import SwiftUI
class SuggestedProfileCardCollectionViewCell: UICollectionViewCell {
weak var delegate: (any TuskerNavigationDelegate)?
private var mastodonController: MastodonController! { delegate?.apiController }
private var accountID: String!
private var source: Suggestion.Source!
@IBOutlet weak var headerImageView: CachedImageView!
@IBOutlet weak var avatarContainerView: UIView!
@IBOutlet weak var avatarImageView: CachedImageView!
@IBOutlet weak var displayNameLabel: EmojiLabel!
@IBOutlet weak var usernameLabel: UILabel!
@IBOutlet weak var noteTextView: StatusContentTextView!
@IBOutlet weak var suggestionSourceButton: UIButton!
private var hoverGestureAnimator: UIViewPropertyAnimator?
override func awakeFromNib() {
super.awakeFromNib()
layer.shadowOpacity = 0.2
layer.shadowRadius = 8
layer.shadowOffset = .zero
layer.masksToBounds = false
contentView.layer.cornerRadius = 12.5
updateLayerColors()
headerImageView.cache = .headers
avatarContainerView.layer.masksToBounds = true
avatarImageView.cache = .avatars
avatarImageView.layer.masksToBounds = true
displayNameLabel.font = UIFontMetrics(forTextStyle: .title1).scaledFont(for: .systemFont(ofSize: 24, weight: .semibold))
displayNameLabel.adjustsFontForContentSizeCategory = true
usernameLabel.font = UIFontMetrics.default.scaledFont(for: .systemFont(ofSize: 15, weight: .light))
usernameLabel.adjustsFontForContentSizeCategory = true
noteTextView.defaultFont = .preferredFont(forTextStyle: .body)
noteTextView.monospaceFont = UIFontMetrics.default.scaledFont(for: .monospacedSystemFont(ofSize: 17, weight: .regular))
noteTextView.adjustsFontForContentSizeCategory = true
noteTextView.textContainer.lineBreakMode = .byTruncatingTail
addGestureRecognizer(UIHoverGestureRecognizer(target: self, action: #selector(hoverRecognized)))
NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil)
}
@objc private func preferencesChanged() {
guard let accountID,
let mastodonController,
let account = mastodonController.persistentContainer.account(for: accountID) else {
return
}
updateUIForPreferences(account: account)
}
func updateUI(accountID: String, source: Suggestion.Source) {
guard self.accountID != accountID,
let account = mastodonController.persistentContainer.account(for: accountID) else {
return
}
self.accountID = accountID
self.source = source
updateUIForPreferences(account: account)
avatarImageView.update(for: account.avatar)
headerImageView.update(for: account.header)
usernameLabel.text = "@\(account.acct)"
noteTextView.setTextFromHtml(account.note)
var config = UIButton.Configuration.plain()
config.image = source.image
suggestionSourceButton.configuration = config
suggestionSourceButton.setNeedsUpdateConfiguration()
}
private func updateUIForPreferences(account: AccountMO) {
avatarContainerView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarContainerView)
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
displayNameLabel.updateForAccountDisplayName(account: account)
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateLayerColors()
}
private func updateLayerColors() {
if traitCollection.userInterfaceStyle == .dark {
layer.shadowColor = UIColor.darkGray.cgColor
} else {
layer.shadowColor = UIColor.black.cgColor
}
}
// MARK: Interaction
@IBAction func suggestionSourceButtonPressed(_ sender: Any) {
guard let delegate,
let source else {
return
}
let view = SuggestionSourceView(mastodonController: mastodonController, source: source)
let host = UIHostingController(rootView: view)
let toPresent: UIViewController
if traitCollection.horizontalSizeClass == .compact || traitCollection.verticalSizeClass == .compact {
toPresent = UINavigationController(rootViewController: host)
toPresent.modalPresentationStyle = .pageSheet
let sheetPresentationController = toPresent.sheetPresentationController!
sheetPresentationController.detents = [
.medium()
]
} else {
host.modalPresentationStyle = .popover
let popoverPresentationController = host.popoverPresentationController!
popoverPresentationController.sourceView = suggestionSourceButton
host.preferredContentSize = host.sizeThatFits(in: CGSize(width: 400, height: CGFloat.infinity))
toPresent = host
}
delegate.present(toPresent, animated: true)
}
@objc private func hoverRecognized(_ recognizer: UIHoverGestureRecognizer) {
switch recognizer.state {
case .began, .changed:
hoverGestureAnimator = UIViewPropertyAnimator(duration: 0.2, curve: .easeInOut, animations: {
self.transform = CGAffineTransform(scaleX: 1.05, y: 1.05)
})
hoverGestureAnimator!.startAnimation()
case .ended:
hoverGestureAnimator?.stopAnimation(true)
hoverGestureAnimator?.addAnimations {
self.transform = .identity
}
hoverGestureAnimator?.startAnimation()
default:
break
}
}
}
private extension Suggestion.Source {
var image: UIImage {
switch self {
case .global:
return UIImage(systemName: "chart.line.uptrend.xyaxis")!
case .pastInteractions:
return UIImage(systemName: "clock.arrow.circlepath")!
case .staff:
return UIImage(systemName: "person.2")!
}
}
var title: String {
switch self {
case .global:
return "Popular Recently"
case .pastInteractions:
return "Past Interactions"
case .staff:
return "Staff Recommendation"
}
}
}
private struct SuggestionSourceView: View {
let mastodonController: MastodonController
let source: Suggestion.Source
@Environment(\.dismiss) private var dismiss
var body: some View {
VStack(alignment: .leading) {
HStack {
Image(uiImage: source.image)
Text(source.title)
Spacer()
}
switch source {
case .global:
Text("This account is suggested for you because it has been highly active within the past 30 days.")
case .pastInteractions:
Text("This account is suggested for you because you have interacted with it before.")
case .staff:
Text("This account is recommended by the staff of \(mastodonController.accountInfo!.instanceURL.host!)")
}
}
.padding()
.navigationTitle("Suggestion Reason")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Done") {
dismiss()
}
}
}
}
}