From 39b60a07d9bfc55ea36d19de9e5ed39f50f90f73 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Sun, 17 May 2020 23:36:30 -0700 Subject: [PATCH] Removes active app extension limits on 13.5 or later --- .../Extensions/UserDefaults+AltStore.swift | 7 ++- AltStore/Model/InstalledApp.swift | 5 ++ AltStore/My Apps/MyAppsViewController.swift | 49 ++++++++++++++++--- AltStore/Operations/InstallAppOperation.swift | 6 +-- 4 files changed, 55 insertions(+), 12 deletions(-) diff --git a/AltStore/Extensions/UserDefaults+AltStore.swift b/AltStore/Extensions/UserDefaults+AltStore.swift index 2e04e5d5..e0e59f7e 100644 --- a/AltStore/Extensions/UserDefaults+AltStore.swift +++ b/AltStore/Extensions/UserDefaults+AltStore.swift @@ -23,6 +23,7 @@ extension UserDefaults @NSManaged var legacySideloadedApps: [String]? @NSManaged var isLegacyDeactivationSupported: Bool + @NSManaged var activeAppLimitIncludesExtensions: Bool var activeAppsLimit: Int? { get { @@ -43,9 +44,13 @@ extension UserDefaults func registerDefaults() { + let ios13_5 = OperatingSystemVersion(majorVersion: 13, minorVersion: 5, patchVersion: 0) + let activeAppLimitIncludesExtensions = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios13_5) + self.register(defaults: [ #keyPath(UserDefaults.isBackgroundRefreshEnabled): true, - #keyPath(UserDefaults.isLegacyDeactivationSupported): false + #keyPath(UserDefaults.isLegacyDeactivationSupported): false, + #keyPath(UserDefaults.activeAppLimitIncludesExtensions): activeAppLimitIncludesExtensions ]) } } diff --git a/AltStore/Model/InstalledApp.swift b/AltStore/Model/InstalledApp.swift index 36170e0c..decd28bd 100644 --- a/AltStore/Model/InstalledApp.swift +++ b/AltStore/Model/InstalledApp.swift @@ -56,6 +56,11 @@ class InstalledApp: NSManagedObject, InstalledAppProtocol return 1 + self.appExtensions.count } + var requiredActiveSlots: Int { + let requiredActiveSlots = UserDefaults.standard.activeAppLimitIncludesExtensions ? self.appIDCount : 1 + return requiredActiveSlots + } + private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) { super.init(entity: entity, insertInto: context) diff --git a/AltStore/My Apps/MyAppsViewController.swift b/AltStore/My Apps/MyAppsViewController.swift index 33473a1d..a74afe56 100644 --- a/AltStore/My Apps/MyAppsViewController.swift +++ b/AltStore/My Apps/MyAppsViewController.swift @@ -809,7 +809,18 @@ private extension MyAppsViewController @objc func presentInactiveAppsAlert() { - let alertController = UIAlertController(title: NSLocalizedString("What are inactive apps?", comment: ""), message: NSLocalizedString("Free developer accounts are limited to 3 apps and app extensions. Inactive apps don't count towards your total, but cannot be opened until activated.", comment: ""), preferredStyle: .alert) + let message: String + + if UserDefaults.standard.activeAppLimitIncludesExtensions + { + message = NSLocalizedString("Free developer accounts are limited to 3 apps and app extensions. Inactive apps don't count towards your total, but cannot be opened until activated.", comment: "") + } + else + { + message = NSLocalizedString("Free developer accounts are limited to 3 apps. Inactive apps are backed up and uninstalled so they don't count towards your total, but will be reinstalled with all their data when activated again.", comment: "") + } + + let alertController = UIAlertController(title: NSLocalizedString("What are inactive apps?", comment: ""), message: message, preferredStyle: .alert) alertController.addAction(.ok) self.present(alertController, animated: true, completion: nil) } @@ -929,12 +940,34 @@ private extension MyAppsViewController let activeApps = InstalledApp.fetchActiveApps(in: DatabaseManager.shared.viewContext) .filter { $0.bundleIdentifier != installedApp.bundleIdentifier } // Don't count app towards total if it matches activating app - let activeAppsCount = activeApps.map { $0.appIDCount }.reduce(0, +) + var title: String = NSLocalizedString("Cannot Activate More than 3 Apps", comment: "") + let message: String + if UserDefaults.standard.activeAppLimitIncludesExtensions + { + if installedApp.appExtensions.isEmpty + { + message = NSLocalizedString("Free developer accounts are limited to 3 active apps and app extensions. Please choose an app to deactivate.", comment: "") + } + else + { + title = NSLocalizedString("Cannot Activate More than 3 Apps and App Extensions", comment: "") + + let appExtensionText = installedApp.appExtensions.count == 1 ? NSLocalizedString("app extension", comment: "") : NSLocalizedString("app extensions", comment: "") + message = String(format: NSLocalizedString("Free developer accounts are limited to 3 active apps and app extensions, and “%@” contains %@ %@. Please choose an app to deactivate.", comment: ""), installedApp.name, NSNumber(value: installedApp.appExtensions.count), appExtensionText) + } + } + else + { + message = NSLocalizedString("Free developer accounts are limited to 3 active apps. Please choose an app to deactivate.", comment: "") + } + + let activeAppsCount = activeApps.map { $0.requiredActiveSlots }.reduce(0, +) + let availableActiveApps = max(activeAppsLimit - activeAppsCount, 0) - guard installedApp.appIDCount > availableActiveApps else { return completion(true) } + guard installedApp.requiredActiveSlots > availableActiveApps else { return completion(true) } - let alertController = UIAlertController(title: NSLocalizedString("Cannot Activate More than 3 Apps", comment: ""), message: NSLocalizedString("Free developer accounts are limited to 3 active apps and app extensions. Please choose an app to deactivate.", comment: ""), preferredStyle: .alert) + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style) { (action) in completion(false) }) @@ -942,8 +975,8 @@ private extension MyAppsViewController for app in activeApps where app.bundleIdentifier != StoreApp.altstoreAppID { alertController.addAction(UIAlertAction(title: app.name, style: .default) { (action) in - let availableActiveApps = availableActiveApps + app.appIDCount - if availableActiveApps >= installedApp.appIDCount + let availableActiveApps = availableActiveApps + app.requiredActiveSlots + if availableActiveApps >= installedApp.requiredActiveSlots { // There are enough slots now to activate the app, so pre-emptively // mark it as active to provide visual feedback sooner. @@ -1572,10 +1605,10 @@ extension MyAppsViewController: UICollectionViewDropDelegate return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) } - let activeAppsCount = (self.activeAppsDataSource.fetchedResultsController.fetchedObjects ?? []).map { $0.appIDCount }.reduce(0, +) + let activeAppsCount = (self.activeAppsDataSource.fetchedResultsController.fetchedObjects ?? []).map { $0.requiredActiveSlots }.reduce(0, +) let availableActiveApps = max(activeAppsLimit - activeAppsCount, 0) - if installedApp.appIDCount <= availableActiveApps + if installedApp.requiredActiveSlots <= availableActiveApps { // Enough active app slots, so no need to deactivate app first. return UICollectionViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath) diff --git a/AltStore/Operations/InstallAppOperation.swift b/AltStore/Operations/InstallAppOperation.swift index 0983f93c..52fdbabb 100644 --- a/AltStore/Operations/InstallAppOperation.swift +++ b/AltStore/Operations/InstallAppOperation.swift @@ -122,10 +122,10 @@ class InstallAppOperation: ResultOperation var activeApps = InstalledApp.fetch(fetchRequest, in: backgroundContext) if !activeApps.contains(installedApp) { - let availableActiveApps = max(sideloadedAppsLimit - activeApps.count, 0) - let requiredActiveAppSlots = 1 + installedExtensions.count // As of iOS 13.3.1, app extensions count as "apps" + let activeAppsCount = activeApps.map { $0.requiredActiveSlots }.reduce(0, +) - if requiredActiveAppSlots <= availableActiveApps + let availableActiveApps = max(sideloadedAppsLimit - activeAppsCount, 0) + if installedApp.requiredActiveSlots <= availableActiveApps { // This app has not been explicitly activated, but there are enough slots available, // so implicitly activate it.