2019-05-20 21:26:01 +02:00
//
// M y A p p s 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
//
// C r e a t e d b y R i l e y T e s t u t o n 5 / 9 / 1 9 .
// 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
import Roxas
2019-06-10 15:03:47 -07:00
import AltSign
2019-05-20 21:26:01 +02:00
class MyAppsViewController : UITableViewController
{
2019-06-06 12:56:13 -07:00
private var refreshErrors = [ String : Error ] ( )
2019-05-20 21:26:01 +02:00
private lazy var dataSource = self . makeDataSource ( )
private lazy var dateFormatter : DateFormatter = {
let dateFormatter = DateFormatter ( )
dateFormatter . dateStyle = . short
2019-06-05 11:03:49 -07:00
dateFormatter . timeStyle = . short
2019-05-20 21:26:01 +02:00
return dateFormatter
} ( )
2019-06-21 11:20:03 -07:00
private var refreshGroup : OperationGroup ?
2019-06-10 15:03:47 -07:00
@IBOutlet private var progressView : UIProgressView !
2019-05-20 21:26:01 +02:00
override func viewDidLoad ( )
{
super . viewDidLoad ( )
self . tableView . dataSource = self . dataSource
2019-06-06 12:56:13 -07:00
2019-06-10 15:03:47 -07:00
if let navigationBar = self . navigationController ? . navigationBar
{
self . progressView . translatesAutoresizingMaskIntoConstraints = false
navigationBar . addSubview ( self . progressView )
NSLayoutConstraint . activate ( [ self . progressView . widthAnchor . constraint ( equalTo : navigationBar . widthAnchor ) ,
self . progressView . bottomAnchor . constraint ( equalTo : navigationBar . bottomAnchor ) ] )
}
2019-06-06 12:56:13 -07:00
self . update ( )
}
override func viewWillAppear ( _ animated : Bool )
{
super . viewWillAppear ( animated )
self . update ( )
2019-05-20 21:26:01 +02:00
}
2019-05-20 21:40:04 +02:00
override func prepare ( for segue : UIStoryboardSegue , sender : Any ? )
{
guard segue . identifier = = " showAppDetail " else { return }
guard let cell = sender as ? UITableViewCell , let indexPath = self . tableView . indexPath ( for : cell ) else { return }
let installedApp = self . dataSource . item ( at : indexPath )
guard let app = installedApp . app else { return }
let appDetailViewController = segue . destination as ! AppDetailViewController
appDetailViewController . app = app
}
2019-05-20 21:26:01 +02:00
}
private extension MyAppsViewController
{
func makeDataSource ( ) -> RSTFetchedResultsTableViewDataSource < InstalledApp >
{
let fetchRequest = InstalledApp . fetchRequest ( ) as NSFetchRequest < InstalledApp >
fetchRequest . relationshipKeyPathsForPrefetching = [ # keyPath ( InstalledApp . app ) ]
fetchRequest . sortDescriptors = [ NSSortDescriptor ( keyPath : \ InstalledApp . app ? . name , ascending : true ) ]
fetchRequest . returnsObjectsAsFaults = false
let dataSource = RSTFetchedResultsTableViewDataSource ( fetchRequest : fetchRequest , managedObjectContext : DatabaseManager . shared . viewContext )
2019-05-20 21:36:39 +02:00
dataSource . proxy = self
2019-06-06 12:56:13 -07:00
dataSource . cellConfigurationHandler = { [ weak self ] ( cell , installedApp , indexPath ) in
2019-05-20 21:26:01 +02:00
guard let app = installedApp . app else { return }
2019-06-17 14:49:23 -07:00
cell . textLabel ? . text = app . name + " ( \( installedApp . version ) ) "
2019-05-20 21:26:01 +02:00
let detailText =
" " "
2019-06-06 12:56:13 -07:00
Expires : \ ( self ? . dateFormatter . string ( from : installedApp . expirationDate ) ? ? " - " )
2019-05-20 21:26:01 +02:00
" " "
2019-06-04 18:50:55 -07:00
cell . detailTextLabel ? . numberOfLines = 1
2019-05-20 21:26:01 +02:00
cell . detailTextLabel ? . text = detailText
2019-06-04 18:50:55 -07:00
cell . detailTextLabel ? . textColor = . red
2019-06-06 12:56:13 -07:00
if let _ = self ? . refreshErrors [ installedApp . bundleIdentifier ]
{
cell . accessoryType = . detailButton
cell . tintColor = . red
}
else
{
cell . accessoryType = . none
cell . tintColor = nil
}
2019-05-20 21:26:01 +02:00
}
return dataSource
}
2019-06-06 12:56:13 -07:00
func update ( )
{
self . navigationItem . rightBarButtonItem ? . isEnabled = ! ( self . dataSource . fetchedResultsController . fetchedObjects ? . isEmpty ? ? true )
self . tableView . reloadData ( )
}
}
private extension MyAppsViewController
{
@IBAction func refreshAllApps ( _ sender : UIBarButtonItem )
{
sender . isIndicatingActivity = true
2019-06-21 11:20:03 -07:00
let installedApps = InstalledApp . fetchAppsForRefreshingAll ( in : DatabaseManager . shared . viewContext )
2019-06-10 15:03:47 -07:00
2019-06-21 11:20:03 -07:00
self . refresh ( installedApps ) { ( result ) in
sender . isIndicatingActivity = false
}
}
func refresh ( _ installedApps : [ InstalledApp ] , completionHandler : @ escaping ( Result < [ String : Result < InstalledApp , Error > ] , Error > ) -> Void )
{
2019-06-25 14:35:00 -07:00
func refresh ( )
2019-06-21 11:20:03 -07:00
{
2019-06-25 14:35:00 -07:00
if self . refreshGroup = = nil
{
let toastView = RSTToastView ( text : " Refreshing... " , detailText : nil )
toastView . tintColor = . altPurple
toastView . activityIndicatorView . startAnimating ( )
toastView . show ( in : self . navigationController ? . view ? ? self . view )
}
let group = AppManager . shared . refresh ( installedApps , presentingViewController : self , group : self . refreshGroup )
group . completionHandler = { ( result ) in
DispatchQueue . main . async {
switch result
2019-06-06 12:56:13 -07:00
{
2019-06-25 14:35:00 -07:00
case . failure ( let error ) :
let toastView = RSTToastView ( text : error . localizedDescription , detailText : nil )
toastView . tintColor = . red
2019-06-06 12:56:13 -07:00
toastView . show ( in : self . navigationController ? . view ? ? self . view , duration : 2.0 )
2019-06-25 14:35:00 -07:00
self . refreshErrors = [ : ]
case . success ( let results ) :
let failures = results . compactMapValues { $0 . error }
if failures . isEmpty
2019-06-06 12:56:13 -07:00
{
2019-06-25 14:35:00 -07:00
let toastView = RSTToastView ( text : NSLocalizedString ( " Successfully refreshed apps! " , comment : " " ) , detailText : nil )
toastView . tintColor = . altPurple
toastView . show ( in : self . navigationController ? . view ? ? self . view , duration : 2.0 )
2019-06-06 12:56:13 -07:00
}
else
{
2019-06-25 14:35:00 -07:00
let localizedText : String
if failures . count = = 1
{
localizedText = String ( format : NSLocalizedString ( " Failed to refresh %@ app. " , comment : " " ) , NSNumber ( value : failures . count ) )
}
else
{
localizedText = String ( format : NSLocalizedString ( " Failed to refresh %@ apps. " , comment : " " ) , NSNumber ( value : failures . count ) )
}
let toastView = RSTToastView ( text : localizedText , detailText : nil )
toastView . tintColor = . red
toastView . show ( in : self . navigationController ? . view ? ? self . view , duration : 2.0 )
2019-06-06 12:56:13 -07:00
}
2019-06-25 14:35:00 -07:00
self . refreshErrors = failures
2019-06-06 12:56:13 -07:00
}
2019-06-25 14:35:00 -07:00
self . progressView . observedProgress = nil
self . progressView . progress = 0.0
self . update ( )
self . refreshGroup = nil
completionHandler ( result )
2019-06-06 12:56:13 -07:00
}
2019-06-10 15:03:47 -07:00
}
2019-06-25 14:35:00 -07:00
self . progressView . observedProgress = group . progress
self . refreshGroup = group
2019-06-10 15:03:47 -07:00
}
2019-06-25 14:35:00 -07:00
if installedApps . contains ( where : { $0 . app . identifier = = App . altstoreAppID } )
{
let alertController = UIAlertController ( title : NSLocalizedString ( " Refresh AltStore? " , comment : " " ) , message : NSLocalizedString ( " AltStore will quit when it is finished refreshing. " , comment : " " ) , preferredStyle : . alert )
alertController . addAction ( . cancel )
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Refresh " , comment : " " ) , style : . default ) { ( action ) in
refresh ( )
} )
self . present ( alertController , animated : true , completion : nil )
}
else
{
refresh ( )
}
2019-06-06 12:56:13 -07:00
}
2019-05-20 21:26:01 +02:00
}
2019-05-20 21:36:39 +02:00
extension MyAppsViewController
{
2019-06-05 11:03:49 -07:00
override func tableView ( _ tableView : UITableView , editActionsForRowAt indexPath : IndexPath ) -> [ UITableViewRowAction ] ?
2019-05-20 21:36:39 +02:00
{
2019-06-05 11:03:49 -07:00
let deleteAction = UITableViewRowAction ( style : . destructive , title : " Delete " ) { ( action , indexPath ) in
let installedApp = self . dataSource . item ( at : indexPath )
2019-05-20 21:36:39 +02:00
2019-06-05 11:03:49 -07:00
DatabaseManager . shared . persistentContainer . performBackgroundTask { ( context ) in
let installedApp = context . object ( with : installedApp . objectID ) as ! InstalledApp
context . delete ( installedApp )
do
{
try context . save ( )
}
catch
{
print ( " Failed to delete installed app. " , error )
}
2019-05-20 21:36:39 +02:00
}
2019-06-05 11:03:49 -07:00
}
let refreshAction = UITableViewRowAction ( style : . normal , title : " Refresh " ) { ( action , indexPath ) in
let installedApp = self . dataSource . item ( at : indexPath )
2019-06-21 11:20:03 -07:00
self . refresh ( [ installedApp ] ) { ( result ) in
print ( " Refreshed " , installedApp . app . identifier )
2019-05-20 21:36:39 +02:00
}
}
2019-06-05 11:03:49 -07:00
return [ deleteAction , refreshAction ]
}
override func tableView ( _ tableView : UITableView , commit editingStyle : UITableViewCell . EditingStyle , forRowAt indexPath : IndexPath )
{
2019-05-20 21:36:39 +02:00
}
2019-06-06 12:56:13 -07:00
override func tableView ( _ tableView : UITableView , accessoryButtonTappedForRowWith indexPath : IndexPath )
{
let installedApp = self . dataSource . item ( at : indexPath )
guard let error = self . refreshErrors [ installedApp . bundleIdentifier ] else { return }
let alertController = UIAlertController ( title : " Failed to Refresh \( installedApp . app . name ) " , message : error . localizedDescription , preferredStyle : . alert )
alertController . addAction ( . ok )
self . present ( alertController , animated : true , completion : nil )
}
2019-05-20 21:36:39 +02:00
}