// // PatreonViewController.swift // AltStore // // Created by Riley Testut on 9/5/19. // Copyright © 2019 Riley Testut. All rights reserved. // import UIKit import SafariServices import AuthenticationServices import Roxas extension PatreonViewController { private enum Section: Int, CaseIterable { case about case patrons } } class PatreonViewController: UICollectionViewController { private lazy var dataSource = self.makeDataSource() private lazy var patronsDataSource = self.makePatronsDataSource() private var prototypeAboutHeader: AboutPatreonHeaderView! private var patronsResult: Result<[Patron], Error>? @IBOutlet private var signInButton: UIBarButtonItem! @IBOutlet private var signOutButton: UIBarButtonItem! override func viewDidLoad() { super.viewDidLoad() let aboutHeaderNib = UINib(nibName: "AboutPatreonHeaderView", bundle: nil) self.prototypeAboutHeader = aboutHeaderNib.instantiate(withOwner: nil, options: nil)[0] as? AboutPatreonHeaderView self.collectionView.dataSource = self.dataSource self.collectionView.register(aboutHeaderNib, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "AboutHeader") self.collectionView.register(PatronsHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "PatronsHeader") self.collectionView.register(PatronsFooterView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "PatronsFooter") self.update() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.fetchPatrons() self.update() } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() let layout = self.collectionViewLayout as! UICollectionViewFlowLayout var itemWidth = (self.collectionView.bounds.width - (layout.sectionInset.left + layout.sectionInset.right + layout.minimumInteritemSpacing)) / 2 itemWidth.round(.down) layout.itemSize = CGSize(width: itemWidth, height: layout.itemSize.height) } } private extension PatreonViewController { func makeDataSource() -> RSTCompositeCollectionViewDataSource { let aboutDataSource = RSTDynamicCollectionViewDataSource() aboutDataSource.numberOfSectionsHandler = { 1 } aboutDataSource.numberOfItemsHandler = { _ in 0 } let dataSource = RSTCompositeCollectionViewDataSource(dataSources: [aboutDataSource, self.patronsDataSource]) dataSource.proxy = self return dataSource } func makePatronsDataSource() -> RSTArrayCollectionViewDataSource { let patronsDataSource = RSTArrayCollectionViewDataSource(items: []) patronsDataSource.cellConfigurationHandler = { (cell, patron, indexPath) in let cell = cell as! PatronCollectionViewCell cell.textLabel.text = patron.name } return patronsDataSource } func update() { if PatreonAPI.shared.isAuthenticated && DatabaseManager.shared.patreonAccount() != nil { self.navigationItem.rightBarButtonItem = self.signOutButton } else { self.navigationItem.rightBarButtonItem = self.signInButton } } } private extension PatreonViewController { @objc func fetchPatrons() { if let result = self.patronsResult, case .failure = result { self.patronsResult = nil self.collectionView.reloadData() } PatreonAPI.shared.fetchPatrons { (result) in self.patronsResult = result do { let patrons = try result.get() let sortedPatrons = patrons.sorted { $0.name < $1.name } self.patronsDataSource.items = sortedPatrons } catch { print("Failed to fetch patrons:", error) DispatchQueue.main.async { self.collectionView.reloadData() } } } } @objc func openPatreonURL(_ sender: UIButton) { let patreonURL = URL(string: "https://www.patreon.com/rileytestut")! let safariViewController = SFSafariViewController(url: patreonURL) safariViewController.preferredControlTintColor = self.view.tintColor self.present(safariViewController, animated: true, completion: nil) } @IBAction func authenticate(_ sender: UIBarButtonItem) { PatreonAPI.shared.authenticate { (result) in do { let account = try result.get() try account.managedObjectContext?.save() DispatchQueue.main.async { self.update() } } catch ASWebAuthenticationSessionError.canceledLogin { // Ignore } catch { DispatchQueue.main.async { let toastView = ToastView(text: error.localizedDescription, detailText: nil) toastView.show(in: self.navigationController?.view ?? self.view, duration: 2.0) } } } } @IBAction func signOut(_ sender: UIBarButtonItem) { func signOut() { PatreonAPI.shared.signOut { (result) in do { try result.get() DispatchQueue.main.async { self.update() } } catch { DispatchQueue.main.async { let toastView = ToastView(text: error.localizedDescription, detailText: nil) toastView.show(in: self.navigationController?.view ?? self.view, duration: 2.0) } } } } let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to sign out?", comment: ""), message: NSLocalizedString("You will no longer have access to beta versions of apps once you sign out.", comment: ""), preferredStyle: .actionSheet) alertController.addAction(UIAlertAction(title: NSLocalizedString("Sign Out", comment: ""), style: .destructive) { _ in signOut() }) alertController.addAction(.cancel) self.present(alertController, animated: true, completion: nil) } } extension PatreonViewController { override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { let section = Section.allCases[indexPath.section] switch section { case .about: let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "AboutHeader", for: indexPath) as! AboutPatreonHeaderView headerView.supportButton.addTarget(self, action: #selector(PatreonViewController.openPatreonURL(_:)), for: .primaryActionTriggered) return headerView case .patrons: if kind == UICollectionView.elementKindSectionHeader { let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "PatronsHeader", for: indexPath) as! PatronsHeaderView headerView.textLabel.text = NSLocalizedString("Special thanks to...", comment: "") return headerView } else { let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "PatronsFooter", for: indexPath) as! PatronsFooterView footerView.button.isIndicatingActivity = false footerView.button.isHidden = false footerView.button.addTarget(self, action: #selector(PatreonViewController.fetchPatrons), for: .primaryActionTriggered) switch self.patronsResult { case .none: footerView.button.isIndicatingActivity = true case .success?: footerView.button.isHidden = true case .failure?: footerView.button.setTitle(NSLocalizedString("Error Loading Patrons", comment: ""), for: .normal) } return footerView } } } } extension PatreonViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { let section = Section.allCases[section] switch section { case .about: let widthConstraint = self.prototypeAboutHeader.widthAnchor.constraint(equalToConstant: collectionView.bounds.width) NSLayoutConstraint.activate([widthConstraint]) defer { NSLayoutConstraint.deactivate([widthConstraint]) } let size = self.prototypeAboutHeader.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) return size case .patrons: return CGSize(width: 320, height: 20) } } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize { let section = Section.allCases[section] switch section { case .about: return .zero case .patrons: return CGSize(width: 320, height: 20) } } }