Files
SideStore/AltStore/Settings/PatreonViewController.swift
Riley Testut cf3977e7f3 Revises Settings + Patreon UI (again)
- Changes background color to red
- Improves Patreon screen
- Adds credits + software licenses
2019-09-07 15:34:07 -07:00

320 lines
12 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// 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>?
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<Patron>
{
let aboutDataSource = RSTDynamicCollectionViewDataSource<Patron>()
aboutDataSource.numberOfSectionsHandler = { 1 }
aboutDataSource.numberOfItemsHandler = { _ in 0 }
let dataSource = RSTCompositeCollectionViewDataSource<Patron>(dataSources: [aboutDataSource, self.patronsDataSource])
dataSource.proxy = self
return dataSource
}
func makePatronsDataSource() -> RSTArrayCollectionViewDataSource<Patron>
{
let patronsDataSource = RSTArrayCollectionViewDataSource<Patron>(items: [])
patronsDataSource.cellConfigurationHandler = { (cell, patron, indexPath) in
let cell = cell as! PatronCollectionViewCell
cell.textLabel.text = patron.name
}
return patronsDataSource
}
func update()
{
self.collectionView.reloadData()
}
func prepare(_ headerView: AboutPatreonHeaderView)
{
headerView.layoutMargins = self.view.layoutMargins
headerView.supportButton.addTarget(self, action: #selector(PatreonViewController.openPatreonURL(_:)), for: .primaryActionTriggered)
headerView.accountButton.removeTarget(self, action: nil, for: .primaryActionTriggered)
if let account = DatabaseManager.shared.patreonAccount(), PatreonAPI.shared.isAuthenticated
{
headerView.accountButton.addTarget(self, action: #selector(PatreonViewController.signOut(_:)), for: .primaryActionTriggered)
headerView.supportButton.setTitle(NSLocalizedString("View Patreon", comment: ""), for: .normal)
headerView.accountButton.setTitle(String(format: NSLocalizedString("Unlink %@", comment: ""), account.name), for: .normal)
let text = """
Hey ,
Youre the best. Your account was linked successfully, so you now have access to the beta versions of all of my apps. You can find them all in the Browse tab.
Thanks for all of your support. Enjoy!
Riley
"""
let font = UIFont.systemFont(ofSize: 16)
let attributedText = NSMutableAttributedString(string: text, attributes: [.font: font,
.foregroundColor: UIColor.white])
let boldedName = NSAttributedString(string: account.firstName ?? account.name, attributes: [.font: UIFont.boldSystemFont(ofSize: font.pointSize),
.foregroundColor: UIColor.white])
attributedText.insert(boldedName, at: 4)
headerView.textView.attributedText = attributedText
}
else
{
headerView.accountButton.addTarget(self, action: #selector(PatreonViewController.authenticate(_:)), for: .primaryActionTriggered)
headerView.supportButton.setTitle(NSLocalizedString("Become a patron", comment: ""), for: .normal)
headerView.accountButton.setTitle(NSLocalizedString("Link Patreon account", comment: ""), for: .normal)
headerView.textView.text = """
Hey y'all,
If you'd like to support my work, you can donate here. In return, you'll gain access to beta versions of all of my apps and be among the first to try the latest features.
Thanks for all your support 💜
Riley
"""
}
}
}
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 unlink your Patreon account?", comment: ""), message: NSLocalizedString("You will no longer have access to beta versions of apps.", comment: ""), preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Unlink Patreon Account", 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
self.prepare(headerView)
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]) }
self.prepare(self.prototypeAboutHeader)
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)
}
}
}