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 UIKit
2019-09-05 15:37:58 -07:00
import SafariServices
2019-08-28 11:13:22 -07:00
import AuthenticationServices
2020-09-03 16:39:08 -07:00
import AltStoreCore
2019-08-28 11:13:22 -07:00
import Roxas
2019-09-05 15:37:58 -07:00
extension PatreonViewController
{
private enum Section : Int , CaseIterable
{
case about
case patrons
}
}
2023-01-04 09:52:12 -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 ( )
private var prototypeAboutHeader : AboutPatreonHeaderView !
2019-10-24 13:04:30 -07:00
override var preferredStatusBarStyle : UIStatusBarStyle {
return . lightContent
}
2019-08-28 11:13:22 -07:00
override func viewDidLoad ( )
{
super . viewDidLoad ( )
2019-09-05 15:37:58 -07:00
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 " )
2022-11-14 17:36:23 -07:00
// 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 " )
2019-08-28 11:13:22 -07:00
2022-11-14 17:36:23 -07:00
// 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 )
2022-04-14 17:39:43 -07:00
2019-08-28 11:13:22 -07:00
self . update ( )
}
override func viewWillAppear ( _ animated : Bool )
{
super . viewWillAppear ( animated )
2022-11-14 17:36:23 -07:00
// s e l f . f e t c h P a t r o n s ( )
2019-09-05 15:37:58 -07:00
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 )
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
}
}
private extension PatreonViewController
{
2022-04-18 15:31:29 -07:00
func makeDataSource ( ) -> RSTCompositeCollectionViewDataSource < ManagedPatron >
2019-08-28 11:13:22 -07:00
{
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 }
2019-08-28 11:13:22 -07:00
2022-04-18 15:31:29 -07:00
let dataSource = RSTCompositeCollectionViewDataSource < ManagedPatron > ( dataSources : [ aboutDataSource , self . patronsDataSource ] )
2019-09-05 15:37:58 -07:00
dataSource . proxy = self
2019-08-28 11:13:22 -07:00
return dataSource
}
2022-04-18 15:31:29 -07:00
func makePatronsDataSource ( ) -> RSTFetchedResultsCollectionViewDataSource < ManagedPatron >
2019-09-05 15:37:58 -07:00
{
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 ( _ : ) ) ) ]
2022-04-14 17:39:43 -07:00
2022-04-18 15:31:29 -07:00
let patronsDataSource = RSTFetchedResultsCollectionViewDataSource < ManagedPatron > ( fetchRequest : fetchRequest , managedObjectContext : DatabaseManager . shared . viewContext )
2019-09-05 15:37:58 -07:00
patronsDataSource . cellConfigurationHandler = { ( cell , patron , indexPath ) in
let cell = cell as ! PatronCollectionViewCell
cell . textLabel . text = patron . name
}
return patronsDataSource
}
2019-08-28 11:13:22 -07:00
func update ( )
{
2019-09-07 15:34:07 -07:00
self . collectionView . reloadData ( )
}
func prepare ( _ headerView : AboutPatreonHeaderView )
{
headerView . layoutMargins = self . view . layoutMargins
headerView . supportButton . addTarget ( self , action : #selector ( PatreonViewController . openPatreonURL ( _ : ) ) , for : . primaryActionTriggered )
2023-06-13 23:14:01 -07:00
headerView . twitterButton . addTarget ( self , action : #selector ( PatreonViewController . openTwitterURL ( _ : ) ) , for : . primaryActionTriggered )
headerView . instagramButton . addTarget ( self , action : #selector ( PatreonViewController . openInstagramURL ( _ : ) ) , for : . primaryActionTriggered )
2019-09-12 13:08:38 -07:00
let defaultSupportButtonTitle = NSLocalizedString ( " Become a patron " , comment : " " )
let isPatronSupportButtonTitle = NSLocalizedString ( " View Patreon " , comment : " " )
let defaultText = NSLocalizedString ( " " "
2022-11-14 17:36:23 -07:00
Hello , thank you for using SideStore !
2019-09-12 13:08:38 -07: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 .
2019-09-12 13:08:38 -07:00
2022-11-14 17:36:23 -07:00
- SideTeam
2019-09-12 13:08:38 -07:00
" " " , comment: " " )
let isPatronText = NSLocalizedString ( " " "
Hey ,
2024-09-16 07:18:41 -04:00
You ’ re the best . Your account was linked successfully , so you now have access to any beta versions of our apps . You can find them all in the Browse tab .
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: " " )
2019-09-07 15:34:07 -07:00
if let account = DatabaseManager . shared . patreonAccount ( ) , PatreonAPI . shared . isAuthenticated
2019-08-28 11:13:22 -07:00
{
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-11-29 17:47:17 -06:00
if account . isAltStorePatron
2019-09-12 13:08:38 -07:00
{
headerView . supportButton . setTitle ( isPatronSupportButtonTitle , for : . normal )
let font = UIFont . systemFont ( ofSize : 16 )
let attributedText = NSMutableAttributedString ( string : isPatronText , 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 . supportButton . setTitle ( defaultSupportButtonTitle , for : . normal )
headerView . textView . text = defaultText
}
2019-08-28 11:13:22 -07:00
}
2022-11-14 17:36:23 -07:00
2019-08-28 11:13:22 -07:00
}
2019-09-05 15:37:58 -07:00
}
private extension PatreonViewController
{
@objc func fetchPatrons ( )
2019-08-28 11:13:22 -07:00
{
2022-04-14 17:39:43 -07:00
AppManager . shared . updatePatronsIfNeeded ( )
self . update ( )
2019-08-28 11:13:22 -07:00
}
2019-09-05 15:37:58 -07:00
@objc func openPatreonURL ( _ sender : UIButton )
{
2024-09-16 07:18:41 -04:00
let patreonURL = URL ( string : " https://www.patreon.com/SideStoreIO " ) !
2019-09-05 15:37:58 -07:00
let safariViewController = SFSafariViewController ( url : patreonURL )
safariViewController . preferredControlTintColor = self . view . tintColor
self . present ( safariViewController , animated : true , completion : nil )
2023-06-13 23:14:01 -07:00
}
@objc func openTwitterURL ( _ sender : UIButton )
{
2024-11-19 16:00:48 -05:00
let twitterURL = URL ( string : " https://twitter.com/sidestoreio " ) !
2023-06-13 23:14:01 -07:00
let safariViewController = SFSafariViewController ( url : twitterURL )
safariViewController . preferredControlTintColor = self . view . tintColor
self . present ( safariViewController , animated : true , completion : nil )
}
@objc func openInstagramURL ( _ sender : UIButton )
{
let twitterURL = URL ( string : " https://instagram.com/sidestore.io " ) !
let safariViewController = SFSafariViewController ( url : twitterURL )
safariViewController . preferredControlTintColor = self . view . tintColor
self . present ( safariViewController , animated : true , completion : nil )
2019-09-05 15:37:58 -07:00
}
2019-08-28 11:13:22 -07:00
@IBAction func authenticate ( _ sender : UIBarButtonItem )
{
2023-11-29 17:28:46 -06:00
PatreonAPI . shared . authenticate ( presentingViewController : self ) { ( result ) in
2019-08-28 11:13:22 -07:00
do
{
let account = try result . get ( )
try account . managedObjectContext ? . save ( )
2023-11-29 17:37:21 -06:00
// U p d a t e s o u r c e s t o s h o w a n y P a t r e o n - o n l y a p p s .
AppManager . shared . fetchSources { result in
do
{
let ( _ , context ) = try result . get ( )
try context . save ( )
}
catch
{
Logger . main . error ( " Failed to update sources after authenticating Patreon account. \( error . localizedDescription , privacy : . public ) " )
}
DispatchQueue . main . async {
self . update ( )
}
2019-08-28 11:13:22 -07:00
}
}
2023-11-29 17:28:46 -06:00
catch is CancellationError
2019-08-28 11:13:22 -07:00
{
2023-11-29 17:28:46 -06:00
// C l e a r i n - a p p b r o w s e r c a c h e i n c a s e t h e y a r e s i g n e d i n t o w r o n g a c c o u n t .
Task < Void , Never > . detached {
await PatreonAPI . shared . deleteAuthCookies ( )
}
2019-08-28 11:13:22 -07:00
}
catch
{
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
}
}
}
}
@IBAction func signOut ( _ sender : UIBarButtonItem )
{
2019-09-05 15:37:58 -07:00
func signOut ( )
{
PatreonAPI . shared . signOut { ( result ) in
do
{
try result . get ( )
DispatchQueue . main . async {
self . update ( )
}
}
catch
{
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
}
2024-02-15 14:43:34 -06:00
#if MARKETPLACE
let message = NSLocalizedString ( " You will no longer be able to install or update any apps that require pledges. " , comment : " " )
#else
let message = NSLocalizedString ( " You will no longer be able to install or refresh any apps that require pledges. " , comment : " " )
#endif
let alertController = UIAlertController ( title : NSLocalizedString ( " Are you sure you want to unlink your Patreon account? " , comment : " " ) , message : message , preferredStyle : . actionSheet )
2019-09-07 15:34:07 -07:00
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Unlink Patreon Account " , comment : " " ) , style : . destructive ) { _ in signOut ( ) } )
2019-09-05 15:37:58 -07:00
alertController . addAction ( . cancel )
self . present ( alertController , animated : true , completion : nil )
}
2022-04-14 17:39:43 -07:00
@objc func didUpdatePatrons ( _ notification : 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
}
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
2019-09-07 15:34:07 -07:00
self . prepare ( headerView )
2019-09-05 15:37:58 -07:00
return headerView
case . patrons :
if kind = = UICollectionView . elementKindSectionHeader
2019-08-28 11:13:22 -07:00
{
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
}
else
{
let footerView = collectionView . dequeueReusableSupplementaryView ( ofKind : kind , withReuseIdentifier : " PatronsFooter " , for : indexPath ) as ! PatronsFooterView
footerView . button . isIndicatingActivity = false
footerView . button . isHidden = false
2022-11-14 17:36:23 -07: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 )
2019-09-05 15:37:58 -07:00
2022-09-21 17:31:23 -05:00
switch AppManager . shared . updatePatronsResult
2019-09-05 15:37:58 -07:00
{
2022-09-21 17:31:23 -05:00
case . none : footerView . button . isIndicatingActivity = true
case . success ? : footerView . button . isHidden = true
case . failure ? :
#if DEBUG
let debug = true
#else
let debug = false
#endif
if self . patronsDataSource . itemCount = = 0 || debug
{
// 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 ) .
footerView . button . isHidden = false
footerView . button . setTitle ( NSLocalizedString ( " Error Loading Patrons " , comment : " " ) , for : . normal )
}
else
2022-04-14 17:39:43 -07:00
{
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
}
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
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 ] ) }
2019-09-07 15:34:07 -07:00
self . prepare ( self . prototypeAboutHeader )
2019-09-05 15:37:58 -07:00
let size = self . prototypeAboutHeader . systemLayoutSizeFitting ( UIView . layoutFittingCompressedSize )
return size
case . patrons :
2022-11-14 17:36:23 -07:00
return CGSize ( width : 0 , height : 0 )
2019-09-05 15:37:58 -07:00
}
}
func collectionView ( _ collectionView : UICollectionView , layout collectionViewLayout : UICollectionViewLayout , referenceSizeForFooterInSection section : Int ) -> CGSize
{
let section = Section . allCases [ section ]
switch section
{
case . about : return . zero
2024-11-10 02:54:18 +05:30
case . patrons : return CGSize ( width : 320 , height : 44 )
2019-09-05 15:37:58 -07:00
}
}
}