// // StatusUpdatedNotificationCollectionViewCell.swift // Tusker // // Created by Shadowfacts on 5/7/23. // Copyright © 2023 Shadowfacts. All rights reserved. // import UIKit import Pachyderm import SwiftSoup class StatusUpdatedNotificationCollectionViewCell: UICollectionViewListCell { private let iconView = UIImageView(image: UIImage(systemName: "pencil")).configure { $0.contentMode = .scaleAspectFit NSLayoutConstraint.activate([ $0.heightAnchor.constraint(equalToConstant: 30), $0.widthAnchor.constraint(equalToConstant: 30), ]) } private let descriptionLabel = UILabel().configure { $0.text = "A post was edited" $0.font = .preferredFont(forTextStyle: .body) $0.adjustsFontForContentSizeCategory = true $0.setContentHuggingPriority(.init(249), for: .horizontal) } private let timestampLabel = UILabel().configure { $0.textColor = .secondaryLabel $0.font = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .body).addingAttributes([ .traits: [ UIFontDescriptor.TraitKey.weight: UIFont.Weight.light.rawValue, ] ]), size: 0) $0.adjustsFontForContentSizeCategory = true } private lazy var hStack = UIStackView(arrangedSubviews: [ descriptionLabel, timestampLabel, ]).configure { $0.axis = .horizontal $0.alignment = .fill } private let displayNameLabel = EmojiLabel().configure { $0.textColor = .secondaryLabel $0.font = .preferredFont(forTextStyle: .body).withTraits(.traitBold)! $0.adjustsFontForContentSizeCategory = true $0.numberOfLines = 2 $0.lineBreakMode = .byTruncatingTail } private let contentLabel = UILabel().configure { $0.textColor = .secondaryLabel $0.font = .preferredFont(forTextStyle: .body) $0.adjustsFontForContentSizeCategory = true $0.numberOfLines = 2 $0.lineBreakMode = .byTruncatingTail } private lazy var vStack = UIStackView(arrangedSubviews: [ hStack, displayNameLabel, contentLabel, ]).configure { $0.axis = .vertical $0.alignment = .fill } weak var delegate: (TuskerNavigationDelegate & MenuActionProvider)? private var mastodonController: MastodonController { delegate!.apiController } private var notification: Pachyderm.Notification! private var updateTimestampWorkItem: DispatchWorkItem? deinit { updateTimestampWorkItem?.cancel() } override init(frame: CGRect) { super.init(frame: frame) iconView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(iconView) vStack.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(vStack) NSLayoutConstraint.activate([ iconView.topAnchor.constraint(equalTo: vStack.topAnchor), iconView.trailingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16 + 50), vStack.leadingAnchor.constraint(equalTo: iconView.trailingAnchor, constant: 8), vStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), vStack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8), vStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8), ]) NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func updateConfiguration(using state: UICellConfigurationState) { backgroundConfiguration = .appListPlainCell(for: state) } func updateUI(notification: Pachyderm.Notification) { guard notification.kind == .update, let status = notification.status, let account = mastodonController.persistentContainer.account(for: notification.account.id) else { return } self.notification = notification updateTimestamp() updateDisplayName(account: account) // todo: use htmlconverter let doc = try! SwiftSoup.parseBodyFragment(status.content) contentLabel.text = try! doc.text() } @objc private func updateUIForPreferences() { if let account = mastodonController.persistentContainer.account(for: notification.account.id) { updateDisplayName(account: account) } } private func updateDisplayName(account: AccountMO) { if Preferences.shared.hideCustomEmojiInUsernames { displayNameLabel.text = account.displayNameWithoutCustomEmoji displayNameLabel.removeEmojis() } else { displayNameLabel.text = account.displayOrUserName displayNameLabel.setEmojis(account.emojis, identifier: account.id) } } private func updateTimestamp() { guard let notification else { return } timestampLabel.text = notification.createdAt.timeAgoString() let delay: DispatchTimeInterval? switch notification.createdAt.timeAgo().1 { case .second: delay = .seconds(10) case .minute: delay = .seconds(60) default: delay = nil } if let delay = delay { if updateTimestampWorkItem == nil { updateTimestampWorkItem = DispatchWorkItem { [weak self] in self?.updateTimestamp() } DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: updateTimestampWorkItem!) } } else { updateTimestampWorkItem = nil } } override func prepareForReuse() { super.prepareForReuse() updateTimestampWorkItem?.cancel() } // MARK: Accessibility override var accessibilityLabel: String? { get { guard let notification else { return nil } var str = "Post from " str += notification.account.displayNameWithoutCustomEmoji str += " edited " str += notification.createdAt.formatted(.relative(presentation: .numeric)) str += ", " str += contentLabel.text ?? "" return str } set {} } }