// // FollowRequestNotificationTableViewCell.swift // Tusker // // Created by Shadowfacts on 1/4/20. // Copyright © 2020 Shadowfacts. All rights reserved. // import UIKit import Pachyderm class FollowRequestNotificationTableViewCell: UITableViewCell { weak var delegate: (TuskerNavigationDelegate & MenuActionProvider)? var mastodonController: MastodonController! { delegate?.apiController } @IBOutlet weak var stackView: UIStackView! @IBOutlet weak var avatarImageView: UIImageView! @IBOutlet weak var timestampLabel: UILabel! @IBOutlet weak var actionLabel: EmojiLabel! @IBOutlet weak var actionButtonsStackView: UIStackView! @IBOutlet weak var acceptButton: UIButton! @IBOutlet weak var rejectButton: UIButton! var notification: Pachyderm.Notification? var account: Account! private var avatarRequest: ImageCache.Request? private var updateTimestampWorkItem: DispatchWorkItem? private var isGrayscale = false deinit { updateTimestampWorkItem?.cancel() } override func awakeFromNib() { super.awakeFromNib() timestampLabel.font = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .body).addingAttributes([ .traits: [ UIFontDescriptor.TraitKey.weight: UIFont.Weight.light.rawValue, ] ]), size: 0) timestampLabel.adjustsFontForContentSizeCategory = true avatarImageView.layer.masksToBounds = true NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil) updateUIForPreferences() } @objc func updateUIForPreferences() { avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 30 if isGrayscale != Preferences.shared.grayscaleImages, let account = self.account { updateUI(account: account) } } func updateUI(notification: Pachyderm.Notification) { self.notification = notification updateUI(account: notification.account) updateTimestamp() } func updateUI(account: Account) { // todo: update to use managed objects self.account = account if Preferences.shared.hideCustomEmojiInUsernames { actionLabel.text = "Request to follow from \(account.displayName)" actionLabel.removeEmojis() } else { actionLabel.text = "Request to follow from \(account.displayName)" actionLabel.setEmojis(account.emojis, identifier: account.id) } if let avatarURL = account.avatar { avatarRequest = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in guard let self = self else { return } self.avatarRequest = nil guard self.account == account, let image = image, let transformedImage = ImageGrayscalifier.convertIfNecessary(url: avatarURL, image: image) else { return } DispatchQueue.main.async { self.avatarImageView.image = transformedImage } } } } private func updateTimestamp() { guard let notification = 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() avatarRequest?.cancel() updateTimestampWorkItem?.cancel() updateTimestampWorkItem = nil } private func addLabel(_ text: String) { let label = UILabel() label.textAlignment = .center label.font = .boldSystemFont(ofSize: 17) label.text = text self.stackView.addArrangedSubview(label) } // MARK: - Interaction @IBAction func rejectButtonPressed() { acceptButton.isEnabled = false rejectButton.isEnabled = false Task { let request = Account.rejectFollowRequest(account) do { _ = try await mastodonController.run(request) UIImpactFeedbackGenerator(style: .light).impactOccurred() self.actionButtonsStackView.isHidden = true self.addLabel(NSLocalizedString("Rejected", comment: "rejected follow request label")) } catch let error as Client.Error { acceptButton.isEnabled = true rejectButton.isEnabled = true if let toastable = delegate?.toastableViewController { let config = ToastConfiguration(from: error, with: "Rejecting Follow", in: toastable) { [weak self] toast in toast.dismissToast(animated: true) self?.rejectButtonPressed() } toastable.showToast(configuration: config, animated: true) } } } } @IBAction func acceptButtonPressed() { acceptButton.isEnabled = false rejectButton.isEnabled = false Task { let request = Account.authorizeFollowRequest(account) do { _ = try await mastodonController.run(request) UIImpactFeedbackGenerator(style: .light).impactOccurred() self.actionButtonsStackView.isHidden = true self.addLabel(NSLocalizedString("Accepted", comment: "accepted follow request label")) } catch let error as Client.Error { acceptButton.isEnabled = true rejectButton.isEnabled = true if let toastable = delegate?.toastableViewController { let config = ToastConfiguration(from: error, with: "Accepting Follow", in: toastable) { [weak self] toast in toast.dismissToast(animated: true) self?.acceptButtonPressed() } toastable.showToast(configuration: config, animated: true) } } } } } extension FollowRequestNotificationTableViewCell: SelectableTableViewCell { func didSelectCell() { delegate?.selected(account: account.id) } } extension FollowRequestNotificationTableViewCell: MenuPreviewProvider { func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? { guard let mastodonController = mastodonController else { return nil } return (content: { return ProfileViewController(accountID: self.account.id, mastodonController: mastodonController) }, actions: { return [] }) } } extension FollowRequestNotificationTableViewCell: DraggableTableViewCell { func dragItemsForBeginning(session: UIDragSession) -> [UIDragItem] { let provider = NSItemProvider(object: account.url as NSURL) let activity = UserActivityManager.showProfileActivity(id: account.id, accountID: mastodonController.accountInfo!.id) activity.displaysAuxiliaryScene = true provider.registerObject(activity, visibility: .all) return [UIDragItem(itemProvider: provider)] } }