Files
SideStore/AltStore/My Apps/MyAppsViewController.swift

2063 lines
85 KiB
Swift
Raw Normal View History

//
// MyAppsViewController.swift
// AltStore
//
// Created by Riley Testut on 7/16/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
import MobileCoreServices
import Intents
import Combine
import UniformTypeIdentifiers
import AltStoreCore
2019-07-28 15:51:36 -07:00
import AltSign
import Roxas
import minimuxer
2019-07-28 15:51:36 -07:00
import Nuke
private let maximumCollapsedUpdatesCount = 2
extension MyAppsViewController
{
private enum Section: Int, CaseIterable
{
case noUpdates
case updates
case activeApps
case inactiveApps
}
}
final class MyAppsViewController: UICollectionViewController
{
private let coordinator = NSFileCoordinator()
private let operationQueue = OperationQueue()
private lazy var dataSource = self.makeDataSource()
private lazy var noUpdatesDataSource = self.makeNoUpdatesDataSource()
private lazy var updatesDataSource = self.makeUpdatesDataSource()
private lazy var activeAppsDataSource = self.makeActiveAppsDataSource()
private lazy var inactiveAppsDataSource = self.makeInactiveAppsDataSource()
private var prototypeUpdateCell: UpdateCollectionViewCell!
2019-07-28 15:51:36 -07:00
private var sideloadingProgressView: UIProgressView!
// State
private var isUpdateSectionCollapsed = true
private var expandedAppUpdates = Set<String>()
private var isRefreshingAllApps = false
private var refreshGroup: RefreshGroup?
2019-07-28 15:51:36 -07:00
private var sideloadingProgress: Progress?
private var dropDestinationIndexPath: IndexPath?
private var _imagePickerInstalledApp: InstalledApp?
// Cache
private var cachedUpdateSizes = [String: CGSize]()
private lazy var dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none
return dateFormatter
}()
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
NotificationCenter.default.addObserver(self, selector: #selector(MyAppsViewController.didFetchSource(_:)), name: AppManager.didFetchSourceNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(MyAppsViewController.importApp(_:)), name: AppDelegate.importAppDeepLinkNotification, object: nil)
}
override func viewDidLoad()
{
super.viewDidLoad()
// Allows us to intercept delegate callbacks.
self.updatesDataSource.fetchedResultsController.delegate = self
self.collectionView.dataSource = self.dataSource
self.collectionView.prefetchDataSource = self.dataSource
self.collectionView.dragDelegate = self
self.collectionView.dropDelegate = self
self.collectionView.dragInteractionEnabled = true
self.prototypeUpdateCell = UpdateCollectionViewCell.instantiate(with: UpdateCollectionViewCell.nib!)
self.prototypeUpdateCell.contentView.translatesAutoresizingMaskIntoConstraints = false
self.collectionView.register(UpdateCollectionViewCell.nib, forCellWithReuseIdentifier: "UpdateCell")
self.collectionView.register(UpdatesCollectionHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "UpdatesHeader")
self.collectionView.register(InstalledAppsCollectionHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "ActiveAppsHeader")
self.collectionView.register(InstalledAppsCollectionHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "InactiveAppsHeader")
2019-07-28 15:51:36 -07:00
self.sideloadingProgressView = UIProgressView(progressViewStyle: .bar)
self.sideloadingProgressView.translatesAutoresizingMaskIntoConstraints = false
2019-09-19 11:29:10 -07:00
self.sideloadingProgressView.progressTintColor = .altPrimary
2019-07-28 15:51:36 -07:00
self.sideloadingProgressView.progress = 0
if let navigationBar = self.navigationController?.navigationBar
{
navigationBar.addSubview(self.sideloadingProgressView)
NSLayoutConstraint.activate([self.sideloadingProgressView.leadingAnchor.constraint(equalTo: navigationBar.leadingAnchor),
self.sideloadingProgressView.trailingAnchor.constraint(equalTo: navigationBar.trailingAnchor),
self.sideloadingProgressView.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor)])
}
if #available(iOS 13, *) {}
else
{
self.registerForPreviewing(with: self, sourceView: self.collectionView)
}
}
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
self.updateDataSource()
self.fetchAppIDs()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
2019-09-12 13:51:03 -07:00
guard let identifier = segue.identifier else { return }
2019-09-12 13:51:03 -07:00
switch identifier
{
case "showApp", "showUpdate":
guard let cell = sender as? UICollectionViewCell, let indexPath = self.collectionView.indexPath(for: cell) else { return }
let installedApp = self.dataSource.item(at: indexPath)
let appViewController = segue.destination as! AppViewController
appViewController.app = installedApp.storeApp
default: break
}
}
2019-07-28 15:51:36 -07:00
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool
{
guard identifier == "showApp" else { return true }
guard let cell = sender as? UICollectionViewCell, let indexPath = self.collectionView.indexPath(for: cell) else { return true }
let installedApp = self.dataSource.item(at: indexPath)
return !installedApp.isSideloaded
}
@IBAction func unwindToMyAppsViewController(_ segue: UIStoryboardSegue)
{
}
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
var minimuxerStatus: Bool {
guard minimuxer.ready() else {
ToastView(error: (OperationError.noWiFi as NSError).withLocalizedTitle("No WiFi or VPN!")).show(in: self)
return false
}
return true
}
}
private extension MyAppsViewController
{
func makeDataSource() -> RSTCompositeCollectionViewPrefetchingDataSource<InstalledApp, UIImage>
{
let dataSource = RSTCompositeCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(dataSources: [self.noUpdatesDataSource, self.updatesDataSource, self.activeAppsDataSource, self.inactiveAppsDataSource])
dataSource.proxy = self
return dataSource
}
func makeNoUpdatesDataSource() -> RSTDynamicCollectionViewPrefetchingDataSource<InstalledApp, UIImage>
{
let dynamicDataSource = RSTDynamicCollectionViewPrefetchingDataSource<InstalledApp, UIImage>()
dynamicDataSource.numberOfSectionsHandler = { 1 }
dynamicDataSource.numberOfItemsHandler = { _ in self.updatesDataSource.itemCount == 0 ? 1 : 0 }
dynamicDataSource.cellIdentifierHandler = { _ in "NoUpdatesCell" }
dynamicDataSource.cellConfigurationHandler = { (cell, _, indexPath) in
2019-10-23 13:32:17 -07:00
let cell = cell as! NoUpdatesCollectionViewCell
cell.layoutMargins.left = self.view.layoutMargins.left
cell.layoutMargins.right = self.view.layoutMargins.right
cell.blurView.layer.cornerRadius = 20
cell.blurView.layer.masksToBounds = true
cell.blurView.backgroundColor = .altPrimary
}
return dynamicDataSource
}
func makeUpdatesDataSource() -> RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>
{
let fetchRequest = InstalledApp.updatesFetchRequest()
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \InstalledApp.storeApp?.latestSupportedVersion?.date, ascending: false),
NSSortDescriptor(keyPath: \InstalledApp.name, ascending: true)]
fetchRequest.returnsObjectsAsFaults = false
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
dataSource.liveFetchLimit = maximumCollapsedUpdatesCount
dataSource.cellIdentifierHandler = { _ in "UpdateCell" }
2019-09-12 13:51:03 -07:00
dataSource.cellConfigurationHandler = { [weak self] (cell, installedApp, indexPath) in
guard let self = self else { return }
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
guard let app = installedApp.storeApp, let latestSupportedVersion = app.latestSupportedVersion else { return }
let cell = cell as! UpdateCollectionViewCell
2019-10-23 13:32:17 -07:00
cell.layoutMargins.left = self.view.layoutMargins.left
cell.layoutMargins.right = self.view.layoutMargins.right
2019-09-19 11:29:10 -07:00
cell.tintColor = app.tintColor ?? .altPrimary
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
cell.versionDescriptionTextView.text = latestSupportedVersion.localizedDescription
2019-10-23 13:32:17 -07:00
cell.bannerView.iconImageView.image = nil
cell.bannerView.iconImageView.isIndicatingActivity = true
2020-08-27 15:23:21 -07:00
cell.bannerView.configure(for: app)
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
let versionDate = Date().relativeDateString(since: latestSupportedVersion.date, dateFormatter: self.dateFormatter)
2020-08-27 15:23:21 -07:00
cell.bannerView.subtitleLabel.text = versionDate
let appName: String
if app.isBeta
{
appName = String(format: NSLocalizedString("%@ beta", comment: ""), app.name)
}
else
{
appName = app.name
}
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
cell.bannerView.accessibilityLabel = String(format: NSLocalizedString("%@ %@ update. Released on %@.", comment: ""), appName, latestSupportedVersion.version, versionDate)
2019-10-23 13:32:17 -07:00
cell.bannerView.button.isIndicatingActivity = false
cell.bannerView.button.addTarget(self, action: #selector(MyAppsViewController.updateApp(_:)), for: .primaryActionTriggered)
2020-08-27 15:23:21 -07:00
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Update %@", comment: ""), installedApp.name)
if self.expandedAppUpdates.contains(app.bundleIdentifier)
{
cell.mode = .expanded
}
else
{
cell.mode = .collapsed
}
cell.versionDescriptionTextView.moreButton.addTarget(self, action: #selector(MyAppsViewController.toggleUpdateCellMode(_:)), for: .primaryActionTriggered)
let progress = AppManager.shared.installationProgress(for: app)
2019-10-23 13:32:17 -07:00
cell.bannerView.button.progress = progress
cell.setNeedsLayout()
}
dataSource.prefetchHandler = { (installedApp, indexPath, completionHandler) in
guard let iconURL = installedApp.storeApp?.iconURL else { return nil }
return RSTAsyncBlockOperation() { (operation) in
ImagePipeline.shared.loadImage(with: iconURL, progress: nil, completion: { (response, error) in
guard !operation.isCancelled else { return operation.finish() }
if let image = response?.image
{
completionHandler(image, nil)
}
else
{
completionHandler(nil, error)
}
})
}
}
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
let cell = cell as! UpdateCollectionViewCell
2019-10-23 13:32:17 -07:00
cell.bannerView.iconImageView.isIndicatingActivity = false
cell.bannerView.iconImageView.image = image
if let error = error
{
print("Error loading image:", error)
}
}
return dataSource
}
func makeActiveAppsDataSource() -> RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>
{
let fetchRequest = InstalledApp.activeAppsFetchRequest()
fetchRequest.relationshipKeyPathsForPrefetching = [#keyPath(InstalledApp.storeApp)]
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \InstalledApp.expirationDate, ascending: true),
NSSortDescriptor(keyPath: \InstalledApp.refreshedDate, ascending: false),
NSSortDescriptor(keyPath: \InstalledApp.name, ascending: true)]
fetchRequest.returnsObjectsAsFaults = false
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
dataSource.cellIdentifierHandler = { _ in "AppCell" }
dataSource.cellConfigurationHandler = { (cell, installedApp, indexPath) in
2019-09-19 11:29:10 -07:00
let tintColor = installedApp.storeApp?.tintColor ?? .altPrimary
let cell = cell as! InstalledAppCollectionViewCell
2019-10-23 13:32:17 -07:00
cell.layoutMargins.left = self.view.layoutMargins.left
cell.layoutMargins.right = self.view.layoutMargins.right
cell.tintColor = tintColor
2019-07-28 15:51:36 -07:00
cell.deactivateBadge?.isHidden = false
if let dropIndexPath = self.dropDestinationIndexPath, dropIndexPath.section == Section.activeApps.rawValue && dropIndexPath.item == indexPath.item
{
cell.bannerView.alpha = 0.4
cell.deactivateBadge?.alpha = 1.0
cell.deactivateBadge?.transform = .identity
}
else
{
cell.bannerView.alpha = 1.0
cell.deactivateBadge?.alpha = 0.0
cell.deactivateBadge?.transform = CGAffineTransform.identity.scaledBy(x: 0.33, y: 0.33)
}
2020-08-27 15:23:21 -07:00
cell.bannerView.configure(for: installedApp)
2019-10-23 13:32:17 -07:00
cell.bannerView.iconImageView.isIndicatingActivity = true
cell.bannerView.buttonLabel.isHidden = false
cell.bannerView.buttonLabel.text = NSLocalizedString("Expires in", comment: "")
2019-10-23 13:32:17 -07:00
cell.bannerView.button.isIndicatingActivity = false
cell.bannerView.button.removeTarget(self, action: nil, for: .primaryActionTriggered)
2019-10-23 13:32:17 -07:00
cell.bannerView.button.addTarget(self, action: #selector(MyAppsViewController.refreshApp(_:)), for: .primaryActionTriggered)
let currentDate = Date()
let numberOfDays = installedApp.expirationDate.numberOfCalendarDays(since: currentDate)
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.includesApproximationPhrase = false
formatter.includesTimeRemainingPhrase = false
formatter.allowedUnits = [.day, .hour, .minute]
formatter.maximumUnitCount = 1
cell.bannerView.button.setTitle(formatter.string(from: currentDate, to: installedApp.expirationDate)?.uppercased(), for: .normal)
2020-08-27 15:23:21 -07:00
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Refresh %@", comment: ""), installedApp.name)
formatter.includesTimeRemainingPhrase = true
cell.bannerView.accessibilityLabel? += ". " + (formatter.string(from: currentDate, to: installedApp.expirationDate) ?? NSLocalizedString("Unknown", comment: "")) + " "
// Make sure refresh button is correct size.
cell.layoutIfNeeded()
switch numberOfDays
{
2019-10-23 13:32:17 -07:00
case 2...3: cell.bannerView.button.tintColor = .refreshOrange
case 4...5: cell.bannerView.button.tintColor = .refreshYellow
case 6...: cell.bannerView.button.tintColor = .refreshGreen
default: cell.bannerView.button.tintColor = .refreshRed
}
if let progress = AppManager.shared.refreshProgress(for: installedApp), progress.fractionCompleted < 1.0
{
2019-10-23 13:32:17 -07:00
cell.bannerView.button.progress = progress
}
else
{
2019-10-23 13:32:17 -07:00
cell.bannerView.button.progress = nil
}
}
2019-07-28 15:51:36 -07:00
dataSource.prefetchHandler = { (item, indexPath, completion) in
RSTAsyncBlockOperation { (operation) in
item.managedObjectContext?.perform {
item.loadIcon { (result) in
switch result
{
case .failure(let error): completion(nil, error)
case .success(let image): completion(image, nil)
}
}
2019-07-28 15:51:36 -07:00
}
}
}
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
let cell = cell as! InstalledAppCollectionViewCell
2019-10-23 13:32:17 -07:00
cell.bannerView.iconImageView.image = image
cell.bannerView.iconImageView.isIndicatingActivity = false
2019-07-28 15:51:36 -07:00
}
return dataSource
}
func makeInactiveAppsDataSource() -> RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>
{
let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest<InstalledApp>
fetchRequest.relationshipKeyPathsForPrefetching = [#keyPath(InstalledApp.storeApp)]
fetchRequest.predicate = NSPredicate(format: "%K == NO", #keyPath(InstalledApp.isActive))
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \InstalledApp.expirationDate, ascending: true),
NSSortDescriptor(keyPath: \InstalledApp.refreshedDate, ascending: false),
NSSortDescriptor(keyPath: \InstalledApp.name, ascending: true)]
fetchRequest.returnsObjectsAsFaults = false
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
dataSource.cellIdentifierHandler = { _ in "AppCell" }
dataSource.cellConfigurationHandler = { (cell, installedApp, indexPath) in
let tintColor = installedApp.storeApp?.tintColor ?? .altPrimary
let cell = cell as! InstalledAppCollectionViewCell
cell.layoutMargins.left = self.view.layoutMargins.left
cell.layoutMargins.right = self.view.layoutMargins.right
cell.tintColor = UIColor.gray
cell.bannerView.iconImageView.isIndicatingActivity = true
cell.bannerView.buttonLabel.isHidden = true
cell.bannerView.alpha = 1.0
cell.deactivateBadge?.isHidden = true
cell.deactivateBadge?.alpha = 0.0
cell.deactivateBadge?.transform = CGAffineTransform.identity.scaledBy(x: 0.5, y: 0.5)
2020-08-27 15:23:21 -07:00
cell.bannerView.configure(for: installedApp)
cell.bannerView.button.isIndicatingActivity = false
cell.bannerView.button.tintColor = tintColor
cell.bannerView.button.setTitle(NSLocalizedString("ACTIVATE", comment: ""), for: .normal)
cell.bannerView.button.removeTarget(self, action: nil, for: .primaryActionTriggered)
cell.bannerView.button.addTarget(self, action: #selector(MyAppsViewController.activateApp(_:)), for: .primaryActionTriggered)
2020-08-27 15:23:21 -07:00
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Activate %@", comment: ""), installedApp.name)
// Make sure refresh button is correct size.
cell.layoutIfNeeded()
// Ensure no leftover progress from active apps cell reuse.
cell.bannerView.button.progress = nil
if let progress = AppManager.shared.refreshProgress(for: installedApp), progress.fractionCompleted < 1.0
{
cell.bannerView.button.progress = progress
}
else
{
cell.bannerView.button.progress = nil
}
}
dataSource.prefetchHandler = { (item, indexPath, completion) in
RSTAsyncBlockOperation { (operation) in
item.managedObjectContext?.perform {
item.loadIcon { (result) in
switch result
{
case .failure(let error): completion(nil, error)
case .success(let image): completion(image, nil)
}
}
}
}
}
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
let cell = cell as! InstalledAppCollectionViewCell
cell.bannerView.iconImageView.image = image
cell.bannerView.iconImageView.isIndicatingActivity = false
}
return dataSource
}
func updateDataSource()
{
self.dataSource.predicate = nil
}
}
private extension MyAppsViewController
{
func update()
{
if self.updatesDataSource.itemCount > 0
{
self.navigationController?.tabBarItem.badgeValue = String(describing: self.updatesDataSource.itemCount)
UIApplication.shared.applicationIconBadgeNumber = Int(self.updatesDataSource.itemCount)
}
else
{
self.navigationController?.tabBarItem.badgeValue = nil
UIApplication.shared.applicationIconBadgeNumber = 0
}
if self.isViewLoaded
{
UIView.performWithoutAnimation {
self.collectionView.reloadSections(IndexSet(integer: Section.updates.rawValue))
}
}
}
func fetchAppIDs()
{
AppManager.shared.fetchAppIDs { (result) in
do
{
let (_, context) = try result.get()
try context.save()
}
catch
{
print("Failed to fetch App IDs.", error)
}
}
}
func refresh(_ installedApps: [InstalledApp], completionHandler: @escaping ([String : Result<InstalledApp, Error>]) -> Void)
{
let group = AppManager.shared.refresh(installedApps, presentingViewController: self, group: self.refreshGroup)
group.completionHandler = { (results) in
DispatchQueue.main.async {
let failures = results.compactMapValues { (result) -> Error? in
switch result
{
case .failure(OperationError.cancelled): return nil
case .failure(let error): return error
case .success: return nil
}
}
guard !failures.isEmpty else { return }
if let failure = failures.first, results.count == 1
{
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
ToastView(error: failure.value).show(in: self)
}
else
{
let localizedText: String
if failures.count == 1
{
localizedText = NSLocalizedString("Failed to refresh 1 app.", comment: "")
}
else
{
localizedText = String(format: NSLocalizedString("Failed to refresh %@ apps.", comment: ""), NSNumber(value: failures.count))
}
let error = failures.first?.value as NSError?
2020-03-19 15:02:35 -07:00
let detailText = error?.localizedFailure ?? error?.localizedFailureReason ?? error?.localizedDescription
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
let toastView = ToastView(text: localizedText, detailText: detailText, opensLog: true)
toastView.preferredDuration = 4.0
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
toastView.show(in: self)
}
}
self.refreshGroup = nil
completionHandler(results)
}
self.refreshGroup = group
UIView.performWithoutAnimation {
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
}
}
}
private extension MyAppsViewController
{
@IBAction func toggleAppUpdates(_ sender: UIButton)
{
let visibleCells = self.collectionView.visibleCells
self.collectionView.performBatchUpdates({
self.isUpdateSectionCollapsed.toggle()
UIView.animate(withDuration: 0.3, animations: {
if self.isUpdateSectionCollapsed
2019-06-05 11:03:49 -07:00
{
self.updatesDataSource.liveFetchLimit = maximumCollapsedUpdatesCount
self.expandedAppUpdates.removeAll()
for case let cell as UpdateCollectionViewCell in visibleCells
{
cell.mode = .collapsed
}
self.cachedUpdateSizes.removeAll()
sender.titleLabel?.transform = .identity
2019-06-05 11:03:49 -07:00
}
else
2019-06-05 11:03:49 -07:00
{
self.updatesDataSource.liveFetchLimit = 0
sender.titleLabel?.transform = CGAffineTransform.identity.rotated(by: .pi)
2019-06-05 11:03:49 -07:00
}
})
self.collectionView.collectionViewLayout.invalidateLayout()
}, completion: nil)
}
@IBAction func toggleUpdateCellMode(_ sender: UIButton)
{
let point = self.collectionView.convert(sender.center, from: sender.superview)
guard let indexPath = self.collectionView.indexPathForItem(at: point) else { return }
let installedApp = self.dataSource.item(at: indexPath)
let cell = self.collectionView.cellForItem(at: indexPath) as? UpdateCollectionViewCell
if self.expandedAppUpdates.contains(installedApp.bundleIdentifier)
{
self.expandedAppUpdates.remove(installedApp.bundleIdentifier)
cell?.mode = .collapsed
}
else
{
self.expandedAppUpdates.insert(installedApp.bundleIdentifier)
cell?.mode = .expanded
2019-06-05 11:03:49 -07:00
}
self.cachedUpdateSizes[installedApp.bundleIdentifier] = nil
self.collectionView.performBatchUpdates({
self.collectionView.collectionViewLayout.invalidateLayout()
}, completion: nil)
}
@IBAction func refreshApp(_ sender: UIButton)
{
let point = self.collectionView.convert(sender.center, from: sender.superview)
guard let indexPath = self.collectionView.indexPathForItem(at: point) else { return }
let installedApp = self.dataSource.item(at: indexPath)
self.refresh(installedApp)
2019-06-05 11:03:49 -07:00
}
@IBAction func refreshAllApps(_ sender: UIBarButtonItem)
2019-06-05 11:03:49 -07:00
{
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
guard minimuxerStatus else { return }
self.isRefreshingAllApps = true
self.collectionView.collectionViewLayout.invalidateLayout()
let installedApps = InstalledApp.fetchAppsForRefreshingAll(in: DatabaseManager.shared.viewContext)
self.refresh(installedApps) { (result) in
DispatchQueue.main.async {
self.isRefreshingAllApps = false
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
}
}
if #available(iOS 14, *)
{
let interaction = INInteraction.refreshAllApps()
interaction.donate { (error) in
guard let error = error else { return }
print("Failed to donate intent \(interaction.intent).", error)
}
}
}
@IBAction func updateApp(_ sender: UIButton)
{
let point = self.collectionView.convert(sender.center, from: sender.superview)
guard let indexPath = self.collectionView.indexPathForItem(at: point) else { return }
let installedApp = self.dataSource.item(at: indexPath)
let previousProgress = AppManager.shared.installationProgress(for: installedApp)
guard previousProgress == nil else {
previousProgress?.cancel()
return
}
_ = AppManager.shared.update(installedApp, presentingViewController: self) { (result) in
DispatchQueue.main.async {
switch result
{
case .failure(OperationError.cancelled):
self.collectionView.reloadItems(at: [indexPath])
case .failure(let error):
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
ToastView(error: error, opensLog: true).show(in: self)
self.collectionView.reloadItems(at: [indexPath])
case .success:
print("Updated app:", installedApp.bundleIdentifier)
// No need to reload, since the the update cell is gone now.
}
self.update()
}
}
self.collectionView.reloadItems(at: [indexPath])
}
2019-07-28 15:51:36 -07:00
@IBAction func sideloadApp(_ sender: UIBarButtonItem)
{
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
guard minimuxerStatus else { return }
let supportedTypes = UTType.types(tag: "ipa", tagClass: .filenameExtension, conformingTo: nil)
2020-03-30 13:25:14 -07:00
let documentPickerViewController = UIDocumentPickerViewController(forOpeningContentTypes: supportedTypes, asCopy: true)
2020-03-30 13:25:14 -07:00
documentPickerViewController.delegate = self
self.present(documentPickerViewController, animated: true, completion: nil)
2019-07-28 15:51:36 -07:00
}
func sideloadApp(at url: URL, completion: @escaping (Result<Void, Error>) -> Void)
{
let progress = Progress.discreteProgress(totalUnitCount: 100)
self.navigationItem.leftBarButtonItem?.isIndicatingActivity = true
class Context
{
var fileURL: URL?
var application: ALTApplication?
var installedApp: InstalledApp? {
didSet {
self.installedAppContext = self.installedApp?.managedObjectContext
}
}
private var installedAppContext: NSManagedObjectContext?
var error: Error?
}
let temporaryDirectory = FileManager.default.uniqueTemporaryURL()
let unzippedAppDirectory = temporaryDirectory.appendingPathComponent("App")
let context = Context()
let downloadOperation: RSTAsyncBlockOperation?
if url.isFileURL
{
downloadOperation = nil
context.fileURL = url
progress.totalUnitCount -= 20
}
else
{
let downloadProgress = Progress.discreteProgress(totalUnitCount: 100)
downloadOperation = RSTAsyncBlockOperation { (operation) in
let downloadTask = URLSession.shared.downloadTask(with: url) { (fileURL, response, error) in
do
{
let (fileURL, _) = try Result((fileURL, response), error).get()
try FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: true, attributes: nil)
let destinationURL = temporaryDirectory.appendingPathComponent("App.ipa")
try FileManager.default.moveItem(at: fileURL, to: destinationURL)
context.fileURL = destinationURL
}
catch
{
context.error = error
}
operation.finish()
}
downloadProgress.addChild(downloadTask.progress, withPendingUnitCount: 100)
downloadTask.resume()
}
progress.addChild(downloadProgress, withPendingUnitCount: 20)
}
let unzipProgress = Progress.discreteProgress(totalUnitCount: 1)
let unzipAppOperation = BlockOperation {
do
{
if let error = context.error
{
throw error
}
guard let fileURL = context.fileURL else { throw OperationError.invalidParameters }
defer {
try? FileManager.default.removeItem(at: fileURL)
}
try FileManager.default.createDirectory(at: unzippedAppDirectory, withIntermediateDirectories: true, attributes: nil)
let unzippedApplicationURL = try FileManager.default.unzipAppBundle(at: fileURL, toDirectory: unzippedAppDirectory)
guard let application = ALTApplication(fileURL: unzippedApplicationURL) else { throw OperationError.invalidApp }
context.application = application
unzipProgress.completedUnitCount = 1
}
catch
{
context.error = error
}
}
progress.addChild(unzipProgress, withPendingUnitCount: 10)
if let downloadOperation = downloadOperation
{
unzipAppOperation.addDependency(downloadOperation)
}
let installProgress = Progress.discreteProgress(totalUnitCount: 100)
let installAppOperation = RSTAsyncBlockOperation { (operation) in
do
{
if let error = context.error
{
throw error
}
guard let application = context.application else { throw OperationError.invalidParameters }
let group = AppManager.shared.install(application, presentingViewController: self) { (result) in
switch result
{
case .success(let installedApp): context.installedApp = installedApp
case .failure(let error): context.error = error
}
operation.finish()
}
installProgress.addChild(group.progress, withPendingUnitCount: 100)
}
catch
{
context.error = error
operation.finish()
}
}
installAppOperation.completionBlock = {
try? FileManager.default.removeItem(at: temporaryDirectory)
DispatchQueue.main.async {
self.navigationItem.leftBarButtonItem?.isIndicatingActivity = false
self.sideloadingProgressView.observedProgress = nil
self.sideloadingProgressView.setHidden(true, animated: true)
switch Result(context.installedApp, context.error)
{
case .success(let app):
completion(.success(()))
app.managedObjectContext?.perform {
print("Successfully installed app:", app.bundleIdentifier)
}
case .failure(OperationError.cancelled):
completion(.failure((OperationError.cancelled)))
case .failure(let error):
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
ToastView(error: error, opensLog: true).show(in: self)
completion(.failure(error))
}
}
}
installAppOperation.addDependency(unzipAppOperation)
progress.addChild(installProgress, withPendingUnitCount: 65)
self.sideloadingProgress = progress
self.sideloadingProgressView.progress = 0
self.sideloadingProgressView.isHidden = false
self.sideloadingProgressView.observedProgress = self.sideloadingProgress
let operations = [downloadOperation, unzipAppOperation, installAppOperation].compactMap { $0 }
self.operationQueue.addOperations(operations, waitUntilFinished: false)
}
@IBAction func activateApp(_ sender: UIButton)
{
let point = self.collectionView.convert(sender.center, from: sender.superview)
guard let indexPath = self.collectionView.indexPathForItem(at: point) else { return }
let installedApp = self.dataSource.item(at: indexPath)
self.activate(installedApp)
}
@IBAction func deactivateApp(_ sender: UIButton)
{
let point = self.collectionView.convert(sender.center, from: sender.superview)
guard let indexPath = self.collectionView.indexPathForItem(at: point) else { return }
let installedApp = self.dataSource.item(at: indexPath)
self.deactivate(installedApp)
}
@objc func presentInactiveAppsAlert()
{
let message: String
if UserDefaults.standard.activeAppLimitIncludesExtensions
{
message = NSLocalizedString("Non-developer Apple IDs 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("Non-developer Apple IDs 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)
}
func updateCell(at indexPath: IndexPath)
{
guard let cell = collectionView.cellForItem(at: indexPath) as? InstalledAppCollectionViewCell else { return }
let installedApp = self.dataSource.item(at: indexPath)
self.dataSource.cellConfigurationHandler(cell, installedApp, indexPath)
cell.bannerView.iconImageView.isIndicatingActivity = false
}
}
private extension MyAppsViewController
{
func open(_ installedApp: InstalledApp)
{
UIApplication.shared.open(installedApp.openAppURL) { success in
guard !success else { return }
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
ToastView(error: OperationError.openAppFailed(name: installedApp.name), opensLog: true).show(in: self)
}
}
func refresh(_ installedApp: InstalledApp)
{
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
guard minimuxerStatus else { return }
let previousProgress = AppManager.shared.refreshProgress(for: installedApp)
guard previousProgress == nil else {
previousProgress?.cancel()
return
}
self.refresh([installedApp]) { (results) in
// If an error occured, reload the section so the progress bar is no longer visible.
if results.values.contains(where: { $0.error != nil })
{
DispatchQueue.main.async {
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
}
}
print("Finished refreshing with results:", results.map { ($0, $1.error?.localizedDescription ?? "success") })
}
}
func activate(_ installedApp: InstalledApp)
{
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
guard minimuxerStatus else { return }
func finish(_ result: Result<InstalledApp, Error>)
{
do
{
let app = try result.get()
app.managedObjectContext?.perform {
try? app.managedObjectContext?.save()
}
}
catch OperationError.cancelled
{
// Ignore
}
catch
{
print("Failed to activate app:", error)
DispatchQueue.main.async {
installedApp.isActive = false
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
ToastView(error: error, opensLog: true).show(in: self)
}
}
}
if !UserDefaults.standard.isAppLimitDisabled && UserDefaults.standard.activeAppsLimit != nil, #available(iOS 13, *)
{
// UserDefaults.standard.activeAppsLimit is only non-nil on iOS 13.3.1 or later, so the #available check is just so we can use Combine.
guard let app = ALTApplication(fileURL: installedApp.fileURL) else { return finish(.failure(OperationError.invalidApp)) }
var cancellable: AnyCancellable?
cancellable = DatabaseManager.shared.viewContext.registeredObjects.publisher
.compactMap { $0 as? InstalledApp }
.filter(\.isActive)
.map { $0.publisher(for: \.isActive) }
.collect()
.flatMap { publishers in
Publishers.MergeMany(publishers)
}
.first { isActive in !isActive }
.sink { _ in
// A previously active app is now inactive,
// which means there are now enough slots to activate the app,
// so pre-emptively mark it as active to provide visual feedback sooner.
installedApp.isActive = true
cancellable?.cancel()
}
AppManager.shared.deactivateApps(for: app, presentingViewController: self) { result in
cancellable?.cancel()
installedApp.managedObjectContext?.perform {
switch result
{
case .failure(let error):
installedApp.isActive = false
finish(.failure(error))
case .success:
installedApp.isActive = true
AppManager.shared.activate(installedApp, presentingViewController: self, completionHandler: finish(_:))
}
}
}
}
else
{
installedApp.isActive = true
AppManager.shared.activate(installedApp, presentingViewController: self, completionHandler: finish(_:))
}
}
func deactivate(_ installedApp: InstalledApp, completionHandler: ((Result<InstalledApp, Error>) -> Void)? = nil)
{
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
guard installedApp.isActive, minimuxerStatus else { return }
installedApp.isActive = false
AppManager.shared.deactivate(installedApp, presentingViewController: self) { (result) in
do
{
let app = try result.get()
try? app.managedObjectContext?.save()
print("Finished deactivating app:", app.bundleIdentifier)
}
catch
{
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
print("Failed to deactivate app:", error)
DispatchQueue.main.async {
installedApp.isActive = true
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
ToastView(error: error, opensLog: true).show(in: self)
}
}
completionHandler?(result)
}
}
func remove(_ installedApp: InstalledApp)
2019-07-28 15:51:36 -07:00
{
let title = String(format: NSLocalizedString("Remove “%@” from SideStore?", 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: "")
}
2024-08-17 10:28:42 +09:00
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
2019-07-28 15:51:36 -07:00
alertController.addAction(.cancel)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove", comment: ""), style: .destructive, handler: { (action) in
AppManager.shared.remove(installedApp) { (result) in
switch result
{
case .success: break
case .failure(let error):
DispatchQueue.main.async {
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
ToastView(error: error, opensLog: true).show(in: self)
}
}
2019-07-28 15:51:36 -07:00
}
}))
self.present(alertController, animated: true, completion: nil)
}
func backup(_ installedApp: InstalledApp)
{
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
guard minimuxerStatus else { return }
let title = NSLocalizedString("Start Backup?", comment: "")
let message = NSLocalizedString("This will replace any previous backups. Please leave SideStore open until the backup is complete.", comment: "")
let alertController = UIAlertController(title: title, message: message, preferredStyle: .actionSheet)
alertController.addAction(.cancel)
let actionTitle = String(format: NSLocalizedString("Back Up %@", comment: ""), installedApp.name)
alertController.addAction(UIAlertAction(title: actionTitle, style: .default, handler: { (action) in
AppManager.shared.backup(installedApp, presentingViewController: self) { (result) in
do
{
let app = try result.get()
try? app.managedObjectContext?.save()
print("Finished backing up app:", app.bundleIdentifier)
}
catch
{
print("Failed to back up app:", error)
DispatchQueue.main.async {
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
ToastView(error: error, opensLog: true).show(in: self)
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
}
}
}
DispatchQueue.main.async {
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
}
}))
self.present(alertController, animated: true, completion: nil)
}
func restore(_ installedApp: InstalledApp)
{
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
guard minimuxerStatus else { return }
let message = String(format: NSLocalizedString("This will replace all data you currently have in %@.", comment: ""), installedApp.name)
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to restore this backup?", comment: ""), message: message, preferredStyle: .actionSheet)
alertController.addAction(.cancel)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Restore Backup", comment: ""), style: .destructive, handler: { (action) in
AppManager.shared.restore(installedApp, presentingViewController: self) { (result) in
do
{
let app = try result.get()
try? app.managedObjectContext?.save()
print("Finished restoring app:", app.bundleIdentifier)
}
catch
{
print("Failed to restore app:", error)
DispatchQueue.main.async {
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
ToastView(error: error, opensLog: true).show(in: self)
}
}
}
DispatchQueue.main.async {
self.collectionView.reloadSections([Section.activeApps.rawValue])
}
}))
self.present(alertController, animated: true, completion: nil)
}
func exportBackup(for installedApp: InstalledApp)
{
guard let backupURL = FileManager.default.backupDirectoryURL(for: installedApp) else { return }
let documentPicker = UIDocumentPickerViewController(forExporting: [backupURL], asCopy: true)
// Don't set delegate to avoid conflicting with import callbacks.
// documentPicker.delegate = self
self.present(documentPicker, animated: true, completion: nil)
}
func chooseIcon(for installedApp: InstalledApp)
{
self._imagePickerInstalledApp = installedApp
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.allowsEditing = true
self.present(imagePicker, animated: true, completion: nil)
}
func changeIcon(for installedApp: InstalledApp, to image: UIImage?)
{
// Remove previous icon from cache.
self.activeAppsDataSource.prefetchItemCache.removeObject(forKey: installedApp)
self.inactiveAppsDataSource.prefetchItemCache.removeObject(forKey: installedApp)
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
do
{
let tempApp = context.object(with: installedApp.objectID) as! InstalledApp
tempApp.needsResign = true
tempApp.hasAlternateIcon = (image != nil)
if let image = image
{
guard let icon = image.resizing(toFill: CGSize(width: 256, height: 256)),
let iconData = icon.pngData()
else { return }
try iconData.write(to: tempApp.alternateIconURL, options: .atomic)
}
else
{
try FileManager.default.removeItem(at: tempApp.alternateIconURL)
}
try context.save()
if tempApp.isActive
{
DispatchQueue.main.async {
self.refresh(installedApp)
}
}
}
catch
{
print("Failed to change app icon.", error)
DispatchQueue.main.async {
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
ToastView(error: error, opensLog: true).show(in: self)
}
}
}
}
@available(iOS 14, *)
func enableJIT(for installedApp: InstalledApp)
{
let sidejitenabled = UserDefaults.standard.sidejitenable
if #unavailable(iOS 17) {
guard minimuxerStatus else { return }
}
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
if #available(iOS 17, *), !sidejitenabled {
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
ToastView(error: (OperationError.tooNewError as NSError).withLocalizedTitle("No iOS 17 On Device JIT!"), opensLog: true).show(in: self)
AppManager.shared.log(OperationError.tooNewError, operation: .enableJIT, app: installedApp)
return
}
AppManager.shared.enableJIT(for: installedApp) { result in
DispatchQueue.main.async {
switch result
{
case .success: break
case .failure(let error):
merge AltStore 1.6.3, add dynamic anisette lists, merge SideJITServer integration * Change error from Swift.Error to NSError * Adds ResultOperation.localizedFailure * Finish Riley's monster commit 3b38d725d7e8e45fb2c0cb465a3968828616c209 May the Gods have mercy on my soul. * Fix format strings I broke * Include "Enable JIT" errors in Error Log * Fix minimuxer status checking * [skip ci] Update the no wifi message to include VPN * Opens Error Log when tapping ToastView * Fixes Error Log context menu covering cell content * Fixes Error Log context menu appearing while scrolling * Fixes incorrect Search FAQ URL * Fix Error Log showing UIAlertController on iOS 14+ * Fix Error Log not showing UIAlertController on iOS <=13 * Fix wrong color in AuthenticationViewController * Fix typo * Fixes logging non-AltServerErrors as AltServerError.underlyingError * Limits quitting other AltStore/SideStore processes to database migrations * Skips logging cancelled errors * Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. * Conforms OperatingSystemVersion to Comparable * Parses AppVersion.minOSVersion/maxOSVersion from source JSON * Supports non-NSManagedObjects for @Managed properties This allows us to use @Managed with properties that may or may not be NSManagedObjects at runtime (e.g. protocols). If they are, Managed will keep strong reference to context like before. * Supports optional @Managed properties * Conforms AppVersion to AppProtocol * Verifies min/max OS version before downloading app + asks user to download older app version if necessary * Improves error message when file does not exist at AppVersion.downloadURL * Removes unnecessary StoreApp convenience properties * Removes unnecessary StoreApp convenience properties as well as fix other issues * Remove Settings bundle, add SwiftUI view instead Fix refresh all shortcut intent * Update AuthenticationOperation.swift Signed-off-by: June Park <rjp2030@outlook.com> * Fix build issues given by develop * Add availability check to fix CI build(?) * If it's gonna be that way... --------- Signed-off-by: June Park <rjp2030@outlook.com> Co-authored-by: nythepegasus <nythepegasus84@gmail.com> Co-authored-by: Riley Testut <riley@rileytestut.com> Co-authored-by: ny <me@nythepegas.us>
2024-08-06 10:43:52 +09:00
ToastView(error: error, opensLog: true).show(in: self)
AppManager.shared.log(error, operation: .enableJIT, app: installedApp)
}
}
}
}
2019-07-28 15:51:36 -07:00
}
private extension MyAppsViewController
{
@objc func didFetchSource(_ notification: Notification)
{
DispatchQueue.main.async {
if self.updatesDataSource.fetchedResultsController.fetchedObjects == nil
{
do { try self.updatesDataSource.fetchedResultsController.performFetch() }
catch { print("Error fetching:", error) }
}
self.update()
}
}
2019-07-28 15:51:36 -07:00
@objc func importApp(_ notification: Notification)
{
2020-02-11 13:29:28 -08:00
// Make sure left UIBarButtonItem has been set.
self.loadViewIfNeeded()
guard let url = notification.userInfo?[AppDelegate.importAppDeepLinkURLKey] as? URL else { return }
self.sideloadApp(at: url) { (result) in
guard url.isFileURL else { return }
do
{
try FileManager.default.removeItem(at: url)
}
catch
{
print("Unable to remove imported .ipa.", error)
}
}
}
}
extension MyAppsViewController
{
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView
{
let section = Section(rawValue: indexPath.section)!
switch section
{
case .noUpdates: return UICollectionReusableView()
case .updates:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "UpdatesHeader", for: indexPath) as! UpdatesCollectionHeaderView
UIView.performWithoutAnimation {
2019-09-19 11:29:10 -07:00
headerView.button.backgroundColor = UIColor.altPrimary.withAlphaComponent(0.15)
headerView.button.setTitle("", for: .normal)
headerView.button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 28)
2019-09-19 11:29:10 -07:00
headerView.button.setTitleColor(.altPrimary, for: .normal)
headerView.button.addTarget(self, action: #selector(MyAppsViewController.toggleAppUpdates), for: .primaryActionTriggered)
if self.isUpdateSectionCollapsed
{
headerView.button.titleLabel?.transform = .identity
}
else
{
headerView.button.titleLabel?.transform = CGAffineTransform.identity.rotated(by: .pi)
}
headerView.isHidden = (self.updatesDataSource.itemCount <= 2)
headerView.button.layoutIfNeeded()
}
return headerView
case .activeApps where kind == UICollectionView.elementKindSectionHeader:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "ActiveAppsHeader", for: indexPath) as! InstalledAppsCollectionHeaderView
UIView.performWithoutAnimation {
headerView.layoutMargins.left = self.view.layoutMargins.left
headerView.layoutMargins.right = self.view.layoutMargins.right
if UserDefaults.standard.activeAppsLimit == nil || UserDefaults.standard.isAppLimitDisabled
{
headerView.textLabel.text = NSLocalizedString("Installed", comment: "")
}
else
{
headerView.textLabel.text = NSLocalizedString("Active", comment: "")
}
headerView.button.isIndicatingActivity = false
2019-09-19 11:29:10 -07:00
headerView.button.activityIndicatorView.color = .altPrimary
headerView.button.setTitle(NSLocalizedString("Refresh All", comment: ""), for: .normal)
headerView.button.addTarget(self, action: #selector(MyAppsViewController.refreshAllApps(_:)), for: .primaryActionTriggered)
headerView.button.layoutIfNeeded()
2020-08-27 15:25:52 -07:00
if self.isRefreshingAllApps
{
headerView.button.isIndicatingActivity = true
headerView.button.accessibilityLabel = NSLocalizedString("Refreshing", comment: "")
headerView.button.accessibilityTraits.remove(.notEnabled)
}
else
{
headerView.button.isIndicatingActivity = false
headerView.button.accessibilityLabel = nil
}
}
return headerView
case .inactiveApps where kind == UICollectionView.elementKindSectionHeader:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "InactiveAppsHeader", for: indexPath) as! InstalledAppsCollectionHeaderView
UIView.performWithoutAnimation {
headerView.layoutMargins.left = self.view.layoutMargins.left
headerView.layoutMargins.right = self.view.layoutMargins.right
headerView.textLabel.text = NSLocalizedString("Inactive", comment: "")
headerView.button.setTitle(nil, for: .normal)
if #available(iOS 13.0, *)
{
headerView.button.setImage(UIImage(systemName: "questionmark.circle"), for: .normal)
}
headerView.button.addTarget(self, action: #selector(MyAppsViewController.presentInactiveAppsAlert), for: .primaryActionTriggered)
}
return headerView
2020-01-24 14:54:52 -08:00
case .activeApps, .inactiveApps:
2020-01-24 14:54:52 -08:00
let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "InstalledAppsFooter", for: indexPath) as! InstalledAppsCollectionFooterView
guard let team = DatabaseManager.shared.activeTeam() else { return footerView }
switch team.type
2020-01-24 14:54:52 -08:00
{
case .free:
let registeredAppIDs = team.appIDs.count
let maximumAppIDCount = 10
let remainingAppIDs = maximumAppIDCount - registeredAppIDs
if remainingAppIDs == 1
{
footerView.textLabel.text = String(format: NSLocalizedString("1 App ID Remaining", comment: ""))
}
else
{
footerView.textLabel.text = String(format: NSLocalizedString("%@ App IDs Remaining", comment: ""), NSNumber(value: remainingAppIDs))
}
footerView.textLabel.isHidden = remainingAppIDs < 0
case .individual, .organization, .unknown: footerView.textLabel.isHidden = true
@unknown default: break
2020-01-24 14:54:52 -08:00
}
return footerView
}
}
2019-09-12 13:51:03 -07:00
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{
let section = Section.allCases[indexPath.section]
switch section
{
case .updates:
guard let cell = collectionView.cellForItem(at: indexPath) else { break }
self.performSegue(withIdentifier: "showUpdate", sender: cell)
default: break
}
}
}
@available(iOS 13.0, *)
extension MyAppsViewController
{
private func actions(for installedApp: InstalledApp) -> [UIMenuElement]
{
var actions = [UIMenuElement]()
let openAction = UIAction(title: NSLocalizedString("Open", comment: ""), image: UIImage(systemName: "arrow.up.forward.app")) { (action) in
self.open(installedApp)
}
let openMenu = UIMenu(title: "", options: .displayInline, children: [openAction])
let refreshAction = UIAction(title: NSLocalizedString("Refresh", comment: ""), image: UIImage(systemName: "arrow.clockwise")) { (action) in
self.refresh(installedApp)
}
let activateAction = UIAction(title: NSLocalizedString("Activate", comment: ""), image: UIImage(systemName: "checkmark.circle")) { (action) in
self.activate(installedApp)
}
let deactivateAction = UIAction(title: NSLocalizedString("Deactivate", comment: ""), image: UIImage(systemName: "xmark.circle"), attributes: .destructive) { (action) in
self.deactivate(installedApp)
}
let removeAction = UIAction(title: NSLocalizedString("Remove", comment: ""), image: UIImage(systemName: "trash"), attributes: .destructive) { (action) in
self.remove(installedApp)
}
let jitAction = UIAction(title: NSLocalizedString("Enable JIT", comment: ""), image: UIImage(systemName: "bolt")) { (action) in
guard #available(iOS 14, *) else { return }
self.enableJIT(for: installedApp)
}
let backupAction = UIAction(title: NSLocalizedString("Back Up", comment: ""), image: UIImage(systemName: "doc.on.doc")) { (action) in
self.backup(installedApp)
}
let exportBackupAction = UIAction(title: NSLocalizedString("Export Backup", comment: ""), image: UIImage(systemName: "arrow.up.doc")) { (action) in
self.exportBackup(for: installedApp)
}
let restoreBackupAction = UIAction(title: NSLocalizedString("Restore Backup", comment: ""), image: UIImage(systemName: "arrow.down.doc")) { (action) in
self.restore(installedApp)
}
let chooseIconAction = UIAction(title: NSLocalizedString("Photos", comment: ""), image: UIImage(systemName: "photo")) { (action) in
self.chooseIcon(for: installedApp)
}
let removeIconAction = UIAction(title: NSLocalizedString("Remove Custom Icon", comment: ""), image: UIImage(systemName: "trash"), attributes: [.destructive]) { (action) in
self.changeIcon(for: installedApp, to: nil)
}
var changeIconActions = [chooseIconAction]
if installedApp.hasAlternateIcon
{
changeIconActions.append(removeIconAction)
}
let changeIconMenu = UIMenu(title: NSLocalizedString("Change Icon", comment: ""), image: UIImage(systemName: "photo"), children: changeIconActions)
guard installedApp.bundleIdentifier != StoreApp.altstoreAppID else {
#if BETA
return [refreshAction, changeIconMenu]
#else
return [refreshAction]
#endif
}
if installedApp.isActive
{
actions.append(openMenu)
actions.append(refreshAction)
}
else
{
actions.append(activateAction)
}
if installedApp.isActive, #available(iOS 14, *)
{
actions.append(jitAction)
}
#if BETA
actions.append(changeIconMenu)
#endif
if installedApp.isActive
{
actions.append(backupAction)
}
else if let _ = UTTypeCopyDeclaration(installedApp.installedAppUTI as CFString)?.takeRetainedValue() as NSDictionary?, !UserDefaults.standard.isLegacyDeactivationSupported
{
// Allow backing up inactive apps if they are still installed,
// but on an iOS version that no longer supports legacy deactivation.
// This handles edge case where you can't install more apps until you
// delete some, but can't activate inactive apps again to back them up first.
actions.append(backupAction)
}
if let backupDirectoryURL = FileManager.default.backupDirectoryURL(for: installedApp)
{
var backupExists = false
var outError: NSError? = nil
self.coordinator.coordinate(readingItemAt: backupDirectoryURL, options: [.withoutChanges], error: &outError) { (backupDirectoryURL) in
#if DEBUG
backupExists = true
#else
backupExists = FileManager.default.fileExists(atPath: backupDirectoryURL.path)
#endif
}
if backupExists
{
actions.append(exportBackupAction)
if installedApp.isActive
{
actions.append(restoreBackupAction)
}
}
else if let error = outError
{
print("Unable to check if backup exists:", error)
}
}
if installedApp.isActive
{
actions.append(deactivateAction)
}
#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
}
override func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration?
{
let section = Section(rawValue: indexPath.section)!
switch section
{
case .updates, .noUpdates: return nil
case .activeApps, .inactiveApps:
let installedApp = self.dataSource.item(at: indexPath)
return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: nil) { (suggestedActions) -> UIMenu? in
let actions = self.actions(for: installedApp)
let menu = UIMenu(title: "", children: actions)
return menu
}
}
}
override func collectionView(_ collectionView: UICollectionView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview?
{
guard let indexPath = configuration.identifier as? NSIndexPath else { return nil }
guard let cell = collectionView.cellForItem(at: indexPath as IndexPath) as? InstalledAppCollectionViewCell else { return nil }
let parameters = UIPreviewParameters()
parameters.backgroundColor = .clear
parameters.visiblePath = UIBezierPath(roundedRect: cell.bannerView.bounds, cornerRadius: cell.bannerView.layer.cornerRadius)
let preview = UITargetedPreview(view: cell.bannerView, parameters: parameters)
return preview
}
override func collectionView(_ collectionView: UICollectionView, previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview?
{
return self.collectionView(collectionView, previewForHighlightingContextMenuWithConfiguration: configuration)
}
}
extension MyAppsViewController: UICollectionViewDelegateFlowLayout
{
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
let section = Section.allCases[indexPath.section]
switch section
{
case .noUpdates:
2019-10-23 13:32:17 -07:00
let size = CGSize(width: collectionView.bounds.width, height: 44)
return size
case .updates:
let item = self.dataSource.item(at: indexPath)
if let previousHeight = self.cachedUpdateSizes[item.bundleIdentifier]
{
return previousHeight
}
2019-10-23 13:32:17 -07:00
// Manually change cell's width to prevent conflicting with UIView-Encapsulated-Layout-Width constraints.
self.prototypeUpdateCell.frame.size.width = collectionView.bounds.width
let widthConstraint = self.prototypeUpdateCell.contentView.widthAnchor.constraint(equalToConstant: collectionView.bounds.width)
NSLayoutConstraint.activate([widthConstraint])
defer { NSLayoutConstraint.deactivate([widthConstraint]) }
self.dataSource.cellConfigurationHandler(self.prototypeUpdateCell, item, indexPath)
let size = self.prototypeUpdateCell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
self.cachedUpdateSizes[item.bundleIdentifier] = size
return size
case .activeApps, .inactiveApps:
2019-10-23 13:32:17 -07:00
return CGSize(width: collectionView.bounds.width, height: 88)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize
{
let section = Section.allCases[section]
switch section
{
case .noUpdates: return .zero
case .updates:
let height: CGFloat = self.updatesDataSource.itemCount > maximumCollapsedUpdatesCount ? 26 : 0
return CGSize(width: collectionView.bounds.width, height: height)
case .activeApps: return CGSize(width: collectionView.bounds.width, height: 29)
case .inactiveApps where self.inactiveAppsDataSource.itemCount == 0: return .zero
case .inactiveApps: return CGSize(width: collectionView.bounds.width, height: 29)
}
}
2020-01-24 14:54:52 -08:00
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize
{
let section = Section.allCases[section]
func appIDsFooterSize() -> CGSize
2020-01-24 14:54:52 -08:00
{
guard let _ = DatabaseManager.shared.activeTeam() else { return .zero }
let indexPath = IndexPath(row: 0, section: section.rawValue)
let footerView = self.collectionView(collectionView, viewForSupplementaryElementOfKind: UICollectionView.elementKindSectionFooter, at: indexPath) as! InstalledAppsCollectionFooterView
let size = footerView.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height),
withHorizontalFittingPriority: .required,
verticalFittingPriority: .fittingSizeLevel)
return size
2020-01-24 14:54:52 -08:00
}
switch section
{
case .noUpdates: return .zero
case .updates: return .zero
case .activeApps where self.inactiveAppsDataSource.itemCount == 0: return appIDsFooterSize()
case .activeApps: return .zero
case .inactiveApps where self.inactiveAppsDataSource.itemCount == 0: return .zero
case .inactiveApps: return appIDsFooterSize()
}
2020-01-24 14:54:52 -08:00
}
2019-10-23 13:32:17 -07:00
func collectionView(_ myCV: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets
{
let section = Section.allCases[section]
switch section
{
2019-10-23 13:32:17 -07:00
case .noUpdates where self.updatesDataSource.itemCount != 0: return .zero
case .updates where self.updatesDataSource.itemCount == 0: return .zero
default: return UIEdgeInsets(top: 12, left: 0, bottom: 20, right: 0)
}
}
}
extension MyAppsViewController: UICollectionViewDragDelegate
{
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem]
{
switch Section(rawValue: indexPath.section)!
{
case .updates, .noUpdates:
return []
case .activeApps, .inactiveApps:
guard UserDefaults.standard.activeAppsLimit != nil && !UserDefaults.standard.isAppLimitDisabled else { return [] }
guard let cell = collectionView.cellForItem(at: indexPath as IndexPath) as? InstalledAppCollectionViewCell else { return [] }
let item = self.dataSource.item(at: indexPath)
guard item.bundleIdentifier != StoreApp.altstoreAppID else { return [] }
let dragItem = UIDragItem(itemProvider: NSItemProvider(item: nil, typeIdentifier: nil))
dragItem.localObject = item
dragItem.previewProvider = {
let parameters = UIDragPreviewParameters()
parameters.backgroundColor = .clear
parameters.visiblePath = UIBezierPath(roundedRect: cell.bannerView.iconImageView.bounds, cornerRadius: cell.bannerView.iconImageView.layer.cornerRadius)
let preview = UIDragPreview(view: cell.bannerView.iconImageView, parameters: parameters)
return preview
}
return [dragItem]
}
}
func collectionView(_ collectionView: UICollectionView, dragPreviewParametersForItemAt indexPath: IndexPath) -> UIDragPreviewParameters?
{
guard let cell = collectionView.cellForItem(at: indexPath as IndexPath) as? InstalledAppCollectionViewCell else { return nil }
let parameters = UIDragPreviewParameters()
parameters.backgroundColor = .clear
parameters.visiblePath = UIBezierPath(roundedRect: cell.bannerView.frame, cornerRadius: cell.bannerView.layer.cornerRadius)
return parameters
}
func collectionView(_ collectionView: UICollectionView, dragSessionDidEnd session: UIDragSession)
{
let previousDestinationIndexPath = self.dropDestinationIndexPath
self.dropDestinationIndexPath = nil
if let indexPath = previousDestinationIndexPath
{
// Access cell directly to prevent UI glitches due to race conditions when refreshing
self.updateCell(at: indexPath)
}
}
}
extension MyAppsViewController: UICollectionViewDropDelegate
{
func collectionView(_ collectionView: UICollectionView, canHandle session: UIDropSession) -> Bool
{
return session.localDragSession != nil
}
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal
{
guard
!UserDefaults.standard.isAppLimitDisabled,
let activeAppsLimit = UserDefaults.standard.activeAppsLimit,
let installedApp = session.items.first?.localObject as? InstalledApp
else { return UICollectionViewDropProposal(operation: .cancel) }
// Retrieve header attributes for location calculations.
guard
let activeAppsHeaderAttributes = collectionView.layoutAttributesForSupplementaryElement(ofKind: UICollectionView.elementKindSectionHeader, at: IndexPath(item: 0, section: Section.activeApps.rawValue)),
let inactiveAppsHeaderAttributes = collectionView.layoutAttributesForSupplementaryElement(ofKind: UICollectionView.elementKindSectionHeader, at: IndexPath(item: 0, section: Section.inactiveApps.rawValue))
else { return UICollectionViewDropProposal(operation: .cancel) }
var dropDestinationIndexPath: IndexPath? = nil
defer
{
// Animate selection changes.
if dropDestinationIndexPath != self.dropDestinationIndexPath
{
let previousIndexPath = self.dropDestinationIndexPath
self.dropDestinationIndexPath = dropDestinationIndexPath
let indexPaths = [previousIndexPath, dropDestinationIndexPath].compactMap { $0 }
let propertyAnimator = UIViewPropertyAnimator(springTimingParameters: UISpringTimingParameters()) {
for indexPath in indexPaths
{
// Access cell directly so we can animate it correctly.
self.updateCell(at: indexPath)
}
}
propertyAnimator.startAnimation()
}
}
let point = session.location(in: collectionView)
if installedApp.isActive
{
// Deactivating
if point.y > inactiveAppsHeaderAttributes.frame.minY
{
// Inactive apps section.
return UICollectionViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath)
}
else if point.y > activeAppsHeaderAttributes.frame.minY
{
// Active apps section.
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}
else
{
return UICollectionViewDropProposal(operation: .cancel)
}
}
else
{
// Activating
guard point.y > activeAppsHeaderAttributes.frame.minY else {
// Above active apps section.
return UICollectionViewDropProposal(operation: .cancel)
}
guard point.y < inactiveAppsHeaderAttributes.frame.minY else {
// Inactive apps section.
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}
let activeAppsCount = (self.activeAppsDataSource.fetchedResultsController.fetchedObjects ?? []).map { $0.requiredActiveSlots }.reduce(0, +)
let availableActiveApps = max(activeAppsLimit - activeAppsCount, 0)
if installedApp.requiredActiveSlots <= availableActiveApps
{
// Enough active app slots, so no need to deactivate app first.
return UICollectionViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath)
}
else
{
// Not enough active app slots, so we need to deactivate an app.
// Provided destinationIndexPath is inaccurate.
guard let indexPath = collectionView.indexPathForItem(at: point), indexPath.section == Section.activeApps.rawValue else {
// Invalid destination index path.
return UICollectionViewDropProposal(operation: .cancel)
}
let installedApp = self.dataSource.item(at: indexPath)
guard installedApp.bundleIdentifier != StoreApp.altstoreAppID else {
// Can't deactivate AltStore.
return UICollectionViewDropProposal(operation: .forbidden, intent: .insertIntoDestinationIndexPath)
}
// This app can be deactivated!
dropDestinationIndexPath = indexPath
return UICollectionViewDropProposal(operation: .move, intent: .insertIntoDestinationIndexPath)
}
}
}
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator)
{
guard let installedApp = coordinator.session.items.first?.localObject as? InstalledApp else { return }
guard let destinationIndexPath = coordinator.destinationIndexPath else { return }
if installedApp.isActive
{
guard destinationIndexPath.section == Section.inactiveApps.rawValue else { return }
self.deactivate(installedApp)
}
else
{
guard destinationIndexPath.section == Section.activeApps.rawValue else { return }
switch coordinator.proposal.intent
{
case .insertIntoDestinationIndexPath:
installedApp.isActive = true
let previousInstalledApp = self.dataSource.item(at: destinationIndexPath)
self.deactivate(previousInstalledApp) { (result) in
installedApp.managedObjectContext?.perform {
switch result
{
case .failure: installedApp.isActive = false
case .success: self.activate(installedApp)
}
}
}
case .insertAtDestinationIndexPath:
self.activate(installedApp)
case .unspecified: break
@unknown default: break
}
}
}
}
extension MyAppsViewController: NSFetchedResultsControllerDelegate
{
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
{
// Responding to NSFetchedResultsController updates before the collection view has
// been shown may throw exceptions because the collection view cannot accurately
// count the number of items before the update. However, if we manually call
// performBatchUpdates _before_ responding to updates, the collection view can get
// an accurate pre-update item count.
self.collectionView.performBatchUpdates(nil, completion: nil)
self.updatesDataSource.controllerWillChangeContent(controller)
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType)
{
self.updatesDataSource.controller(controller, didChange: sectionInfo, atSectionIndex: UInt(sectionIndex), for: type)
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?)
{
self.updatesDataSource.controller(controller, didChange: anObject, at: indexPath, for: type, newIndexPath: newIndexPath)
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
{
let previousUpdateCount = self.collectionView.numberOfItems(inSection: Section.updates.rawValue)
let updateCount = Int(self.updatesDataSource.itemCount)
if previousUpdateCount == 0 && updateCount > 0
{
// Remove "No Updates Available" cell.
let change = RSTCellContentChange(type: .delete, currentIndexPath: IndexPath(item: 0, section: Section.noUpdates.rawValue), destinationIndexPath: nil)
self.collectionView.add(change)
}
else if previousUpdateCount > 0 && updateCount == 0
{
// Insert "No Updates Available" cell.
let change = RSTCellContentChange(type: .insert, currentIndexPath: nil, destinationIndexPath: IndexPath(item: 0, section: Section.noUpdates.rawValue))
self.collectionView.add(change)
}
self.updatesDataSource.controllerDidChangeContent(controller)
}
}
2019-07-28 15:51:36 -07:00
extension MyAppsViewController: UIDocumentPickerDelegate
{
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL])
{
guard let fileURL = urls.first else { return }
self.sideloadApp(at: fileURL) { (result) in
print("Sideloaded app at \(fileURL) with result:", result)
2019-07-28 15:51:36 -07:00
}
}
}
2019-09-12 13:51:03 -07:00
extension MyAppsViewController: UIViewControllerPreviewingDelegate
{
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController?
{
guard
let indexPath = self.collectionView.indexPathForItem(at: location),
let cell = self.collectionView.cellForItem(at: indexPath)
else { return nil }
let section = Section.allCases[indexPath.section]
switch section
{
case .updates:
previewingContext.sourceRect = cell.frame
let app = self.dataSource.item(at: indexPath)
guard let storeApp = app.storeApp else { return nil}
let appViewController = AppViewController.makeAppViewController(app: storeApp)
return appViewController
default: return nil
}
}
func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController)
{
let point = CGPoint(x: previewingContext.sourceRect.midX, y: previewingContext.sourceRect.midY)
guard let indexPath = self.collectionView.indexPathForItem(at: point), let cell = self.collectionView.cellForItem(at: indexPath) else { return }
self.performSegue(withIdentifier: "showUpdate", sender: cell)
}
}
extension MyAppsViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate
{
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any])
{
defer {
picker.dismiss(animated: true, completion: nil)
self._imagePickerInstalledApp = nil
}
guard let image = info[.editedImage] as? UIImage, let installedApp = self._imagePickerInstalledApp else { return }
self.changeIcon(for: installedApp, to: image)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController)
{
picker.dismiss(animated: true, completion: nil)
self._imagePickerInstalledApp = nil
}
}