mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
Adds support for sideloading .ipa’s via “Open in…”
This commit is contained in:
@@ -52,7 +52,10 @@ private let ReceivedApplicationState: @convention(c) (CFNotificationCenter?, Uns
|
|||||||
|
|
||||||
extension AppDelegate
|
extension AppDelegate
|
||||||
{
|
{
|
||||||
static let openPatreonSettingsDeepLinkNotification = Notification.Name("openPatreonSettingsDeepLinkNotification")
|
static let openPatreonSettingsDeepLinkNotification = Notification.Name("com.rileytestut.AltStore.OpenPatreonSettingsDeepLinkNotification")
|
||||||
|
static let importAppDeepLinkNotification = Notification.Name("com.rileytestut.AltStore.ImportAppDeepLinkNotification")
|
||||||
|
|
||||||
|
static let importAppDeepLinkURLKey = "fileURL"
|
||||||
}
|
}
|
||||||
|
|
||||||
@UIApplicationMain
|
@UIApplicationMain
|
||||||
@@ -115,14 +118,27 @@ private extension AppDelegate
|
|||||||
|
|
||||||
func open(_ url: URL) -> Bool
|
func open(_ url: URL) -> Bool
|
||||||
{
|
{
|
||||||
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return false }
|
if url.isFileURL
|
||||||
guard let host = components.host, host.lowercased() == "patreon" else { return false }
|
{
|
||||||
|
guard url.pathExtension.lowercased() == "ipa" else { return false }
|
||||||
DispatchQueue.main.async {
|
|
||||||
NotificationCenter.default.post(name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil)
|
DispatchQueue.main.async {
|
||||||
|
NotificationCenter.default.post(name: AppDelegate.importAppDeepLinkNotification, object: nil, userInfo: [AppDelegate.importAppDeepLinkURLKey: url])
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return false }
|
||||||
|
guard let host = components.host, host.lowercased() == "patreon" else { return false }
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
NotificationCenter.default.post(name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,23 @@
|
|||||||
<string>1AAAB6FD-E8CE-4B70-8F26-4073215C03B0</string>
|
<string>1AAAB6FD-E8CE-4B70-8F26-4073215C03B0</string>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleDocumentTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeIconFiles</key>
|
||||||
|
<array/>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>iOS App</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Viewer</string>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Alternate</string>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>com.apple.itunes.ipa</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ class MyAppsViewController: UICollectionViewController
|
|||||||
super.init(coder: aDecoder)
|
super.init(coder: aDecoder)
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(MyAppsViewController.didFetchSource(_:)), name: AppManager.didFetchSourceNotification, object: nil)
|
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()
|
override func viewDidLoad()
|
||||||
@@ -577,23 +578,83 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
@IBAction func sideloadApp(_ sender: UIBarButtonItem)
|
@IBAction func sideloadApp(_ sender: UIBarButtonItem)
|
||||||
{
|
{
|
||||||
func sideloadApp()
|
self.presentSideloadingAlert { (shouldContinue) in
|
||||||
{
|
guard shouldContinue else { return }
|
||||||
|
|
||||||
let iOSAppUTI = "com.apple.itunes.ipa" // Declared by the system.
|
let iOSAppUTI = "com.apple.itunes.ipa" // Declared by the system.
|
||||||
|
|
||||||
let documentPickerViewController = UIDocumentPickerViewController(documentTypes: [iOSAppUTI], in: .import)
|
let documentPickerViewController = UIDocumentPickerViewController(documentTypes: [iOSAppUTI], in: .import)
|
||||||
documentPickerViewController.delegate = self
|
documentPickerViewController.delegate = self
|
||||||
self.present(documentPickerViewController, animated: true, completion: nil)
|
self.present(documentPickerViewController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func presentSideloadingAlert(completion: @escaping (Bool) -> Void)
|
||||||
|
{
|
||||||
let alertController = UIAlertController(title: NSLocalizedString("Sideload Apps (Beta)", comment: ""), message: NSLocalizedString("You may only install 10 apps + app extensions per week due to Apple's restrictions.\n\nIf you encounter an app that is not able to be sideloaded, please report the app to support@altstore.io.", comment: ""), preferredStyle: .alert)
|
let alertController = UIAlertController(title: NSLocalizedString("Sideload Apps (Beta)", comment: ""), message: NSLocalizedString("You may only install 10 apps + app extensions per week due to Apple's restrictions.\n\nIf you encounter an app that is not able to be sideloaded, please report the app to support@altstore.io.", comment: ""), preferredStyle: .alert)
|
||||||
alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("OK"), style: .default, handler: { (action) in
|
alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("OK"), style: .default, handler: { (action) in
|
||||||
sideloadApp()
|
completion(true)
|
||||||
|
}))
|
||||||
|
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { (action) in
|
||||||
|
completion(false)
|
||||||
}))
|
}))
|
||||||
alertController.addAction(.cancel)
|
|
||||||
self.present(alertController, animated: true, completion: nil)
|
self.present(alertController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func installApp(at fileURL: URL, completion: @escaping (Result<Void, Error>) -> Void)
|
||||||
|
{
|
||||||
|
self.navigationItem.leftBarButtonItem?.isIndicatingActivity = true
|
||||||
|
|
||||||
|
DispatchQueue.global().async {
|
||||||
|
let temporaryDirectory = FileManager.default.uniqueTemporaryURL()
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
try FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||||
|
|
||||||
|
let unzippedApplicationURL = try FileManager.default.unzipAppBundle(at: fileURL, toDirectory: temporaryDirectory)
|
||||||
|
|
||||||
|
guard let application = ALTApplication(fileURL: unzippedApplicationURL) else { return }
|
||||||
|
|
||||||
|
self.sideloadingProgress = AppManager.shared.install(application, presentingViewController: self) { (result) in
|
||||||
|
try? FileManager.default.removeItem(at: temporaryDirectory)
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if let error = result.error
|
||||||
|
{
|
||||||
|
let toastView = ToastView(text: error.localizedDescription, detailText: nil)
|
||||||
|
toastView.show(in: self.view, duration: 2.0)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
print("Successfully installed app:", application.bundleIdentifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.navigationItem.leftBarButtonItem?.isIndicatingActivity = false
|
||||||
|
self.sideloadingProgressView.observedProgress = nil
|
||||||
|
self.sideloadingProgressView.setHidden(true, animated: true)
|
||||||
|
|
||||||
|
completion(.success(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.sideloadingProgressView.progress = 0
|
||||||
|
self.sideloadingProgressView.isHidden = false
|
||||||
|
self.sideloadingProgressView.observedProgress = self.sideloadingProgress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
try? FileManager.default.removeItem(at: temporaryDirectory)
|
||||||
|
|
||||||
|
self.navigationItem.leftBarButtonItem?.isIndicatingActivity = false
|
||||||
|
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc func presentAlert(for installedApp: InstalledApp)
|
@objc func presentAlert(for installedApp: InstalledApp)
|
||||||
{
|
{
|
||||||
let alertController = UIAlertController(title: nil, message: NSLocalizedString("Removing a sideloaded app only removes it from AltStore. You must also delete it from the home screen to fully uninstall the app.", comment: ""), preferredStyle: .actionSheet)
|
let alertController = UIAlertController(title: nil, message: NSLocalizedString("Removing a sideloaded app only removes it from AltStore. You must also delete it from the home screen to fully uninstall the app.", comment: ""), preferredStyle: .actionSheet)
|
||||||
@@ -643,6 +704,41 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
self.presentAlert(for: installedApp)
|
self.presentAlert(for: installedApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func importApp(_ notification: Notification)
|
||||||
|
{
|
||||||
|
#if BETA
|
||||||
|
|
||||||
|
guard let fileURL = notification.userInfo?[AppDelegate.importAppDeepLinkURLKey] as? URL else { return }
|
||||||
|
guard self.presentedViewController == nil else { return }
|
||||||
|
|
||||||
|
func finish()
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
try FileManager.default.removeItem(at: fileURL)
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("Unable to remove imported .ipa.", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.presentSideloadingAlert { (shouldContinue) in
|
||||||
|
if shouldContinue
|
||||||
|
{
|
||||||
|
self.installApp(at: fileURL) { (result) in
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MyAppsViewController
|
extension MyAppsViewController
|
||||||
@@ -834,51 +930,8 @@ extension MyAppsViewController: UIDocumentPickerDelegate
|
|||||||
{
|
{
|
||||||
guard let fileURL = urls.first else { return }
|
guard let fileURL = urls.first else { return }
|
||||||
|
|
||||||
self.navigationItem.leftBarButtonItem?.isIndicatingActivity = true
|
self.installApp(at: fileURL) { (result) in
|
||||||
|
print("Sideloaded app at \(fileURL) with result:", result)
|
||||||
DispatchQueue.global().async {
|
|
||||||
let temporaryDirectory = FileManager.default.uniqueTemporaryURL()
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
try FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: true, attributes: nil)
|
|
||||||
|
|
||||||
let unzippedApplicationURL = try FileManager.default.unzipAppBundle(at: fileURL, toDirectory: temporaryDirectory)
|
|
||||||
|
|
||||||
guard let application = ALTApplication(fileURL: unzippedApplicationURL) else { return }
|
|
||||||
|
|
||||||
self.sideloadingProgress = AppManager.shared.install(application, presentingViewController: self) { (result) in
|
|
||||||
try? FileManager.default.removeItem(at: temporaryDirectory)
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
if let error = result.error
|
|
||||||
{
|
|
||||||
let toastView = ToastView(text: error.localizedDescription, detailText: nil)
|
|
||||||
toastView.show(in: self.navigationController?.view ?? self.view, duration: 2.0)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
print("Successfully installed app:", application.bundleIdentifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.navigationItem.leftBarButtonItem?.isIndicatingActivity = false
|
|
||||||
self.sideloadingProgressView.observedProgress = nil
|
|
||||||
self.sideloadingProgressView.setHidden(true, animated: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.sideloadingProgressView.progress = 0
|
|
||||||
self.sideloadingProgressView.isHidden = false
|
|
||||||
self.sideloadingProgressView.observedProgress = self.sideloadingProgress
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
try? FileManager.default.removeItem(at: temporaryDirectory)
|
|
||||||
|
|
||||||
self.navigationItem.leftBarButtonItem?.isIndicatingActivity = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,17 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
extension TabBarController
|
||||||
|
{
|
||||||
|
private enum Tab: Int, CaseIterable
|
||||||
|
{
|
||||||
|
case news
|
||||||
|
case browse
|
||||||
|
case myApps
|
||||||
|
case settings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class TabBarController: UITabBarController
|
class TabBarController: UITabBarController
|
||||||
{
|
{
|
||||||
required init?(coder aDecoder: NSCoder)
|
required init?(coder aDecoder: NSCoder)
|
||||||
@@ -15,6 +26,7 @@ class TabBarController: UITabBarController
|
|||||||
super.init(coder: aDecoder)
|
super.init(coder: aDecoder)
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.openPatreonSettings(_:)), name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.openPatreonSettings(_:)), name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil)
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.importApp(_:)), name: AppDelegate.importAppDeepLinkNotification, object: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,7 +34,11 @@ private extension TabBarController
|
|||||||
{
|
{
|
||||||
@objc func openPatreonSettings(_ notification: Notification)
|
@objc func openPatreonSettings(_ notification: Notification)
|
||||||
{
|
{
|
||||||
guard let items = self.tabBar.items else { return }
|
self.selectedIndex = Tab.settings.rawValue
|
||||||
self.selectedIndex = items.count - 1
|
}
|
||||||
|
|
||||||
|
@objc func importApp(_ notification: Notification)
|
||||||
|
{
|
||||||
|
self.selectedIndex = Tab.myApps.rawValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user