Files
SideStore/SideStoreApp/Sources/SideStoreUIKit/Settings/PatreonViewController.swift

289 lines
12 KiB
Swift
Raw Permalink Normal View History

//
// PatreonViewController.swift
// AltStore
//
2019-09-05 15:37:58 -07:00
// Created by Riley Testut on 9/5/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import AuthenticationServices
2023-03-01 00:48:36 -05:00
import SafariServices
import UIKit
2023-03-01 00:48:36 -05:00
import SideStoreCore
2023-03-01 14:36:52 -05:00
import RoxasUIKit
2023-03-01 00:48:36 -05:00
extension PatreonViewController {
private enum Section: Int, CaseIterable {
2019-09-05 15:37:58 -07:00
case about
case patrons
}
}
2023-03-01 00:48:36 -05:00
final class PatreonViewController: UICollectionViewController {
private lazy var dataSource = self.makeDataSource()
2019-09-05 15:37:58 -07:00
private lazy var patronsDataSource = self.makePatronsDataSource()
2023-03-01 00:48:36 -05:00
2019-09-05 15:37:58 -07:00
private var prototypeAboutHeader: AboutPatreonHeaderView!
2023-03-01 00:48:36 -05:00
2019-10-24 13:04:30 -07:00
override var preferredStatusBarStyle: UIStatusBarStyle {
2023-03-01 00:48:36 -05:00
.lightContent
2019-10-24 13:04:30 -07:00
}
2023-03-01 00:48:36 -05:00
override func viewDidLoad() {
super.viewDidLoad()
2023-03-01 00:48:36 -05:00
2023-03-02 01:09:10 -05:00
let aboutHeaderNib = UINib(nibName: "AboutPatreonHeaderView", bundle: Bundle(for: PatronsHeaderView.self))
2023-03-01 00:48:36 -05:00
prototypeAboutHeader = aboutHeaderNib.instantiate(withOwner: nil, options: nil)[0] as? AboutPatreonHeaderView
collectionView.dataSource = dataSource
collectionView.register(aboutHeaderNib, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "AboutHeader")
collectionView.register(PatronsHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "PatronsHeader")
// self.collectionView.register(PatronsFooterView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "PatronsFooter")
// NotificationCenter.default.addObserver(self, selector: #selector(PatreonViewController.didUpdatePatrons(_:)), name: AppManager.didUpdatePatronsNotification, object: nil)
update()
}
2023-03-01 00:48:36 -05:00
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
2023-03-01 00:48:36 -05:00
// self.fetchPatrons()
update()
2019-09-05 15:37:58 -07:00
}
2023-03-01 00:48:36 -05:00
override func viewDidLayoutSubviews() {
2019-09-05 15:37:58 -07:00
super.viewDidLayoutSubviews()
2023-03-01 00:48:36 -05:00
let layout = collectionViewLayout as! UICollectionViewFlowLayout
var itemWidth = (collectionView.bounds.width - (layout.sectionInset.left + layout.sectionInset.right + layout.minimumInteritemSpacing)) / 2
2019-09-05 15:37:58 -07:00
itemWidth.round(.down)
2023-03-01 00:48:36 -05:00
// TODO: if the intention here is to hide the cells, we should just modify the data source. @JoeMatt
layout.itemSize = CGSize(width: 0, height: 0)
}
}
2023-03-01 00:48:36 -05:00
private extension PatreonViewController {
func makeDataSource() -> RSTCompositeCollectionViewDataSource<ManagedPatron> {
let aboutDataSource = RSTDynamicCollectionViewDataSource<ManagedPatron>()
2019-09-05 15:37:58 -07:00
aboutDataSource.numberOfSectionsHandler = { 1 }
aboutDataSource.numberOfItemsHandler = { _ in 0 }
2023-03-01 00:48:36 -05:00
let dataSource = RSTCompositeCollectionViewDataSource<ManagedPatron>(dataSources: [aboutDataSource, patronsDataSource])
2019-09-05 15:37:58 -07:00
dataSource.proxy = self
return dataSource
}
2023-03-01 00:48:36 -05:00
func makePatronsDataSource() -> RSTFetchedResultsCollectionViewDataSource<ManagedPatron> {
let fetchRequest: NSFetchRequest<ManagedPatron> = ManagedPatron.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(ManagedPatron.name), ascending: true, selector: #selector(NSString.caseInsensitiveCompare(_:)))]
2023-03-01 00:48:36 -05:00
let patronsDataSource = RSTFetchedResultsCollectionViewDataSource<ManagedPatron>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
2023-03-01 00:48:36 -05:00
patronsDataSource.cellConfigurationHandler = { cell, patron, _ in
2019-09-05 15:37:58 -07:00
let cell = cell as! PatronCollectionViewCell
cell.textLabel.text = patron.name
}
2023-03-01 00:48:36 -05:00
2019-09-05 15:37:58 -07:00
return patronsDataSource
}
2023-03-01 00:48:36 -05:00
func update() {
collectionView.reloadData()
}
2023-03-01 00:48:36 -05:00
func prepare(_ headerView: AboutPatreonHeaderView) {
headerView.layoutMargins = view.layoutMargins
headerView.supportButton.addTarget(self, action: #selector(PatreonViewController.openPatreonURL(_:)), for: .primaryActionTriggered)
2023-03-01 00:48:36 -05:00
let defaultSupportButtonTitle = NSLocalizedString("Become a patron", comment: "")
let isPatronSupportButtonTitle = NSLocalizedString("View Patreon", comment: "")
2023-03-01 00:48:36 -05:00
let defaultText = NSLocalizedString("""
Hello, thank you for using SideStore!
2023-03-01 00:48:36 -05:00
If you would subscribe to the patreon that would support us and make sure we can continue developing SideStore for you.
2023-03-01 00:48:36 -05:00
-SideTeam
""", comment: "")
2023-03-01 00:48:36 -05:00
let isPatronText = NSLocalizedString("""
Hey ,
2023-03-01 00:48:36 -05:00
2022-04-13 19:40:11 -07:00
Youre the best. Your account was linked successfully, so you now have access to the beta versions of all of our apps. You can find them all in the Browse tab.
2023-03-01 00:48:36 -05:00
Thanks for all of your support. Enjoy!
- SideTeam
""", comment: "")
2023-03-01 00:48:36 -05:00
if let account = DatabaseManager.shared.patreonAccount(), PatreonAPI.shared.isAuthenticated {
headerView.accountButton.addTarget(self, action: #selector(PatreonViewController.signOut(_:)), for: .primaryActionTriggered)
headerView.accountButton.setTitle(String(format: NSLocalizedString("Unlink %@", comment: ""), account.name), for: .normal)
2023-03-01 00:48:36 -05:00
if account.isPatron {
headerView.supportButton.setTitle(isPatronSupportButtonTitle, for: .normal)
2023-03-01 00:48:36 -05:00
let font = UIFont.systemFont(ofSize: 16)
2023-03-01 00:48:36 -05:00
let attributedText = NSMutableAttributedString(string: isPatronText, attributes: [.font: font,
.foregroundColor: UIColor.white])
2023-03-01 00:48:36 -05:00
let boldedName = NSAttributedString(string: account.firstName ?? account.name,
attributes: [.font: UIFont.boldSystemFont(ofSize: font.pointSize),
.foregroundColor: UIColor.white])
attributedText.insert(boldedName, at: 4)
2023-03-01 00:48:36 -05:00
headerView.textView.attributedText = attributedText
2023-03-01 00:48:36 -05:00
} else {
headerView.supportButton.setTitle(defaultSupportButtonTitle, for: .normal)
headerView.textView.text = defaultText
}
}
}
2019-09-05 15:37:58 -07:00
}
2023-03-01 00:48:36 -05:00
private extension PatreonViewController {
@objc func fetchPatrons() {
AppManager.shared.updatePatronsIfNeeded()
2023-03-01 00:48:36 -05:00
update()
}
2023-03-01 00:48:36 -05:00
@objc func openPatreonURL(_: UIButton) {
2023-01-18 14:41:24 -05:00
let patreonURL = URL(string: "https://www.patreon.com/SideStore")!
2023-03-01 00:48:36 -05:00
2019-09-05 15:37:58 -07:00
let safariViewController = SFSafariViewController(url: patreonURL)
2023-03-01 00:48:36 -05:00
safariViewController.preferredControlTintColor = view.tintColor
present(safariViewController, animated: true, completion: nil)
2019-09-05 15:37:58 -07:00
}
2023-03-01 00:48:36 -05:00
@IBAction func authenticate(_: UIBarButtonItem) {
PatreonAPI.shared.authenticate { result in
do {
let account = try result.get()
try account.managedObjectContext?.save()
2023-03-01 00:48:36 -05:00
DispatchQueue.main.async {
self.update()
}
2023-03-01 00:48:36 -05:00
} catch ASWebAuthenticationSessionError.canceledLogin {
// Ignore
2023-03-01 00:48:36 -05:00
} catch {
DispatchQueue.main.async {
let toastView = ToastView(error: error)
toastView.show(in: self)
}
}
}
}
2023-03-01 00:48:36 -05:00
@IBAction func signOut(_: UIBarButtonItem) {
func signOut() {
PatreonAPI.shared.signOut { result in
do {
2019-09-05 15:37:58 -07:00
try result.get()
2023-03-01 00:48:36 -05:00
2019-09-05 15:37:58 -07:00
DispatchQueue.main.async {
self.update()
}
2023-03-01 00:48:36 -05:00
} catch {
2019-09-05 15:37:58 -07:00
DispatchQueue.main.async {
let toastView = ToastView(error: error)
toastView.show(in: self)
2019-09-05 15:37:58 -07:00
}
}
}
2019-09-05 15:37:58 -07:00
}
2023-03-01 00:48:36 -05:00
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() })
2019-09-05 15:37:58 -07:00
alertController.addAction(.cancel)
2023-03-01 00:48:36 -05:00
present(alertController, animated: true, completion: nil)
2019-09-05 15:37:58 -07:00
}
2023-03-01 00:48:36 -05:00
@objc func didUpdatePatrons(_: Notification) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
// Wait short delay before reloading or else footer won't properly update if it's already visible 🤷
self.collectionView.reloadData()
}
}
2019-09-05 15:37:58 -07:00
}
2023-03-01 00:48:36 -05:00
extension PatreonViewController {
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
2019-09-05 15:37:58 -07:00
let section = Section.allCases[indexPath.section]
2023-03-01 00:48:36 -05:00
switch section {
2019-09-05 15:37:58 -07:00
case .about:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "AboutHeader", for: indexPath) as! AboutPatreonHeaderView
2023-03-01 00:48:36 -05:00
prepare(headerView)
2019-09-05 15:37:58 -07:00
return headerView
2023-03-01 00:48:36 -05:00
2019-09-05 15:37:58 -07:00
case .patrons:
2023-03-01 00:48:36 -05:00
if kind == UICollectionView.elementKindSectionHeader {
2019-09-05 15:37:58 -07:00
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "PatronsHeader", for: indexPath) as! PatronsHeaderView
headerView.textLabel.text = NSLocalizedString("Special thanks to...", comment: "")
return headerView
2023-03-01 00:48:36 -05:00
} else {
2019-09-05 15:37:58 -07:00
let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "PatronsFooter", for: indexPath) as! PatronsFooterView
footerView.button.isIndicatingActivity = false
footerView.button.isHidden = false
2023-03-01 00:48:36 -05:00
// footerView.button.addTarget(self, action: #selector(PatreonViewController.fetchPatrons), for: .primaryActionTriggered)
switch AppManager.shared.updatePatronsResult {
case .none: footerView.button.isIndicatingActivity = true
case .success?: footerView.button.isHidden = true
case .failure?:
#if DEBUG
2023-03-01 00:48:36 -05:00
let debug = true
#else
2023-03-01 00:48:36 -05:00
let debug = false
#endif
2023-03-01 00:48:36 -05:00
if patronsDataSource.itemCount == 0 || debug {
// Only show error message if there aren't any cached Patrons (or if this is a debug build).
2023-03-01 00:48:36 -05:00
footerView.button.isHidden = false
footerView.button.setTitle(NSLocalizedString("Error Loading Patrons", comment: ""), for: .normal)
2023-03-01 00:48:36 -05:00
} else {
footerView.button.isHidden = true
}
}
2023-03-01 00:48:36 -05:00
2019-09-05 15:37:58 -07:00
return footerView
}
}
}
}
2019-09-05 15:37:58 -07:00
2023-03-01 00:48:36 -05:00
extension PatreonViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout _: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
2019-09-05 15:37:58 -07:00
let section = Section.allCases[section]
2023-03-01 00:48:36 -05:00
switch section {
2019-09-05 15:37:58 -07:00
case .about:
2023-03-01 00:48:36 -05:00
let widthConstraint = prototypeAboutHeader.widthAnchor.constraint(equalToConstant: collectionView.bounds.width)
2019-09-05 15:37:58 -07:00
NSLayoutConstraint.activate([widthConstraint])
defer { NSLayoutConstraint.deactivate([widthConstraint]) }
2023-03-01 00:48:36 -05:00
prepare(prototypeAboutHeader)
let size = prototypeAboutHeader.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
2019-09-05 15:37:58 -07:00
return size
2023-03-01 00:48:36 -05:00
2019-09-05 15:37:58 -07:00
case .patrons:
return CGSize(width: 0, height: 0)
2019-09-05 15:37:58 -07:00
}
}
2023-03-01 00:48:36 -05:00
func collectionView(_: UICollectionView, layout _: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
2019-09-05 15:37:58 -07:00
let section = Section.allCases[section]
2023-03-01 00:48:36 -05:00
switch section {
2019-09-05 15:37:58 -07:00
case .about: return .zero
case .patrons: return CGSize(width: 0, height: 0)
2019-09-05 15:37:58 -07:00
}
}
}