mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
[removeExtensions]: Refactored AppManager and moved out removeAppExtensions to fix subtle logic bugs
This commit is contained in:
@@ -64,6 +64,7 @@
|
|||||||
A859ED5C2D1EE827003DCC58 /* OpenSSL.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A859ED5B2D1EE80D003DCC58 /* OpenSSL.xcframework */; };
|
A859ED5C2D1EE827003DCC58 /* OpenSSL.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A859ED5B2D1EE80D003DCC58 /* OpenSSL.xcframework */; };
|
||||||
A859ED5D2D1EE827003DCC58 /* OpenSSL.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A859ED5B2D1EE80D003DCC58 /* OpenSSL.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
A859ED5D2D1EE827003DCC58 /* OpenSSL.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A859ED5B2D1EE80D003DCC58 /* OpenSSL.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
A868CFE42D31999A002F1201 /* SingletonGenericMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = A868CFE32D319988002F1201 /* SingletonGenericMap.swift */; };
|
A868CFE42D31999A002F1201 /* SingletonGenericMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = A868CFE32D319988002F1201 /* SingletonGenericMap.swift */; };
|
||||||
|
A8696EE42D34512C00E96389 /* RemoveAppExtensionsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8696EE32D34512C00E96389 /* RemoveAppExtensionsOperation.swift */; };
|
||||||
A8945AA62D059B6100D86CBE /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A8945AA52D059B6100D86CBE /* Roxas.framework */; };
|
A8945AA62D059B6100D86CBE /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A8945AA52D059B6100D86CBE /* Roxas.framework */; };
|
||||||
A8A543302D04F14400D72399 /* libfragmentzip.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A8A5432F2D04F0C100D72399 /* libfragmentzip.a */; };
|
A8A543302D04F14400D72399 /* libfragmentzip.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A8A5432F2D04F0C100D72399 /* libfragmentzip.a */; };
|
||||||
A8A853AF2D3065A300995795 /* ActiveAppsTimelineProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8A853AE2D3065A300995795 /* ActiveAppsTimelineProvider.swift */; };
|
A8A853AF2D3065A300995795 /* ActiveAppsTimelineProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8A853AE2D3065A300995795 /* ActiveAppsTimelineProvider.swift */; };
|
||||||
@@ -645,6 +646,7 @@
|
|||||||
A86202322D1F35640091187B /* AltStore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AltStore.xcconfig; sourceTree = "<group>"; };
|
A86202322D1F35640091187B /* AltStore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AltStore.xcconfig; sourceTree = "<group>"; };
|
||||||
A86202332D1F35640091187B /* AltStoreCore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AltStoreCore.xcconfig; sourceTree = "<group>"; };
|
A86202332D1F35640091187B /* AltStoreCore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AltStoreCore.xcconfig; sourceTree = "<group>"; };
|
||||||
A868CFE32D319988002F1201 /* SingletonGenericMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingletonGenericMap.swift; sourceTree = "<group>"; };
|
A868CFE32D319988002F1201 /* SingletonGenericMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingletonGenericMap.swift; sourceTree = "<group>"; };
|
||||||
|
A8696EE32D34512C00E96389 /* RemoveAppExtensionsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveAppExtensionsOperation.swift; sourceTree = "<group>"; };
|
||||||
A8945AA52D059B6100D86CBE /* Roxas.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
A8945AA52D059B6100D86CBE /* Roxas.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
A8A853AE2D3065A300995795 /* ActiveAppsTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveAppsTimelineProvider.swift; sourceTree = "<group>"; };
|
A8A853AE2D3065A300995795 /* ActiveAppsTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveAppsTimelineProvider.swift; sourceTree = "<group>"; };
|
||||||
A8AD35582D31BF29003A28B4 /* PageInfoManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageInfoManager.swift; sourceTree = "<group>"; };
|
A8AD35582D31BF29003A28B4 /* PageInfoManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageInfoManager.swift; sourceTree = "<group>"; };
|
||||||
@@ -2039,6 +2041,7 @@
|
|||||||
D5E1E7C028077DE90016FC96 /* UpdateKnownSourcesOperation.swift */,
|
D5E1E7C028077DE90016FC96 /* UpdateKnownSourcesOperation.swift */,
|
||||||
D5ACE84428E3B8450021CAB9 /* ClearAppCacheOperation.swift */,
|
D5ACE84428E3B8450021CAB9 /* ClearAppCacheOperation.swift */,
|
||||||
D561AF812B21669400BF59C6 /* VerifyAppPledgeOperation.swift */,
|
D561AF812B21669400BF59C6 /* VerifyAppPledgeOperation.swift */,
|
||||||
|
A8696EE32D34512C00E96389 /* RemoveAppExtensionsOperation.swift */,
|
||||||
BF7B44062725A4B8005288A4 /* Patch App */,
|
BF7B44062725A4B8005288A4 /* Patch App */,
|
||||||
);
|
);
|
||||||
path = Operations;
|
path = Operations;
|
||||||
@@ -2988,6 +2991,7 @@
|
|||||||
BFD2478F2284C8F900981D42 /* Button.swift in Sources */,
|
BFD2478F2284C8F900981D42 /* Button.swift in Sources */,
|
||||||
D5151BE12A90344300C96F28 /* RefreshAllAppsWidgetIntent.swift in Sources */,
|
D5151BE12A90344300C96F28 /* RefreshAllAppsWidgetIntent.swift in Sources */,
|
||||||
D513F6162A12CE4E0061EAA1 /* SourceError.swift in Sources */,
|
D513F6162A12CE4E0061EAA1 /* SourceError.swift in Sources */,
|
||||||
|
A8696EE42D34512C00E96389 /* RemoveAppExtensionsOperation.swift in Sources */,
|
||||||
BF56D2AC23DF8E170006506D /* FetchAppIDsOperation.swift in Sources */,
|
BF56D2AC23DF8E170006506D /* FetchAppIDsOperation.swift in Sources */,
|
||||||
BFC1F38D22AEE3A4003AC21A /* DownloadAppOperation.swift in Sources */,
|
BFC1F38D22AEE3A4003AC21A /* DownloadAppOperation.swift in Sources */,
|
||||||
BFE6073A231ADF82002B0E8E /* SettingsViewController.swift in Sources */,
|
BFE6073A231ADF82002B0E8E /* SettingsViewController.swift in Sources */,
|
||||||
|
|||||||
@@ -1234,123 +1234,6 @@ private extension AppManager
|
|||||||
return group
|
return group
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeAppExtensions(from application: ALTApplication, existingApp: InstalledApp?, extensions: Set<ALTApplication>, _ presentingViewController: UIViewController?, completion: @escaping (Result<Void, Error>) -> Void)
|
|
||||||
{
|
|
||||||
|
|
||||||
//App-Extensions: Ensure existing app's extensions and currently installing app's extensions must match
|
|
||||||
let existingAppEx: Set<InstalledExtension> = existingApp?.appExtensions ?? Set()
|
|
||||||
let currentAppEx: Set<ALTApplication> = application.appExtensions
|
|
||||||
|
|
||||||
let currentAppExNames = currentAppEx.map{ appEx in appEx.bundleIdentifier}
|
|
||||||
let existingAppExNames = existingAppEx.map{ appEx in appEx.bundleIdentifier}
|
|
||||||
|
|
||||||
let excessExtensions = currentAppEx.filter{
|
|
||||||
!(existingAppExNames.contains($0.bundleIdentifier))
|
|
||||||
}
|
|
||||||
|
|
||||||
let isMatching = (currentAppEx.count == existingAppEx.count) && excessExtensions.isEmpty
|
|
||||||
let diagnosticsMsg = "AppManager.removeAppExtensions: App Extensions in existingApp and currentApp are matching: \(isMatching)\n"
|
|
||||||
+ "AppManager.removeAppExtensions: existingAppEx: \(existingAppExNames); currentAppEx: \(String(describing: currentAppExNames))\n"
|
|
||||||
print(diagnosticsMsg)
|
|
||||||
|
|
||||||
// if background mode, then remove only the excess extensions
|
|
||||||
guard let presentingViewController: UIViewController = presentingViewController else {
|
|
||||||
// perform silent extensions cleanup for those that aren't already present in existing app
|
|
||||||
print("\n Performing background mode Extensions removal \n")
|
|
||||||
print("AppManager.removeAppExtensions: Excess Extensions: \(excessExtensions)")
|
|
||||||
|
|
||||||
do {
|
|
||||||
for appExtension in excessExtensions {
|
|
||||||
print("Deleting extension \(appExtension.bundleIdentifier)")
|
|
||||||
try FileManager.default.removeItem(at: appExtension.fileURL)
|
|
||||||
}
|
|
||||||
return completion(.success(()))
|
|
||||||
} catch {
|
|
||||||
return completion(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
guard !application.appExtensions.isEmpty else { return completion(.success(())) }
|
|
||||||
|
|
||||||
let firstSentence: String
|
|
||||||
|
|
||||||
if UserDefaults.standard.activeAppLimitIncludesExtensions
|
|
||||||
{
|
|
||||||
firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to 3 active apps and app extensions.", comment: "")
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to creating 10 App IDs per week.", comment: "")
|
|
||||||
}
|
|
||||||
|
|
||||||
let message = firstSentence + " " + NSLocalizedString("Would you like to remove this app's extensions so they don't count towards your limit? There are \(extensions.count) Extensions", comment: "")
|
|
||||||
|
|
||||||
let alertController = UIAlertController(title: NSLocalizedString("App Contains Extensions", comment: ""), message: message, preferredStyle: .alert)
|
|
||||||
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { (action) in
|
|
||||||
completion(.failure(OperationError.cancelled))
|
|
||||||
}))
|
|
||||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions", comment: ""), style: .default) { (action) in
|
|
||||||
completion(.success(()))
|
|
||||||
})
|
|
||||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove App Extensions", comment: ""), style: .destructive) { (action) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
for appExtension in application.appExtensions
|
|
||||||
{
|
|
||||||
print("Deleting extension \(appExtension.bundleIdentifier)")
|
|
||||||
try FileManager.default.removeItem(at: appExtension.fileURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
completion(.success(()))
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
completion(.failure(error))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Choose App Extensions", comment: ""), style: .default) { (action) in
|
|
||||||
let popoverContentController = AppExtensionViewHostingController(extensions: extensions) { (selection) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
for appExtension in selection
|
|
||||||
{
|
|
||||||
print("Deleting extension \(appExtension.bundleIdentifier)")
|
|
||||||
|
|
||||||
try FileManager.default.removeItem(at: appExtension.fileURL)
|
|
||||||
}
|
|
||||||
completion(.success(()))
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
completion(.failure(error))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let suiview = popoverContentController.view!
|
|
||||||
suiview.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
|
|
||||||
popoverContentController.modalPresentationStyle = .popover
|
|
||||||
|
|
||||||
if let popoverPresentationController = popoverContentController.popoverPresentationController {
|
|
||||||
popoverPresentationController.sourceView = presentingViewController.view
|
|
||||||
popoverPresentationController.sourceRect = CGRect(x: 50, y: 50, width: 4, height: 4)
|
|
||||||
popoverPresentationController.delegate = popoverContentController
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
presentingViewController.present(popoverContentController, animated: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
presentingViewController.present(alertController, animated: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func _install(_ app: AppProtocol,
|
private func _install(_ app: AppProtocol,
|
||||||
operation appOperation: AppOperation,
|
operation appOperation: AppOperation,
|
||||||
group: RefreshGroup,
|
group: RefreshGroup,
|
||||||
@@ -1460,52 +1343,18 @@ private extension AppManager
|
|||||||
verifyOperation.addDependency(downloadOperation)
|
verifyOperation.addDependency(downloadOperation)
|
||||||
|
|
||||||
/* Remove App Extensions */
|
/* Remove App Extensions */
|
||||||
|
let removeAppExtensionsOperation = RemoveAppExtensionsOperation(context: context, appInDatabase: app)
|
||||||
let removeAppExtensionsOperation = RSTAsyncBlockOperation { [weak self] (operation) in
|
removeAppExtensionsOperation.resultHandler = { (result) in
|
||||||
do
|
switch result
|
||||||
{
|
|
||||||
if let error = context.error
|
|
||||||
{
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
guard case .install = appOperation else {
|
|
||||||
operation.finish()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
guard let extensions = context.app?.appExtensions else {
|
|
||||||
throw OperationError.invalidParameters("AppManager._install.removeAppExtensionsOperation: context.app?.appExtensions is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let currentApp = context.app else {
|
|
||||||
throw OperationError.invalidParameters("AppManager._install.removeAppExtensionsOperation: context.app is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
self?.removeAppExtensions(from: currentApp,
|
|
||||||
existingApp: app as? InstalledApp,
|
|
||||||
extensions: extensions,
|
|
||||||
context.authenticatedContext.presentingViewController
|
|
||||||
) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(): break
|
|
||||||
case .failure(let error): context.error = error
|
|
||||||
}
|
|
||||||
operation.finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
{
|
||||||
|
case .failure(let error):
|
||||||
context.error = error
|
context.error = error
|
||||||
operation.finish()
|
case .success: break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
removeAppExtensionsOperation.addDependency(verifyOperation)
|
removeAppExtensionsOperation.addDependency(verifyOperation)
|
||||||
|
|
||||||
|
|
||||||
/* Refresh Anisette Data */
|
/* Refresh Anisette Data */
|
||||||
let refreshAnisetteDataOperation = FetchAnisetteDataOperation(context: group.context)
|
let refreshAnisetteDataOperation = FetchAnisetteDataOperation(context: group.context)
|
||||||
refreshAnisetteDataOperation.resultHandler = { (result) in
|
refreshAnisetteDataOperation.resultHandler = { (result) in
|
||||||
|
|||||||
182
AltStore/Operations/RemoveAppExtensionsOperation.swift
Normal file
182
AltStore/Operations/RemoveAppExtensionsOperation.swift
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
//
|
||||||
|
// RefreshAppOperation.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 2/27/20.
|
||||||
|
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
|
import Roxas
|
||||||
|
import AltSign
|
||||||
|
|
||||||
|
@objc(RemoveAppExtensionsOperation)
|
||||||
|
final class RemoveAppExtensionsOperation: ResultOperation<Void>
|
||||||
|
{
|
||||||
|
let context: AppOperationContext
|
||||||
|
let appInDatabase: AppProtocol
|
||||||
|
|
||||||
|
init(context: AppOperationContext, appInDatabase: AppProtocol)
|
||||||
|
{
|
||||||
|
self.context = context
|
||||||
|
self.appInDatabase = appInDatabase
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func main()
|
||||||
|
{
|
||||||
|
super.main()
|
||||||
|
|
||||||
|
if let error = self.context.error
|
||||||
|
{
|
||||||
|
self.finish(.failure(error))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let targetAppBundle = context.app else {
|
||||||
|
return self.finish(.failure(
|
||||||
|
OperationError.invalidParameters("RemoveAppExtensionsOperation: context.app is nil")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
self.removeAppExtensions(from: targetAppBundle,
|
||||||
|
appInDatabase: appInDatabase as? InstalledApp,
|
||||||
|
extensions: targetAppBundle.appExtensions,
|
||||||
|
context.authenticatedContext.presentingViewController)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static func removeExtensions(from extensions: Set<ALTApplication>) throws {
|
||||||
|
for appExtension in extensions {
|
||||||
|
print("Deleting extension \(appExtension.bundleIdentifier)")
|
||||||
|
try FileManager.default.removeItem(at: appExtension.fileURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private func removeAppExtensions(from targetAppBundle: ALTApplication,
|
||||||
|
appInDatabase: InstalledApp?,
|
||||||
|
extensions: Set<ALTApplication>,
|
||||||
|
_ presentingViewController: UIViewController?)
|
||||||
|
{
|
||||||
|
|
||||||
|
// target App Bundle doesn't contain extensions so don't bother
|
||||||
|
guard !targetAppBundle.appExtensions.isEmpty else {
|
||||||
|
return self.finish(.success(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
//App-Extensions: Ensure existing app's extensions in DB and currently installing app bundle's extensions must match
|
||||||
|
let existingAppEx: Set<InstalledExtension> = appInDatabase?.appExtensions ?? Set()
|
||||||
|
let targetAppEx: Set<ALTApplication> = targetAppBundle.appExtensions
|
||||||
|
|
||||||
|
let existingAppExNames = existingAppEx.map{ appEx in appEx.bundleIdentifier}
|
||||||
|
let targetAppExNames = targetAppEx.map{ appEx in appEx.bundleIdentifier}
|
||||||
|
|
||||||
|
let excessExtensionsInTargetApp = targetAppEx.filter{
|
||||||
|
!(existingAppExNames.contains($0.bundleIdentifier))
|
||||||
|
}
|
||||||
|
|
||||||
|
let necessaryExtensionsInExistingApp = existingAppEx.filter{
|
||||||
|
targetAppExNames.contains($0.bundleIdentifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
// always cleanup existing app (app-in-db) based on incoming app that is targeted for install
|
||||||
|
appInDatabase?.appExtensions = necessaryExtensionsInExistingApp
|
||||||
|
|
||||||
|
let isMatching = (targetAppEx.count == existingAppEx.count) && excessExtensionsInTargetApp.isEmpty
|
||||||
|
let diagnosticsMsg = "RemoveAppExtensionsOperation: App Extensions in existingApp and targetAppBundle are matching: \(isMatching)\n"
|
||||||
|
+ "RemoveAppExtensionsOperation: existingAppEx: \(existingAppExNames); targetAppBundleEx: \(String(describing: targetAppExNames))\n"
|
||||||
|
print(diagnosticsMsg)
|
||||||
|
|
||||||
|
// if background mode, then remove only the excess extensions
|
||||||
|
guard let presentingViewController: UIViewController = presentingViewController else {
|
||||||
|
// perform silent extensions cleanup for those that aren't already present in existing app
|
||||||
|
print("\n Performing background mode Extensions removal \n")
|
||||||
|
print("RemoveAppExtensionsOperation: Excess Extensions In TargetAppBundle: \(excessExtensionsInTargetApp)")
|
||||||
|
print("RemoveAppExtensionsOperation: Necessary Extensions In ExistingAppInDatabase: \(necessaryExtensionsInExistingApp)")
|
||||||
|
|
||||||
|
do {
|
||||||
|
try Self.removeExtensions(from: excessExtensionsInTargetApp)
|
||||||
|
return self.finish(.success(()))
|
||||||
|
} catch {
|
||||||
|
return self.finish(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let firstSentence: String
|
||||||
|
|
||||||
|
if UserDefaults.standard.activeAppLimitIncludesExtensions
|
||||||
|
{
|
||||||
|
firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to 3 active apps and app extensions.", comment: "")
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to creating 10 App IDs per week.", comment: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
let message = firstSentence + " " + NSLocalizedString("Would you like to remove this app's extensions so they don't count towards your limit? There are \(extensions.count) Extensions", comment: "")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let alertController = UIAlertController(title: NSLocalizedString("App Contains Extensions", comment: ""), message: message, preferredStyle: .alert)
|
||||||
|
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { (action) in
|
||||||
|
self.finish(.failure(OperationError.cancelled))
|
||||||
|
}))
|
||||||
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions", comment: ""), style: .default) { (action) in
|
||||||
|
self.finish(.success(()))
|
||||||
|
})
|
||||||
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove App Extensions", comment: ""), style: .destructive) { (action) in
|
||||||
|
do {
|
||||||
|
try Self.removeExtensions(from: targetAppBundle.appExtensions)
|
||||||
|
return self.finish(.success(()))
|
||||||
|
} catch {
|
||||||
|
return self.finish(.failure(error))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Choose App Extensions", comment: ""), style: .default) { (action) in
|
||||||
|
|
||||||
|
|
||||||
|
let popoverContentController = AppExtensionViewHostingController(extensions: extensions) { (selection) in
|
||||||
|
do {
|
||||||
|
try Self.removeExtensions(from: Set(selection))
|
||||||
|
return self.finish(.success(()))
|
||||||
|
} catch {
|
||||||
|
return self.finish(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let suiview = popoverContentController.view!
|
||||||
|
suiview.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
|
popoverContentController.modalPresentationStyle = .popover
|
||||||
|
|
||||||
|
if let popoverPresentationController = popoverContentController.popoverPresentationController {
|
||||||
|
popoverPresentationController.sourceView = presentingViewController.view
|
||||||
|
popoverPresentationController.sourceRect = CGRect(x: 50, y: 50, width: 4, height: 4)
|
||||||
|
popoverPresentationController.delegate = popoverContentController
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
presentingViewController.present(popoverContentController, animated: true)
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
self.finish(.failure(
|
||||||
|
OperationError.invalidParameters("RemoveAppExtensionsOperation: popoverContentController.popoverPresentationController is nil"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
presentingViewController.present(alertController, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user