From 19bf19350e1c0f4908cadda06a3e4d67543cb682 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Sat, 16 May 2020 15:34:10 -0700 Subject: [PATCH] Supports removing inactive apps from My Apps --- .../Extensions/UserDefaults+AltStore.swift | 7 +- AltStore/Managing Apps/AppManager.swift | 42 +++++++++ AltStore/My Apps/MyAppsViewController.swift | 88 ++++++++++++------- 3 files changed, 102 insertions(+), 35 deletions(-) diff --git a/AltStore/Extensions/UserDefaults+AltStore.swift b/AltStore/Extensions/UserDefaults+AltStore.swift index 1756348b..2e04e5d5 100644 --- a/AltStore/Extensions/UserDefaults+AltStore.swift +++ b/AltStore/Extensions/UserDefaults+AltStore.swift @@ -22,6 +22,8 @@ extension UserDefaults @NSManaged var legacySideloadedApps: [String]? + @NSManaged var isLegacyDeactivationSupported: Bool + var activeAppsLimit: Int? { get { return self._activeAppsLimit?.intValue @@ -41,6 +43,9 @@ extension UserDefaults func registerDefaults() { - self.register(defaults: [#keyPath(UserDefaults.isBackgroundRefreshEnabled): true]) + self.register(defaults: [ + #keyPath(UserDefaults.isBackgroundRefreshEnabled): true, + #keyPath(UserDefaults.isLegacyDeactivationSupported): false + ]) } } diff --git a/AltStore/Managing Apps/AppManager.swift b/AltStore/Managing Apps/AppManager.swift index 239de97c..c8688380 100644 --- a/AltStore/Managing Apps/AppManager.swift +++ b/AltStore/Managing Apps/AppManager.swift @@ -344,6 +344,48 @@ extension AppManager self.run([deactivateAppOperation], context: context, requiresSerialQueue: true) } + func remove(_ installedApp: InstalledApp, completionHandler: @escaping (Result) -> Void) + { + let authenticationContext = AuthenticatedOperationContext() + let appContext = InstallAppOperationContext(bundleIdentifier: installedApp.bundleIdentifier, authenticatedContext: authenticationContext) + appContext.installedApp = installedApp + + let removeAppOperation = RSTAsyncBlockOperation { (operation) in + DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in + let installedApp = context.object(with: installedApp.objectID) as! InstalledApp + context.delete(installedApp) + + do { try context.save() } + catch { appContext.error = error } + + operation.finish() + } + } + + let removeAppBackupOperation = RemoveAppBackupOperation(context: appContext) + removeAppBackupOperation.resultHandler = { (result) in + switch result + { + case .success: break + case .failure(let error): print("Failed to remove app backup.", error) + } + + // Throw the error from removeAppOperation, + // since that's the error we really care about. + if let error = appContext.error + { + completionHandler(.failure(error)) + } + else + { + completionHandler(.success(())) + } + } + removeAppBackupOperation.addDependency(removeAppOperation) + + self.run([removeAppOperation, removeAppBackupOperation], context: authenticationContext) + } + func installationProgress(for app: AppProtocol) -> Progress? { let progress = self.installationProgress[app.bundleIdentifier] diff --git a/AltStore/My Apps/MyAppsViewController.swift b/AltStore/My Apps/MyAppsViewController.swift index 38bff614..d5d6f987 100644 --- a/AltStore/My Apps/MyAppsViewController.swift +++ b/AltStore/My Apps/MyAppsViewController.swift @@ -960,15 +960,31 @@ private extension MyAppsViewController func remove(_ installedApp: InstalledApp) { - let alertController = UIAlertController(title: nil, message: NSLocalizedString("Removing a sideloaded app only removes it from AltStore. You must also delete it from the home screen to fully uninstall the app.", comment: ""), preferredStyle: .actionSheet) + let title = String(format: NSLocalizedString("Remove “%@” from AltStore?", comment: ""), installedApp.name) + let message: String + + if UserDefaults.standard.isLegacyDeactivationSupported + { + message = NSLocalizedString("You must also delete it from the home screen to fully uninstall the app.", comment: "") + } + else + { + message = NSLocalizedString("This will also erase all backup data for this app.", comment: "") + } + + let alertController = UIAlertController(title: title, message: message, preferredStyle: .actionSheet) alertController.addAction(.cancel) alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove", comment: ""), style: .destructive, handler: { (action) in - 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 remove sideloaded app.", error) } + AppManager.shared.remove(installedApp) { (result) in + switch result + { + case .success: break + case .failure(let error): + DispatchQueue.main.async { + let toastView = ToastView(error: error) + toastView.show(in: self) + } + } } })) @@ -1161,39 +1177,43 @@ extension MyAppsViewController self.remove(installedApp) } - if installedApp.bundleIdentifier == StoreApp.altstoreAppID + guard installedApp.bundleIdentifier != StoreApp.altstoreAppID else { + return [refreshAction] + } + + if installedApp.isActive { - actions = [refreshAction] + actions.append(refreshAction) + actions.append(deactivateAction) } else { - if installedApp.isActive - { - if UserDefaults.standard.activeAppsLimit != nil - { - actions = [refreshAction, deactivateAction] - } - else - { - actions = [refreshAction] - } - } - else - { - actions.append(activateAction) - } - - #if DEBUG - actions.append(removeAction) - #else - if (UserDefaults.standard.legacySideloadedApps ?? []).contains(installedApp.bundleIdentifier) - { - // Only display option for legacy sideloaded apps. - actions.append(removeAction) - } - #endif + actions.append(activateAction) } + #if DEBUG + + if installedApp.bundleIdentifier != StoreApp.altstoreAppID + { + actions.append(removeAction) + } + + #else + + if (UserDefaults.standard.legacySideloadedApps ?? []).contains(installedApp.bundleIdentifier) + { + // Legacy sideloaded app, so can't detect if it's deleted. + actions.append(removeAction) + } + else if !UserDefaults.standard.isLegacyDeactivationSupported && !installedApp.isActive + { + // Inactive apps are actually deleted, so we need another way + // for user to remove them from AltStore. + actions.append(removeAction) + } + + #endif + return actions }