// // InstanceTimelineViewController.swift // Tusker // // Created by Shadowfacts on 12/19/19. // Copyright © 2019 Shadowfacts. All rights reserved. // import UIKit import Pachyderm @MainActor protocol InstanceTimelineViewControllerDelegate: AnyObject { func didSaveInstance(url: URL) func didUnsaveInstance(url: URL) } class InstanceTimelineViewController: TimelineViewController { weak var instanceTimelineDelegate: InstanceTimelineViewControllerDelegate? weak var parentMastodonController: MastodonController? let instanceURL: URL let instanceMastodonController: MastodonController private var toggleSaveButton: UIBarButtonItem! private var isInstanceSaved: Bool { let req = SavedInstance.fetchRequest(url: instanceURL, account: parentMastodonController!.accountInfo!) return parentMastodonController!.persistentContainer.viewContext.objectExists(for: req) } private var toggleSaveButtonTitle: String { if isInstanceSaved { return NSLocalizedString("Unsave", comment: "unsave instance button") } else { return NSLocalizedString("Save", comment: "save instance button") } } var browsingEnabled = true init(for url: URL, parentMastodonController: MastodonController) { self.parentMastodonController = parentMastodonController self.instanceURL = url // the timeline VC only stores a weak reference to it, so we need to store a strong reference to make sure it's not released immediately instanceMastodonController = MastodonController(instanceURL: url, transient: true) super.init(for: .public(local: true), mastodonController: instanceMastodonController) title = url.host! userActivity = nil // todo: activity for instance-specific timelines } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() toggleSaveButton = UIBarButtonItem(title: toggleSaveButtonTitle, style: .plain, target: self, action: #selector(toggleSaveButtonPressed)) navigationItem.rightBarButtonItem = toggleSaveButton NotificationCenter.default.addObserver(self, selector: #selector(savedInstancesChanged), name: .savedInstancesChanged, object: nil) } @objc func savedInstancesChanged() { toggleSaveButton.title = toggleSaveButtonTitle } override func configureStatusCell(_ cell: TimelineStatusCollectionViewCell, id: String, state: CollapseState, filterResult: Filterer.Result, precomputedContent: NSAttributedString?) { cell.delegate = browsingEnabled ? self : nil cell.overrideMastodonController = mastodonController cell.updateUI(statusID: id, state: state, filterResult: filterResult, precomputedContent: precomputedContent) } override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { guard browsingEnabled else { return } super.collectionView(collectionView, didSelectItemAt: indexPath) } // MARK: Timeline override func handleLoadAllError(_ error: Swift.Error) async { guard let error = error as? Client.Error else { await super.handleLoadAllError(error) return } let code: Int switch error.type { case .mastodonError(let c, _), .unexpectedStatus(let c): code = c default: await super.handleLoadAllError(error) return } guard code == 422 || code == 401 else { await super.handleLoadAllError(error) return } collectionView.isHidden = true view.backgroundColor = .appBackground let image = UIImageView(image: UIImage(systemName: "lock.fill")) image.tintColor = .secondaryLabel image.contentMode = .scaleAspectFit let title = UILabel() title.textColor = .secondaryLabel title.font = .preferredFont(forTextStyle: .title1).withTraits(.traitBold)! title.adjustsFontForContentSizeCategory = true title.numberOfLines = 0 title.textAlignment = .center title.text = "This instance requires an account to view." let stack = UIStackView(arrangedSubviews: [ image, title, ]) stack.axis = .vertical stack.alignment = .center stack.spacing = 8 stack.isAccessibilityElement = true stack.accessibilityLabel = title.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), ]) } // MARK: Interaction @objc func toggleSaveButtonPressed() { let context = parentMastodonController!.persistentContainer.viewContext let req = SavedInstance.fetchRequest(url: instanceURL, account: parentMastodonController!.accountInfo!) let existing = try? context.fetch(req).first if let existing = existing { context.delete(existing) instanceTimelineDelegate?.didUnsaveInstance(url: instanceURL) } else { _ = SavedInstance(url: instanceURL, account: parentMastodonController!.accountInfo!, context: context) instanceTimelineDelegate?.didSaveInstance(url: instanceURL) } mastodonController.persistentContainer.save(context: context) } }