2019-08-28 11:13:22 -07:00
//
// P a t r e o n V i e w C o n t r o l l e r . s w i f t
// A l t S t o r e
//
2019-09-05 15:37:58 -07:00
// C r e a t e d b y R i l e y T e s t u t o n 9 / 5 / 1 9 .
2019-08-28 11:13:22 -07:00
// C o p y r i g h t © 2 0 1 9 R i l e y T e s t u t . A l l r i g h t s r e s e r v e d .
//
import AuthenticationServices
2023-03-01 00:48:36 -05:00
import SafariServices
import UIKit
2019-08-28 11:13:22 -07:00
2023-03-01 00:48:36 -05:00
import SideStoreCore
2023-03-01 14:36:52 -05:00
import RoxasUIKit
2019-08-28 11:13:22 -07:00
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 {
2019-08-28 11:13:22 -07:00
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 ( ) {
2019-08-28 11:13:22 -07:00
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 " )
// s e l f . c o l l e c t i o n V i e w . r e g i s t e r ( P a t r o n s F o o t e r V i e w . s e l f , f o r S u p p l e m e n t a r y V i e w O f K i n d : U I C o l l e c t i o n V i e w . e l e m e n t K i n d S e c t i o n F o o t e r , w i t h R e u s e I d e n t i f i e r : " P a t r o n s F o o t e r " )
// N o t i f i c a t i o n C e n t e r . d e f a u l t . a d d O b s e r v e r ( s e l f , s e l e c t o r : # s e l e c t o r ( P a t r e o n V i e w C o n t r o l l e r . d i d U p d a t e P a t r o n s ( _ : ) ) , n a m e : A p p M a n a g e r . d i d U p d a t e P a t r o n s N o t i f i c a t i o n , o b j e c t : n i l )
update ( )
2019-08-28 11:13:22 -07:00
}
2023-03-01 00:48:36 -05:00
override func viewWillAppear ( _ animated : Bool ) {
2019-08-28 11:13:22 -07:00
super . viewWillAppear ( animated )
2023-03-01 00:48:36 -05:00
// s e l f . f e t c h P a t r o n s ( )
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
2022-11-21 21:04:59 -05:00
// TODO: i f t h e i n t e n t i o n h e r e i s t o h i d e t h e c e l l s , w e s h o u l d j u s t m o d i f y t h e d a t a s o u r c e . @ J o e M a t t
2022-11-14 17:36:23 -07:00
layout . itemSize = CGSize ( width : 0 , height : 0 )
2019-08-28 11:13:22 -07:00
}
}
2023-03-01 00:48:36 -05:00
private extension PatreonViewController {
func makeDataSource ( ) -> RSTCompositeCollectionViewDataSource < ManagedPatron > {
2022-04-18 15:31:29 -07:00
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
2019-08-28 11:13:22 -07:00
return dataSource
}
2023-03-01 00:48:36 -05:00
func makePatronsDataSource ( ) -> RSTFetchedResultsCollectionViewDataSource < ManagedPatron > {
2022-04-18 15:31:29 -07:00
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
2022-04-18 15:31:29 -07: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 ( )
2019-09-07 15:34:07 -07:00
}
2023-03-01 00:48:36 -05:00
func prepare ( _ headerView : AboutPatreonHeaderView ) {
headerView . layoutMargins = view . layoutMargins
2019-09-07 15:34:07 -07:00
headerView . supportButton . addTarget ( self , action : #selector ( PatreonViewController . openPatreonURL ( _ : ) ) , for : . primaryActionTriggered )
2023-03-01 00:48:36 -05:00
2019-09-12 13:08:38 -07:00
let defaultSupportButtonTitle = NSLocalizedString ( " Become a patron " , comment : " " )
let isPatronSupportButtonTitle = NSLocalizedString ( " View Patreon " , comment : " " )
2023-03-01 00:48:36 -05:00
2019-09-12 13:08:38 -07:00
let defaultText = NSLocalizedString ( " " "
2022-11-14 17:36:23 -07:00
Hello , thank you for using SideStore !
2023-03-01 00:48:36 -05:00
2022-11-14 17:36:23 -07: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
2022-11-14 17:36:23 -07:00
- SideTeam
2019-09-12 13:08:38 -07:00
" " " , comment: " " )
2023-03-01 00:48:36 -05:00
2019-09-12 13:08:38 -07:00
let isPatronText = NSLocalizedString ( " " "
Hey ,
2023-03-01 00:48:36 -05:00
2022-04-13 19:40:11 -07:00
You ’ re 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
2019-09-12 13:08:38 -07:00
Thanks for all of your support . Enjoy !
2022-11-21 21:04:59 -05:00
- SideTeam
2019-09-12 13:08:38 -07:00
" " " , comment: " " )
2023-03-01 00:48:36 -05:00
if let account = DatabaseManager . shared . patreonAccount ( ) , PatreonAPI . shared . isAuthenticated {
2019-09-07 15:34:07 -07:00
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 {
2019-09-12 13:08:38 -07:00
headerView . supportButton . setTitle ( isPatronSupportButtonTitle , for : . normal )
2023-03-01 00:48:36 -05:00
2019-09-12 13:08:38 -07:00
let font = UIFont . systemFont ( ofSize : 16 )
2023-03-01 00:48:36 -05:00
2019-09-12 13:08:38 -07:00
let attributedText = NSMutableAttributedString ( string : isPatronText , attributes : [ . font : font ,
. foregroundColor : UIColor . white ] )
2023-03-01 00:48:36 -05:00
2019-09-12 13:08:38 -07: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
2019-09-12 13:08:38 -07:00
headerView . textView . attributedText = attributedText
2023-03-01 00:48:36 -05:00
} else {
2019-09-12 13:08:38 -07:00
headerView . supportButton . setTitle ( defaultSupportButtonTitle , for : . normal )
headerView . textView . text = defaultText
}
2019-08-28 11:13:22 -07:00
}
}
2019-09-05 15:37:58 -07:00
}
2023-03-01 00:48:36 -05:00
private extension PatreonViewController {
@objc func fetchPatrons ( ) {
2022-04-14 17:39:43 -07:00
AppManager . shared . updatePatronsIfNeeded ( )
2023-03-01 00:48:36 -05:00
update ( )
2019-08-28 11:13:22 -07:00
}
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 {
2019-08-28 11:13:22 -07:00
let account = try result . get ( )
try account . managedObjectContext ? . save ( )
2023-03-01 00:48:36 -05:00
2019-08-28 11:13:22 -07:00
DispatchQueue . main . async {
self . update ( )
}
2023-03-01 00:48:36 -05:00
} catch ASWebAuthenticationSessionError . canceledLogin {
2019-08-28 11:13:22 -07:00
// I g n o r e
2023-03-01 00:48:36 -05:00
} catch {
2019-08-28 11:13:22 -07:00
DispatchQueue . main . async {
2020-03-30 14:07:18 -07:00
let toastView = ToastView ( error : error )
toastView . show ( in : self )
2019-08-28 11:13:22 -07:00
}
}
}
}
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 {
2020-03-30 14:07:18 -07:00
let toastView = ToastView ( error : error )
toastView . show ( in : self )
2019-09-05 15:37:58 -07:00
}
2019-08-28 11:13:22 -07:00
}
}
2019-09-05 15:37:58 -07:00
}
2023-03-01 00:48:36 -05:00
2019-09-07 15:34:07 -07: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 ) {
2022-09-21 17:31:23 -05:00
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 0.5 ) {
// W a i t s h o r t d e l a y b e f o r e r e l o a d i n g o r e l s e f o o t e r w o n ' t p r o p e r l y u p d a t e i f i t ' s a l r e a d y v i s i b l e 🤷 ♂ ️
2022-04-14 17:39:43 -07:00
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
// f o o t e r V i e w . b u t t o n . a d d T a r g e t ( s e l f , a c t i o n : # s e l e c t o r ( P a t r e o n V i e w C o n t r o l l e r . f e t c h P a t r o n s ) , f o r : . p r i m a r y A c t i o n T r i g g e r e d )
switch AppManager . shared . updatePatronsResult {
2022-09-21 17:31:23 -05:00
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
2022-09-21 17:31:23 -05:00
#else
2023-03-01 00:48:36 -05:00
let debug = false
2022-09-21 17:31:23 -05:00
#endif
2023-03-01 00:48:36 -05:00
if patronsDataSource . itemCount = = 0 || debug {
2022-09-21 17:31:23 -05:00
// O n l y s h o w e r r o r m e s s a g e i f t h e r e a r e n ' t a n y c a c h e d P a t r o n s ( o r i f t h i s i s a d e b u g b u i l d ) .
2023-03-01 00:48:36 -05:00
2022-09-21 17:31:23 -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 {
2022-09-21 17:31:23 -05:00
footerView . button . isHidden = true
2022-04-14 17:39:43 -07:00
}
2019-08-28 11:13:22 -07:00
}
2023-03-01 00:48:36 -05:00
2019-09-05 15:37:58 -07:00
return footerView
2019-08-28 11:13:22 -07:00
}
}
}
}
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 :
2022-11-14 17:36:23 -07:00
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
2022-11-14 17:36:23 -07:00
case . patrons : return CGSize ( width : 0 , height : 0 )
2019-09-05 15:37:58 -07:00
}
}
}