Limits installed Patreon apps that no longer have active pledge

Patreon apps with inactive pledges still support these actions:
* Backed up
* Deactivated
* Export backup
This commit is contained in:
Riley Testut
2023-11-29 18:24:33 -06:00
parent 2d267a1e99
commit d9ebd21541
2 changed files with 164 additions and 109 deletions

View File

@@ -116,6 +116,9 @@ class MyAppsViewController: UICollectionViewController, PeekPopPreviewing
{ {
super.viewIsAppearing(animated) super.viewIsAppearing(animated)
// Ensure the button for each app reflects correct Patreon status.
self.collectionView.reloadData()
self.update() self.update()
self.fetchAppIDs() self.fetchAppIDs()
@@ -357,6 +360,17 @@ private extension MyAppsViewController
cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal) cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal)
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Refresh %@", comment: ""), installedApp.name) cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Refresh %@", comment: ""), installedApp.name)
if let storeApp = installedApp.storeApp, storeApp.isPledgeRequired, !storeApp.isPledged
{
cell.bannerView.button.isEnabled = false
cell.bannerView.button.alpha = 0.5
}
else
{
cell.bannerView.button.isEnabled = true
cell.bannerView.button.alpha = 1.0
}
cell.bannerView.accessibilityLabel? += ". " + String(format: NSLocalizedString("Expires in %@", comment: ""), numberOfDaysText) cell.bannerView.accessibilityLabel? += ". " + String(format: NSLocalizedString("Expires in %@", comment: ""), numberOfDaysText)
// Make sure refresh button is correct size. // Make sure refresh button is correct size.
@@ -438,6 +452,17 @@ private extension MyAppsViewController
cell.bannerView.button.addTarget(self, action: #selector(MyAppsViewController.activateApp(_:)), for: .primaryActionTriggered) cell.bannerView.button.addTarget(self, action: #selector(MyAppsViewController.activateApp(_:)), for: .primaryActionTriggered)
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Activate %@", comment: ""), installedApp.name) cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Activate %@", comment: ""), installedApp.name)
if let storeApp = installedApp.storeApp, storeApp.isPledgeRequired, !storeApp.isPledged
{
cell.bannerView.button.isEnabled = false
cell.bannerView.button.alpha = 0.5
}
else
{
cell.bannerView.button.isEnabled = true
cell.bannerView.button.alpha = 1.0
}
// Make sure refresh button is correct size. // Make sure refresh button is correct size.
cell.layoutIfNeeded() cell.layoutIfNeeded()
@@ -1676,7 +1701,7 @@ extension MyAppsViewController
extension MyAppsViewController extension MyAppsViewController
{ {
private func actions(for installedApp: InstalledApp) -> [UIMenuElement] private func contextMenu(for installedApp: InstalledApp) -> UIMenu
{ {
var actions = [UIMenuElement]() var actions = [UIMenuElement]()
@@ -1734,14 +1759,16 @@ extension MyAppsViewController
let changeIconMenu = UIMenu(title: NSLocalizedString("Change Icon", comment: ""), image: UIImage(systemName: "photo"), children: changeIconActions) let changeIconMenu = UIMenu(title: NSLocalizedString("Change Icon", comment: ""), image: UIImage(systemName: "photo"), children: changeIconActions)
guard installedApp.bundleIdentifier != StoreApp.altstoreAppID else { if installedApp.bundleIdentifier == StoreApp.altstoreAppID
{
#if BETA #if BETA
return [refreshAction, changeIconMenu] actions = [refreshAction, changeIconMenu]
#else #else
return [refreshAction] actions = [refreshAction]
#endif #endif
} }
else
{
if installedApp.isActive if installedApp.isActive
{ {
actions.append(openMenu) actions.append(openMenu)
@@ -1829,8 +1856,31 @@ extension MyAppsViewController
} }
#endif #endif
}
return actions var title: String?
if let storeApp = installedApp.storeApp, storeApp.isPledgeRequired, !storeApp.isPledged
{
let error = OperationError.pledgeInactive(appName: installedApp.name)
title = error.localizedDescription
// Limit options for apps requiring pledges that we are no longer pledged to.
actions = actions.filter {
$0 == openMenu ||
$0 == deactivateAction ||
$0 == removeAction ||
$0 == backupAction ||
$0 == exportBackupAction ||
($0 == refreshAction && storeApp.bundleIdentifier == StoreApp.altstoreAppID) // Always show refresh option for AltStore so the menu will be shown.
}
// Disable refresh action for AltStore.
refreshAction.attributes = .disabled
}
let menu = UIMenu(title: title ?? "", children: actions)
return menu
} }
override func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? override func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration?
@@ -1843,9 +1893,7 @@ extension MyAppsViewController
let installedApp = self.dataSource.item(at: indexPath) let installedApp = self.dataSource.item(at: indexPath)
return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: nil) { (suggestedActions) -> UIMenu? in return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: nil) { (suggestedActions) -> UIMenu? in
let actions = self.actions(for: installedApp) let menu = self.contextMenu(for: installedApp)
let menu = UIMenu(title: "", children: actions)
return menu return menu
} }
} }

View File

@@ -233,17 +233,12 @@ public extension InstalledApp
class func fetchAppsForRefreshingAll(in context: NSManagedObjectContext) -> [InstalledApp] class func fetchAppsForRefreshingAll(in context: NSManagedObjectContext) -> [InstalledApp]
{ {
var predicate = NSPredicate(format: "%K == YES AND %K != %@", #keyPath(InstalledApp.isActive), #keyPath(InstalledApp.bundleIdentifier), StoreApp.altstoreAppID) let predicate = NSPredicate(format: "(%K == YES AND %K != %@) AND (%K == nil OR %K == NO OR %K == YES)",
#keyPath(InstalledApp.isActive),
if let patreonAccount = DatabaseManager.shared.patreonAccount(in: context), patreonAccount.isPatron, PatreonAPI.shared.isAuthenticated #keyPath(InstalledApp.bundleIdentifier), StoreApp.altstoreAppID,
{ #keyPath(InstalledApp.storeApp),
// No additional predicate #keyPath(InstalledApp.storeApp.isPledgeRequired),
} #keyPath(InstalledApp.storeApp.isPledged))
else
{
predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate,
NSPredicate(format: "%K == nil OR %K == NO", #keyPath(InstalledApp.storeApp), #keyPath(InstalledApp.storeApp.isBeta))])
}
var installedApps = InstalledApp.all(satisfying: predicate, var installedApps = InstalledApp.all(satisfying: predicate,
sortedBy: [NSSortDescriptor(keyPath: \InstalledApp.expirationDate, ascending: true)], sortedBy: [NSSortDescriptor(keyPath: \InstalledApp.expirationDate, ascending: true)],
@@ -252,8 +247,18 @@ public extension InstalledApp
if let altStoreApp = InstalledApp.fetchAltStore(in: context) if let altStoreApp = InstalledApp.fetchAltStore(in: context)
{ {
// Refresh AltStore last since it causes app to quit. // Refresh AltStore last since it causes app to quit.
if let storeApp = altStoreApp.storeApp, !storeApp.isPledgeRequired || storeApp.isPledged
{
// Only add AltStore if it's the public version OR if it's the beta and we're pledged to it.
installedApps.append(altStoreApp) installedApps.append(altStoreApp)
} }
else
{
// No associated storeApp, so add it just to be safe.
installedApps.append(altStoreApp)
}
}
return installedApps return installedApps
} }
@@ -263,20 +268,14 @@ public extension InstalledApp
// Date 6 hours before now. // Date 6 hours before now.
let date = Date().addingTimeInterval(-1 * 6 * 60 * 60) let date = Date().addingTimeInterval(-1 * 6 * 60 * 60)
var predicate = NSPredicate(format: "(%K == YES) AND (%K < %@) AND (%K != %@)", let predicate = NSPredicate(format: "(%K == YES) AND (%K < %@) AND (%K != %@) AND (%K == nil OR %K == NO OR %K == YES)",
#keyPath(InstalledApp.isActive), #keyPath(InstalledApp.isActive),
#keyPath(InstalledApp.refreshedDate), date as NSDate, #keyPath(InstalledApp.refreshedDate), date as NSDate,
#keyPath(InstalledApp.bundleIdentifier), StoreApp.altstoreAppID) #keyPath(InstalledApp.bundleIdentifier), StoreApp.altstoreAppID,
#keyPath(InstalledApp.storeApp),
if let patreonAccount = DatabaseManager.shared.patreonAccount(in: context), patreonAccount.isPatron, PatreonAPI.shared.isAuthenticated #keyPath(InstalledApp.storeApp.isPledgeRequired),
{ #keyPath(InstalledApp.storeApp.isPledged)
// No additional predicate )
}
else
{
predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate,
NSPredicate(format: "%K == nil OR %K == NO", #keyPath(InstalledApp.storeApp), #keyPath(InstalledApp.storeApp.isBeta))])
}
var installedApps = InstalledApp.all(satisfying: predicate, var installedApps = InstalledApp.all(satisfying: predicate,
sortedBy: [NSSortDescriptor(keyPath: \InstalledApp.expirationDate, ascending: true)], sortedBy: [NSSortDescriptor(keyPath: \InstalledApp.expirationDate, ascending: true)],
@@ -284,9 +283,17 @@ public extension InstalledApp
if let altStoreApp = InstalledApp.fetchAltStore(in: context), altStoreApp.refreshedDate < date if let altStoreApp = InstalledApp.fetchAltStore(in: context), altStoreApp.refreshedDate < date
{ {
// Refresh AltStore last since it may cause app to quit. if let storeApp = altStoreApp.storeApp, !storeApp.isPledgeRequired || storeApp.isPledged
{
// Only add AltStore if it's the public version OR if it's the beta and we're pledged to it.
installedApps.append(altStoreApp) installedApps.append(altStoreApp)
} }
else
{
// No associated storeApp, so add it just to be safe.
installedApps.append(altStoreApp)
}
}
return installedApps return installedApps
} }