221 lines
8.1 KiB
Swift
221 lines
8.1 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: AccountDisplayNameLabel!
|
|
@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
|
|
contentView.layer.cornerCurve = .continuous
|
|
contentView.backgroundColor = .appGroupedCellBackground
|
|
updateLayerColors()
|
|
|
|
headerImageView.cache = .headers
|
|
|
|
avatarContainerView.layer.masksToBounds = true
|
|
avatarContainerView.layer.cornerCurve = .continuous
|
|
avatarImageView.cache = .avatars
|
|
avatarImageView.layer.masksToBounds = true
|
|
avatarImageView.layer.cornerCurve = .continuous
|
|
|
|
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.withRenderingMode(.alwaysTemplate))
|
|
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()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|