// // NotificationLoadingViewController.swift // Tusker // // Created by Shadowfacts on 4/12/24. // Copyright © 2024 Shadowfacts. All rights reserved. // import UIKit import Pachyderm class NotificationLoadingViewController: UIViewController { private let notificationID: String private let mastodonController: MastodonController init(notificationID: String, mastodonController: MastodonController) { self.notificationID = notificationID self.mastodonController = mastodonController super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .secondarySystemBackground let indicator = UIActivityIndicatorView(style: .medium) indicator.startAnimating() indicator.translatesAutoresizingMaskIntoConstraints = false view.addSubview(indicator) NSLayoutConstraint.activate([ indicator.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor), indicator.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor), ]) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) Task { let request = Client.getNotification(id: notificationID) do { let (notification, _) = try await mastodonController.run(request) await withCheckedContinuation { continuation in mastodonController.persistentContainer.addAll(notifications: [notification]) { continuation.resume() } } showNotification(notification) } catch { showLoadingError(error) } } } private func showNotification(_ notification: Pachyderm.Notification) { let vc: UIViewController switch notification.kind { case .mention, .status, .poll, .update: guard let statusID = notification.status?.id else { showLoadingError(Error.missingStatus) return } vc = ConversationViewController(for: statusID, state: .unknown, mastodonController: mastodonController) case .reblog, .favourite: guard let statusID = notification.status?.id else { showLoadingError(Error.missingStatus) return } let actionType = notification.kind == .reblog ? StatusActionAccountListViewController.ActionType.reblog : .favorite vc = StatusActionAccountListViewController(actionType: actionType, statusID: statusID, statusState: .unknown, accountIDs: [notification.account.id], mastodonController: mastodonController) case .follow: vc = ProfileViewController(accountID: notification.account.id, mastodonController: mastodonController) case .followRequest: // todo return case .unknown: showLoadingError(Error.unknownType) return } guard let navigationController else { fatalError("Don't know how to show notification VC outside of navigation controller") } navigationController.viewControllers[navigationController.viewControllers.count - 1] = vc } private func showLoadingError(_ error: any Swift.Error) { let image = UIImageView(image: UIImage(systemName: "exclamationmark.triangle.fill")!) image.tintColor = .secondaryLabel image.contentMode = .scaleAspectFit let title = UILabel() title.textColor = .secondaryLabel title.font = .preferredFont(forTextStyle: .title1).withTraits(.traitBold)! title.adjustsFontForContentSizeCategory = true title.text = "Couldn't Load Notification" let subtitle = UILabel() subtitle.textColor = .secondaryLabel subtitle.font = .preferredFont(forTextStyle: .body) subtitle.adjustsFontForContentSizeCategory = true subtitle.numberOfLines = 0 subtitle.textAlignment = .center if let error = error as? Error { subtitle.text = error.localizedDescription } else if let error = error as? Client.Error { subtitle.text = error.localizedDescription } else { subtitle.text = error.localizedDescription } let stack = UIStackView(arrangedSubviews: [ image, title, subtitle, ]) stack.axis = .vertical stack.alignment = .center stack.spacing = 8 stack.isAccessibilityElement = true stack.accessibilityLabel = "\(title.text!). \(subtitle.text!)" stack.translatesAutoresizingMaskIntoConstraints = false view.addSubview(stack) NSLayoutConstraint.activate([ image.widthAnchor.constraint(equalToConstant: 64), image.heightAnchor.constraint(equalToConstant: 64), stack.leadingAnchor.constraint(equalToSystemSpacingAfter: view.safeAreaLayoutGuide.leadingAnchor, multiplier: 1), view.safeAreaLayoutGuide.trailingAnchor.constraint(equalToSystemSpacingAfter: stack.trailingAnchor, multiplier: 1), stack.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor), ]) } } private enum Error: LocalizedError { case missingStatus case unknownType var errorDescription: String? { switch self { case .missingStatus: "Missing status for mention/status notification" case .unknownType: "Unknown notification type" } } }